Add path interposer
This will allow us to track (and eventually limit) the commands that the build references via $PATH. These are mostly implicit dependencies on the host system -- for Linux, we assume something similar to Ubuntu 14.04 with a few extra packages, but this will let us better define that. This will not catch uses of tools with absolute paths (/bin/bash, etc), but most uses shouldn't be relying on absolute path names anyways. Adds ~400ms on the first startup, ~140ms on subsequent runs, and overhead of a few ms for every forwarded execution. Test: m Test: build/soong/build_test.bash Test: Add `gcc --version`, TEMPORARY_DISABLE_PATH_RESTRICTIONS=true m Change-Id: Id68cbb1c8ceef65bbbb10751e83722c7662d2351
This commit is contained in:
229
cmd/path_interposer/main.go
Normal file
229
cmd/path_interposer/main.go
Normal file
@@ -0,0 +1,229 @@
|
||||
// Copyright 2018 Google Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"android/soong/ui/build/paths"
|
||||
)
|
||||
|
||||
func main() {
|
||||
interposer, err := os.Executable()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Unable to locate interposer executable:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
disableError := false
|
||||
if e, ok := os.LookupEnv("TEMPORARY_DISABLE_PATH_RESTRICTIONS"); ok {
|
||||
disableError = e == "1" || e == "y" || e == "yes" || e == "on" || e == "true"
|
||||
}
|
||||
|
||||
exitCode, err := Main(os.Stdout, os.Stderr, interposer, os.Args, mainOpts{
|
||||
disableError: disableError,
|
||||
|
||||
sendLog: paths.SendLog,
|
||||
config: paths.GetConfig,
|
||||
lookupParents: lookupParents,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
}
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
var usage = fmt.Errorf(`To use the PATH interposer:
|
||||
* Write the original PATH variable to <interposer>_origpath
|
||||
* Set up a directory of symlinks to the PATH interposer, and use that in PATH
|
||||
|
||||
If a tool isn't in the allowed list, a log will be posted to the unix domain
|
||||
socket at <interposer>_log.`)
|
||||
|
||||
type mainOpts struct {
|
||||
disableError bool
|
||||
|
||||
sendLog func(logSocket string, entry *paths.LogEntry, done chan interface{})
|
||||
config func(name string) paths.PathConfig
|
||||
lookupParents func() []paths.LogProcess
|
||||
}
|
||||
|
||||
func Main(stdout, stderr io.Writer, interposer string, args []string, opts mainOpts) (int, error) {
|
||||
base := filepath.Base(args[0])
|
||||
|
||||
origPathFile := interposer + "_origpath"
|
||||
if base == filepath.Base(interposer) {
|
||||
return 1, usage
|
||||
}
|
||||
|
||||
origPath, err := ioutil.ReadFile(origPathFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return 1, usage
|
||||
} else {
|
||||
return 1, fmt.Errorf("Failed to read original PATH: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := &exec.Cmd{
|
||||
Args: args,
|
||||
Env: os.Environ(),
|
||||
|
||||
Stdin: os.Stdin,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
}
|
||||
|
||||
if err := os.Setenv("PATH", string(origPath)); err != nil {
|
||||
return 1, fmt.Errorf("Failed to set PATH env: %v", err)
|
||||
}
|
||||
|
||||
if config := opts.config(base); config.Log || config.Error {
|
||||
var procs []paths.LogProcess
|
||||
if opts.lookupParents != nil {
|
||||
procs = opts.lookupParents()
|
||||
}
|
||||
|
||||
if opts.sendLog != nil {
|
||||
waitForLog := make(chan interface{})
|
||||
opts.sendLog(interposer+"_log", &paths.LogEntry{
|
||||
Basename: base,
|
||||
Args: args,
|
||||
Parents: procs,
|
||||
}, waitForLog)
|
||||
defer func() { <-waitForLog }()
|
||||
}
|
||||
if config.Error && !opts.disableError {
|
||||
return 1, fmt.Errorf("%q is not allowed to be used. See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.", base)
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Path, err = exec.LookPath(base)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
|
||||
if err = cmd.Run(); err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
||||
if status.Exited() {
|
||||
return status.ExitStatus(), nil
|
||||
} else if status.Signaled() {
|
||||
exitCode := 128 + int(status.Signal())
|
||||
return exitCode, nil
|
||||
} else {
|
||||
return 1, exitErr
|
||||
}
|
||||
} else {
|
||||
return 1, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
type procEntry struct {
|
||||
Pid int
|
||||
Ppid int
|
||||
Command string
|
||||
}
|
||||
|
||||
func readProcs() map[int]procEntry {
|
||||
cmd := exec.Command("ps", "-o", "pid,ppid,command")
|
||||
data, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return parseProcs(data)
|
||||
}
|
||||
|
||||
func parseProcs(data []byte) map[int]procEntry {
|
||||
lines := bytes.Split(data, []byte("\n"))
|
||||
if len(lines) < 2 {
|
||||
return nil
|
||||
}
|
||||
// Remove the header
|
||||
lines = lines[1:]
|
||||
|
||||
ret := make(map[int]procEntry, len(lines))
|
||||
for _, line := range lines {
|
||||
fields := bytes.SplitN(line, []byte(" "), 2)
|
||||
if len(fields) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
pid, err := strconv.Atoi(string(fields[0]))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
line = bytes.TrimLeft(fields[1], " ")
|
||||
|
||||
fields = bytes.SplitN(line, []byte(" "), 2)
|
||||
if len(fields) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
ppid, err := strconv.Atoi(string(fields[0]))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ret[pid] = procEntry{
|
||||
Pid: pid,
|
||||
Ppid: ppid,
|
||||
Command: string(bytes.TrimLeft(fields[1], " ")),
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func lookupParents() []paths.LogProcess {
|
||||
procs := readProcs()
|
||||
if procs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
list := []paths.LogProcess{}
|
||||
pid := os.Getpid()
|
||||
for {
|
||||
entry, ok := procs[pid]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
list = append([]paths.LogProcess{
|
||||
{
|
||||
Pid: pid,
|
||||
Command: entry.Command,
|
||||
},
|
||||
}, list...)
|
||||
|
||||
pid = entry.Ppid
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
Reference in New Issue
Block a user