| // 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. | 
 |  | 
 | // This tool tries to prohibit access to tools on the system on which the build | 
 | // is run. | 
 | // | 
 | // The rationale is that if the build uses a binary that is not shipped in the | 
 | // source tree, it is unknowable which version of that binary will be installed | 
 | // and therefore the output of the build will be unpredictable. Therefore, we | 
 | // should make every effort to use only tools under our control. | 
 | // | 
 | // This is currently implemented by a "sandbox" that sets $PATH to a specific, | 
 | // single directory and creates a symlink for every binary in $PATH in it. That | 
 | // symlink will point to path_interposer, which then uses an embedded | 
 | // configuration to determine whether to allow access to the binary (in which | 
 | // case it calls the original executable) or not (in which case it fails). It | 
 | // can also optionally log invocations. | 
 | // | 
 | // This, of course, does not help if one invokes the tool in question with its | 
 | // full path. | 
 | 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) | 
 | 	} | 
 |  | 
 | 	if fi, err := os.Lstat(interposer); err == nil { | 
 | 		if fi.Mode()&os.ModeSymlink != 0 { | 
 | 			link, err := os.Readlink(interposer) | 
 | 			if err != nil { | 
 | 				fmt.Fprintln(os.Stderr, "Unable to read link to interposer executable:", err) | 
 | 				os.Exit(1) | 
 | 			} | 
 | 			if filepath.IsAbs(link) { | 
 | 				interposer = link | 
 | 			} else { | 
 | 				interposer = filepath.Join(filepath.Dir(interposer), link) | 
 | 			} | 
 | 		} | 
 | 	} else { | 
 | 		fmt.Fprintln(os.Stderr, "Unable to stat interposer executable:", err) | 
 | 		os.Exit(1) | 
 | 	} | 
 |  | 
 | 	exitCode, err := Main(os.Stdout, os.Stderr, interposer, os.Args, mainOpts{ | 
 | 		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 { | 
 | 	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 { | 
 | 			return 1, fmt.Errorf("%q is not allowed to be used. See https://android.googlesource.com/platform/build/+/main/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 | 
 | } |