blob: c8c1464e7b1bc0805bdcbeeecdce72fd4b9fe79e [file] [log] [blame]
Dan Willemsen18490112018-05-25 16:30:04 -07001// 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
Lukacs T. Berki2388f642022-05-06 12:42:05 +020015// 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.
Dan Willemsen18490112018-05-25 16:30:04 -070032package 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
Dan Willemsen18490112018-05-25 16:30:04 -070073 exitCode, err := Main(os.Stdout, os.Stderr, interposer, os.Args, mainOpts{
Dan Willemsen18490112018-05-25 16:30:04 -070074 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 {
Dan Willemsen18490112018-05-25 16:30:04 -070092 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 }
Dan Willemsen347ba752020-05-01 16:29:00 -0700142 if config.Error {
Elliott Hughes10363162024-01-09 22:02:03 +0000143 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)
Dan Willemsen18490112018-05-25 16:30:04 -0700144 }
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}