1// Copyright 2018 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// This tool tries to prohibit access to tools on the system on which the build 16// is run. 17// 18// The rationale is that if the build uses a binary that is not shipped in the 19// source tree, it is unknowable which version of that binary will be installed 20// and therefore the output of the build will be unpredictable. Therefore, we 21// should make every effort to use only tools under our control. 22// 23// This is currently implemented by a "sandbox" that sets $PATH to a specific, 24// single directory and creates a symlink for every binary in $PATH in it. That 25// symlink will point to path_interposer, which then uses an embedded 26// configuration to determine whether to allow access to the binary (in which 27// case it calls the original executable) or not (in which case it fails). It 28// can also optionally log invocations. 29// 30// This, of course, does not help if one invokes the tool in question with its 31// full path. 32package main 33 34import ( 35 "bytes" 36 "fmt" 37 "io" 38 "io/ioutil" 39 "os" 40 "os/exec" 41 "path/filepath" 42 "strconv" 43 "syscall" 44 45 "android/soong/ui/build/paths" 46) 47 48func main() { 49 interposer, err := os.Executable() 50 if err != nil { 51 fmt.Fprintln(os.Stderr, "Unable to locate interposer executable:", err) 52 os.Exit(1) 53 } 54 55 if fi, err := os.Lstat(interposer); err == nil { 56 if fi.Mode()&os.ModeSymlink != 0 { 57 link, err := os.Readlink(interposer) 58 if err != nil { 59 fmt.Fprintln(os.Stderr, "Unable to read link to interposer executable:", err) 60 os.Exit(1) 61 } 62 if filepath.IsAbs(link) { 63 interposer = link 64 } else { 65 interposer = filepath.Join(filepath.Dir(interposer), link) 66 } 67 } 68 } else { 69 fmt.Fprintln(os.Stderr, "Unable to stat interposer executable:", err) 70 os.Exit(1) 71 } 72 73 exitCode, err := Main(os.Stdout, os.Stderr, interposer, os.Args, mainOpts{ 74 sendLog: paths.SendLog, 75 config: paths.GetConfig, 76 lookupParents: lookupParents, 77 }) 78 if err != nil { 79 fmt.Fprintln(os.Stderr, err.Error()) 80 } 81 os.Exit(exitCode) 82} 83 84var usage = fmt.Errorf(`To use the PATH interposer: 85 * Write the original PATH variable to <interposer>_origpath 86 * Set up a directory of symlinks to the PATH interposer, and use that in PATH 87 88If a tool isn't in the allowed list, a log will be posted to the unix domain 89socket at <interposer>_log.`) 90 91type mainOpts struct { 92 sendLog func(logSocket string, entry *paths.LogEntry, done chan interface{}) 93 config func(name string) paths.PathConfig 94 lookupParents func() []paths.LogProcess 95} 96 97func Main(stdout, stderr io.Writer, interposer string, args []string, opts mainOpts) (int, error) { 98 base := filepath.Base(args[0]) 99 100 origPathFile := interposer + "_origpath" 101 if base == filepath.Base(interposer) { 102 return 1, usage 103 } 104 105 origPath, err := ioutil.ReadFile(origPathFile) 106 if err != nil { 107 if os.IsNotExist(err) { 108 return 1, usage 109 } else { 110 return 1, fmt.Errorf("Failed to read original PATH: %v", err) 111 } 112 } 113 114 cmd := &exec.Cmd{ 115 Args: args, 116 Env: os.Environ(), 117 118 Stdin: os.Stdin, 119 Stdout: stdout, 120 Stderr: stderr, 121 } 122 123 if err := os.Setenv("PATH", string(origPath)); err != nil { 124 return 1, fmt.Errorf("Failed to set PATH env: %v", err) 125 } 126 127 if config := opts.config(base); config.Log || config.Error { 128 var procs []paths.LogProcess 129 if opts.lookupParents != nil { 130 procs = opts.lookupParents() 131 } 132 133 if opts.sendLog != nil { 134 waitForLog := make(chan interface{}) 135 opts.sendLog(interposer+"_log", &paths.LogEntry{ 136 Basename: base, 137 Args: args, 138 Parents: procs, 139 }, waitForLog) 140 defer func() { <-waitForLog }() 141 } 142 if config.Error { 143 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) 144 } 145 } 146 147 cmd.Path, err = exec.LookPath(base) 148 if err != nil { 149 return 1, err 150 } 151 152 if err = cmd.Run(); err != nil { 153 if exitErr, ok := err.(*exec.ExitError); ok { 154 if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { 155 if status.Exited() { 156 return status.ExitStatus(), nil 157 } else if status.Signaled() { 158 exitCode := 128 + int(status.Signal()) 159 return exitCode, nil 160 } else { 161 return 1, exitErr 162 } 163 } else { 164 return 1, nil 165 } 166 } 167 } 168 169 return 0, nil 170} 171 172type procEntry struct { 173 Pid int 174 Ppid int 175 Command string 176} 177 178func readProcs() map[int]procEntry { 179 cmd := exec.Command("ps", "-o", "pid,ppid,command") 180 data, err := cmd.Output() 181 if err != nil { 182 return nil 183 } 184 185 return parseProcs(data) 186} 187 188func parseProcs(data []byte) map[int]procEntry { 189 lines := bytes.Split(data, []byte("\n")) 190 if len(lines) < 2 { 191 return nil 192 } 193 // Remove the header 194 lines = lines[1:] 195 196 ret := make(map[int]procEntry, len(lines)) 197 for _, line := range lines { 198 fields := bytes.SplitN(line, []byte(" "), 2) 199 if len(fields) != 2 { 200 continue 201 } 202 203 pid, err := strconv.Atoi(string(fields[0])) 204 if err != nil { 205 continue 206 } 207 208 line = bytes.TrimLeft(fields[1], " ") 209 210 fields = bytes.SplitN(line, []byte(" "), 2) 211 if len(fields) != 2 { 212 continue 213 } 214 215 ppid, err := strconv.Atoi(string(fields[0])) 216 if err != nil { 217 continue 218 } 219 220 ret[pid] = procEntry{ 221 Pid: pid, 222 Ppid: ppid, 223 Command: string(bytes.TrimLeft(fields[1], " ")), 224 } 225 } 226 227 return ret 228} 229 230func lookupParents() []paths.LogProcess { 231 procs := readProcs() 232 if procs == nil { 233 return nil 234 } 235 236 list := []paths.LogProcess{} 237 pid := os.Getpid() 238 for { 239 entry, ok := procs[pid] 240 if !ok { 241 break 242 } 243 244 list = append([]paths.LogProcess{ 245 { 246 Pid: pid, 247 Command: entry.Command, 248 }, 249 }, list...) 250 251 pid = entry.Ppid 252 } 253 254 return list 255} 256