blob: 075bf2eb7bd8efd4f3f98e84c8629acc73461f54 [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
15package build
16
17import (
18 "fmt"
19 "io/ioutil"
20 "os"
Dan Willemsen4e2456b2019-10-03 16:45:58 -070021 "os/exec"
Dan Willemsen18490112018-05-25 16:30:04 -070022 "path/filepath"
Dan Willemsen417be1f2018-10-30 23:18:54 -070023 "runtime"
Dan Willemsen18490112018-05-25 16:30:04 -070024 "strings"
25
26 "github.com/google/blueprint/microfactory"
27
28 "android/soong/ui/build/paths"
Nan Zhang17f27672018-12-12 16:01:49 -080029 "android/soong/ui/metrics"
Dan Willemsen18490112018-05-25 16:30:04 -070030)
31
Jingwen Chen6a9bfa12020-11-18 07:05:12 -050032// parsePathDir returns the list of filenames of readable files in a directory.
33// This does not recurse into subdirectories, and does not contain subdirectory
34// names in the list.
Dan Willemsen18490112018-05-25 16:30:04 -070035func parsePathDir(dir string) []string {
36 f, err := os.Open(dir)
37 if err != nil {
38 return nil
39 }
40 defer f.Close()
41
42 if s, err := f.Stat(); err != nil || !s.IsDir() {
43 return nil
44 }
45
46 infos, err := f.Readdir(-1)
47 if err != nil {
48 return nil
49 }
50
51 ret := make([]string, 0, len(infos))
52 for _, info := range infos {
53 if m := info.Mode(); !m.IsDir() && m&0111 != 0 {
54 ret = append(ret, info.Name())
55 }
56 }
57 return ret
58}
59
Taylor Santiago8b0bed72024-09-03 13:30:22 -070060func updatePathForSandbox(config Config) {
61 wd, err := os.Getwd()
62 if err != nil {
63 return
64 }
65
66 var newPath []string
67 if path, ok := config.Environment().Get("PATH"); ok && path != "" {
68 entries := strings.Split(path, string(filepath.ListSeparator))
69 for _, ent := range entries {
70 newPath = append(newPath, config.sandboxPath(wd, ent))
71 }
72 }
73 config.Environment().Set("PATH", strings.Join(newPath, string(filepath.ListSeparator)))
74}
75
Jingwen Chen6a9bfa12020-11-18 07:05:12 -050076// SetupLitePath is the "lite" version of SetupPath used for dumpvars, or other
77// places that does not need the full logging capabilities of path_interposer,
78// wants the minimal performance overhead, and still get the benefits of $PATH
79// hermeticity.
Patrice Arrudaae2694b2020-06-04 19:34:41 +000080func SetupLitePath(ctx Context, config Config, tmpDir string) {
Jingwen Chen6a9bfa12020-11-18 07:05:12 -050081 // Don't replace the path twice.
Dan Willemsen4e2456b2019-10-03 16:45:58 -070082 if config.pathReplaced {
83 return
84 }
85
86 ctx.BeginTrace(metrics.RunSetupTool, "litepath")
87 defer ctx.EndTrace()
88
89 origPath, _ := config.Environment().Get("PATH")
Patrice Arrudaae2694b2020-06-04 19:34:41 +000090
Jingwen Chen6a9bfa12020-11-18 07:05:12 -050091 // If tmpDir is empty, the default TMPDIR is used from config.
Patrice Arrudaae2694b2020-06-04 19:34:41 +000092 if tmpDir == "" {
93 tmpDir, _ = config.Environment().Get("TMPDIR")
94 }
95 myPath := filepath.Join(tmpDir, "path")
Dan Willemsen4e2456b2019-10-03 16:45:58 -070096 ensureEmptyDirectoriesExist(ctx, myPath)
97
98 os.Setenv("PATH", origPath)
Jingwen Chen6a9bfa12020-11-18 07:05:12 -050099 // Iterate over the ACL configuration of host tools for this build.
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700100 for name, pathConfig := range paths.Configuration {
101 if !pathConfig.Symlink {
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500102 // Excludes 'Forbidden' and 'LinuxOnlyPrebuilt' PathConfigs.
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700103 continue
104 }
105
106 origExec, err := exec.LookPath(name)
107 if err != nil {
108 continue
109 }
110 origExec, err = filepath.Abs(origExec)
111 if err != nil {
112 continue
113 }
114
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500115 // Symlink allowed host tools into a directory for hermeticity.
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700116 err = os.Symlink(origExec, filepath.Join(myPath, name))
117 if err != nil {
118 ctx.Fatalln("Failed to create symlink:", err)
119 }
120 }
121
122 myPath, _ = filepath.Abs(myPath)
123
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500124 // Set up the checked-in prebuilts path directory for the current host OS.
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700125 prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86")
126 myPath = prebuiltsPath + string(os.PathListSeparator) + myPath
127
Cole Faustb1fbc792023-02-27 12:16:11 -0800128 if value, _ := config.Environment().Get("BUILD_BROKEN_PYTHON_IS_PYTHON2"); value == "true" {
129 py2Path, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86/py2")
130 if info, err := os.Stat(py2Path); err == nil && info.IsDir() {
131 myPath = py2Path + string(os.PathListSeparator) + myPath
132 }
133 } else if value != "" {
134 ctx.Fatalf("BUILD_BROKEN_PYTHON_IS_PYTHON2 can only be set to 'true' or an empty string, but got %s\n", value)
135 }
136
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500137 // Set $PATH to be the directories containing the host tool symlinks, and
138 // the prebuilts directory for the current host OS.
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700139 config.Environment().Set("PATH", myPath)
Taylor Santiago8b0bed72024-09-03 13:30:22 -0700140 updatePathForSandbox(config)
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700141 config.pathReplaced = true
142}
143
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500144// SetupPath uses the path_interposer to intercept calls to $PATH binaries, and
145// communicates with the interposer to validate allowed $PATH binaries at
146// runtime, using logs as a medium.
147//
148// This results in hermetic directories in $PATH containing only allowed host
149// tools for the build, and replaces $PATH to contain *only* these directories,
150// and enables an incremental restriction of tools allowed in the $PATH without
151// breaking existing use cases.
Dan Willemsen18490112018-05-25 16:30:04 -0700152func SetupPath(ctx Context, config Config) {
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500153 // Don't replace $PATH twice.
Dan Willemsen18490112018-05-25 16:30:04 -0700154 if config.pathReplaced {
155 return
156 }
157
Nan Zhang17f27672018-12-12 16:01:49 -0800158 ctx.BeginTrace(metrics.RunSetupTool, "path")
Dan Willemsen18490112018-05-25 16:30:04 -0700159 defer ctx.EndTrace()
160
161 origPath, _ := config.Environment().Get("PATH")
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500162 // The directory containing symlinks from binaries in $PATH to the interposer.
Dan Willemsen18490112018-05-25 16:30:04 -0700163 myPath := filepath.Join(config.OutDir(), ".path")
164 interposer := myPath + "_interposer"
165
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500166 // Bootstrap the path_interposer Go binary with microfactory.
Dan Willemsen18490112018-05-25 16:30:04 -0700167 var cfg microfactory.Config
168 cfg.Map("android/soong", "build/soong")
169 cfg.TrimPath, _ = filepath.Abs(".")
170 if _, err := microfactory.Build(&cfg, interposer, "android/soong/cmd/path_interposer"); err != nil {
171 ctx.Fatalln("Failed to build path interposer:", err)
172 }
173
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500174 // Save the original $PATH in a file.
Dan Willemsen18490112018-05-25 16:30:04 -0700175 if err := ioutil.WriteFile(interposer+"_origpath", []byte(origPath), 0777); err != nil {
176 ctx.Fatalln("Failed to write original path:", err)
177 }
178
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500179 // Communication with the path interposer works over log entries. Set up the
180 // listener channel for the log entries here.
Dan Willemsen18490112018-05-25 16:30:04 -0700181 entries, err := paths.LogListener(ctx.Context, interposer+"_log")
182 if err != nil {
183 ctx.Fatalln("Failed to listen for path logs:", err)
184 }
185
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500186 // Loop over all log entry listener channels to validate usage of only
187 // allowed PATH tools at runtime.
Dan Willemsen18490112018-05-25 16:30:04 -0700188 go func() {
189 for log := range entries {
190 curPid := os.Getpid()
191 for i, proc := range log.Parents {
192 if proc.Pid == curPid {
193 log.Parents = log.Parents[i:]
194 break
195 }
196 }
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500197 // Compute the error message along with the process tree, including
198 // parents, for this log line.
Dan Willemsen18490112018-05-25 16:30:04 -0700199 procPrints := []string{
Elliott Hughes10363162024-01-09 22:02:03 +0000200 "See https://android.googlesource.com/platform/build/+/main/Changes.md#PATH_Tools for more information.",
Dan Willemsen18490112018-05-25 16:30:04 -0700201 }
202 if len(log.Parents) > 0 {
203 procPrints = append(procPrints, "Process tree:")
204 for i, proc := range log.Parents {
205 procPrints = append(procPrints, fmt.Sprintf("%s→ %s", strings.Repeat(" ", i), proc.Command))
206 }
207 }
208
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500209 // Validate usage against disallowed or missing PATH tools.
Dan Willemsen18490112018-05-25 16:30:04 -0700210 config := paths.GetConfig(log.Basename)
211 if config.Error {
212 ctx.Printf("Disallowed PATH tool %q used: %#v", log.Basename, log.Args)
213 for _, line := range procPrints {
214 ctx.Println(line)
215 }
216 } else {
217 ctx.Verbosef("Unknown PATH tool %q used: %#v", log.Basename, log.Args)
218 for _, line := range procPrints {
219 ctx.Verboseln(line)
220 }
221 }
222 }
223 }()
224
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500225 // Create the .path directory.
Dan Willemsen18490112018-05-25 16:30:04 -0700226 ensureEmptyDirectoriesExist(ctx, myPath)
227
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500228 // Compute the full list of binaries available in the original $PATH.
Dan Willemsen18490112018-05-25 16:30:04 -0700229 var execs []string
230 for _, pathEntry := range filepath.SplitList(origPath) {
231 if pathEntry == "" {
232 // Ignore the current directory
233 continue
234 }
235 // TODO(dwillemsen): remove path entries under TOP? or anything
236 // that looks like an android source dir? They won't exist on
237 // the build servers, since they're added by envsetup.sh.
238 // (Except for the JDK, which is configured in ui/build/config.go)
239
240 execs = append(execs, parsePathDir(pathEntry)...)
241 }
242
Dan Willemsen347ba752020-05-01 16:29:00 -0700243 if config.Environment().IsEnvTrue("TEMPORARY_DISABLE_PATH_RESTRICTIONS") {
244 ctx.Fatalln("TEMPORARY_DISABLE_PATH_RESTRICTIONS was a temporary migration method, and is now obsolete.")
245 }
246
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500247 // Create symlinks from the path_interposer binary to all binaries for each
248 // directory in the original $PATH. This ensures that during the build,
249 // every call to a binary that's expected to be in the $PATH will be
250 // intercepted by the path_interposer binary, and validated with the
251 // LogEntry listener above at build time.
Dan Willemsen18490112018-05-25 16:30:04 -0700252 for _, name := range execs {
Dan Willemsen347ba752020-05-01 16:29:00 -0700253 if !paths.GetConfig(name).Symlink {
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500254 // Ignore host tools that shouldn't be symlinked.
Dan Willemsen18490112018-05-25 16:30:04 -0700255 continue
256 }
257
258 err := os.Symlink("../.path_interposer", filepath.Join(myPath, name))
259 // Intentionally ignore existing files -- that means that we
260 // just created it, and the first one should win.
261 if err != nil && !os.IsExist(err) {
262 ctx.Fatalln("Failed to create symlink:", err)
263 }
264 }
265
266 myPath, _ = filepath.Abs(myPath)
Dan Willemsen417be1f2018-10-30 23:18:54 -0700267
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500268 // We put some prebuilts in $PATH, since it's infeasible to add dependencies
269 // for all of them.
Dan Willemsen733547d2019-02-14 20:11:26 -0800270 prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86")
271 myPath = prebuiltsPath + string(os.PathListSeparator) + myPath
Dan Willemsen417be1f2018-10-30 23:18:54 -0700272
Cole Faustb1fbc792023-02-27 12:16:11 -0800273 if value, _ := config.Environment().Get("BUILD_BROKEN_PYTHON_IS_PYTHON2"); value == "true" {
274 py2Path, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86/py2")
275 if info, err := os.Stat(py2Path); err == nil && info.IsDir() {
276 myPath = py2Path + string(os.PathListSeparator) + myPath
277 }
278 } else if value != "" {
279 ctx.Fatalf("BUILD_BROKEN_PYTHON_IS_PYTHON2 can only be set to 'true' or an empty string, but got %s\n", value)
280 }
281
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500282 // Replace the $PATH variable with the path_interposer symlinks, and
283 // checked-in prebuilts.
Dan Willemsen18490112018-05-25 16:30:04 -0700284 config.Environment().Set("PATH", myPath)
Taylor Santiago8b0bed72024-09-03 13:30:22 -0700285 updatePathForSandbox(config)
Dan Willemsen18490112018-05-25 16:30:04 -0700286 config.pathReplaced = true
287}