blob: b92d79959add42688da88c48c05cef5c35f6fd99 [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"
23 "strings"
24
25 "github.com/google/blueprint/microfactory"
26
27 "android/soong/ui/build/paths"
Nan Zhang17f27672018-12-12 16:01:49 -080028 "android/soong/ui/metrics"
Dan Willemsen18490112018-05-25 16:30:04 -070029)
30
Jingwen Chen6a9bfa12020-11-18 07:05:12 -050031// parsePathDir returns the list of filenames of readable files in a directory.
32// This does not recurse into subdirectories, and does not contain subdirectory
33// names in the list.
Dan Willemsen18490112018-05-25 16:30:04 -070034func parsePathDir(dir string) []string {
35 f, err := os.Open(dir)
36 if err != nil {
37 return nil
38 }
39 defer f.Close()
40
41 if s, err := f.Stat(); err != nil || !s.IsDir() {
42 return nil
43 }
44
45 infos, err := f.Readdir(-1)
46 if err != nil {
47 return nil
48 }
49
50 ret := make([]string, 0, len(infos))
51 for _, info := range infos {
52 if m := info.Mode(); !m.IsDir() && m&0111 != 0 {
53 ret = append(ret, info.Name())
54 }
55 }
56 return ret
57}
58
Taylor Santiago8b0bed72024-09-03 13:30:22 -070059func updatePathForSandbox(config Config) {
60 wd, err := os.Getwd()
61 if err != nil {
62 return
63 }
64
65 var newPath []string
66 if path, ok := config.Environment().Get("PATH"); ok && path != "" {
67 entries := strings.Split(path, string(filepath.ListSeparator))
68 for _, ent := range entries {
69 newPath = append(newPath, config.sandboxPath(wd, ent))
70 }
71 }
72 config.Environment().Set("PATH", strings.Join(newPath, string(filepath.ListSeparator)))
73}
74
Jingwen Chen6a9bfa12020-11-18 07:05:12 -050075// SetupLitePath is the "lite" version of SetupPath used for dumpvars, or other
76// places that does not need the full logging capabilities of path_interposer,
77// wants the minimal performance overhead, and still get the benefits of $PATH
78// hermeticity.
Patrice Arrudaae2694b2020-06-04 19:34:41 +000079func SetupLitePath(ctx Context, config Config, tmpDir string) {
Jingwen Chen6a9bfa12020-11-18 07:05:12 -050080 // Don't replace the path twice.
Dan Willemsen4e2456b2019-10-03 16:45:58 -070081 if config.pathReplaced {
82 return
83 }
84
85 ctx.BeginTrace(metrics.RunSetupTool, "litepath")
86 defer ctx.EndTrace()
87
88 origPath, _ := config.Environment().Get("PATH")
Patrice Arrudaae2694b2020-06-04 19:34:41 +000089
Jingwen Chen6a9bfa12020-11-18 07:05:12 -050090 // If tmpDir is empty, the default TMPDIR is used from config.
Patrice Arrudaae2694b2020-06-04 19:34:41 +000091 if tmpDir == "" {
92 tmpDir, _ = config.Environment().Get("TMPDIR")
93 }
94 myPath := filepath.Join(tmpDir, "path")
Dan Willemsen4e2456b2019-10-03 16:45:58 -070095 ensureEmptyDirectoriesExist(ctx, myPath)
96
97 os.Setenv("PATH", origPath)
Jingwen Chen6a9bfa12020-11-18 07:05:12 -050098 // Iterate over the ACL configuration of host tools for this build.
Dan Willemsen4e2456b2019-10-03 16:45:58 -070099 for name, pathConfig := range paths.Configuration {
100 if !pathConfig.Symlink {
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500101 // Excludes 'Forbidden' and 'LinuxOnlyPrebuilt' PathConfigs.
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700102 continue
103 }
104
105 origExec, err := exec.LookPath(name)
106 if err != nil {
107 continue
108 }
109 origExec, err = filepath.Abs(origExec)
110 if err != nil {
111 continue
112 }
113
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500114 // Symlink allowed host tools into a directory for hermeticity.
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700115 err = os.Symlink(origExec, filepath.Join(myPath, name))
116 if err != nil {
117 ctx.Fatalln("Failed to create symlink:", err)
118 }
119 }
120
121 myPath, _ = filepath.Abs(myPath)
122
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500123 // Set up the checked-in prebuilts path directory for the current host OS.
Colin Cross92ac46d2025-02-26 19:25:23 -0800124 prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + config.PrebuiltOS())
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700125 myPath = prebuiltsPath + string(os.PathListSeparator) + myPath
126
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500127 // Set $PATH to be the directories containing the host tool symlinks, and
128 // the prebuilts directory for the current host OS.
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700129 config.Environment().Set("PATH", myPath)
Taylor Santiago8b0bed72024-09-03 13:30:22 -0700130 updatePathForSandbox(config)
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700131 config.pathReplaced = true
132}
133
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500134// SetupPath uses the path_interposer to intercept calls to $PATH binaries, and
135// communicates with the interposer to validate allowed $PATH binaries at
136// runtime, using logs as a medium.
137//
138// This results in hermetic directories in $PATH containing only allowed host
139// tools for the build, and replaces $PATH to contain *only* these directories,
140// and enables an incremental restriction of tools allowed in the $PATH without
141// breaking existing use cases.
Dan Willemsen18490112018-05-25 16:30:04 -0700142func SetupPath(ctx Context, config Config) {
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500143 // Don't replace $PATH twice.
Dan Willemsen18490112018-05-25 16:30:04 -0700144 if config.pathReplaced {
145 return
146 }
147
Nan Zhang17f27672018-12-12 16:01:49 -0800148 ctx.BeginTrace(metrics.RunSetupTool, "path")
Dan Willemsen18490112018-05-25 16:30:04 -0700149 defer ctx.EndTrace()
150
151 origPath, _ := config.Environment().Get("PATH")
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500152 // The directory containing symlinks from binaries in $PATH to the interposer.
Dan Willemsen18490112018-05-25 16:30:04 -0700153 myPath := filepath.Join(config.OutDir(), ".path")
154 interposer := myPath + "_interposer"
155
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500156 // Bootstrap the path_interposer Go binary with microfactory.
Dan Willemsen18490112018-05-25 16:30:04 -0700157 var cfg microfactory.Config
158 cfg.Map("android/soong", "build/soong")
159 cfg.TrimPath, _ = filepath.Abs(".")
160 if _, err := microfactory.Build(&cfg, interposer, "android/soong/cmd/path_interposer"); err != nil {
161 ctx.Fatalln("Failed to build path interposer:", err)
162 }
163
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500164 // Save the original $PATH in a file.
Dan Willemsen18490112018-05-25 16:30:04 -0700165 if err := ioutil.WriteFile(interposer+"_origpath", []byte(origPath), 0777); err != nil {
166 ctx.Fatalln("Failed to write original path:", err)
167 }
168
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500169 // Communication with the path interposer works over log entries. Set up the
170 // listener channel for the log entries here.
Dan Willemsen18490112018-05-25 16:30:04 -0700171 entries, err := paths.LogListener(ctx.Context, interposer+"_log")
172 if err != nil {
173 ctx.Fatalln("Failed to listen for path logs:", err)
174 }
175
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500176 // Loop over all log entry listener channels to validate usage of only
177 // allowed PATH tools at runtime.
Dan Willemsen18490112018-05-25 16:30:04 -0700178 go func() {
179 for log := range entries {
180 curPid := os.Getpid()
181 for i, proc := range log.Parents {
182 if proc.Pid == curPid {
183 log.Parents = log.Parents[i:]
184 break
185 }
186 }
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500187 // Compute the error message along with the process tree, including
188 // parents, for this log line.
Dan Willemsen18490112018-05-25 16:30:04 -0700189 procPrints := []string{
Elliott Hughes10363162024-01-09 22:02:03 +0000190 "See https://android.googlesource.com/platform/build/+/main/Changes.md#PATH_Tools for more information.",
Dan Willemsen18490112018-05-25 16:30:04 -0700191 }
192 if len(log.Parents) > 0 {
193 procPrints = append(procPrints, "Process tree:")
194 for i, proc := range log.Parents {
195 procPrints = append(procPrints, fmt.Sprintf("%s→ %s", strings.Repeat(" ", i), proc.Command))
196 }
197 }
198
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500199 // Validate usage against disallowed or missing PATH tools.
Dan Willemsen18490112018-05-25 16:30:04 -0700200 config := paths.GetConfig(log.Basename)
201 if config.Error {
202 ctx.Printf("Disallowed PATH tool %q used: %#v", log.Basename, log.Args)
203 for _, line := range procPrints {
204 ctx.Println(line)
205 }
206 } else {
207 ctx.Verbosef("Unknown PATH tool %q used: %#v", log.Basename, log.Args)
208 for _, line := range procPrints {
209 ctx.Verboseln(line)
210 }
211 }
212 }
213 }()
214
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500215 // Create the .path directory.
Dan Willemsen18490112018-05-25 16:30:04 -0700216 ensureEmptyDirectoriesExist(ctx, myPath)
217
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500218 // Compute the full list of binaries available in the original $PATH.
Dan Willemsen18490112018-05-25 16:30:04 -0700219 var execs []string
220 for _, pathEntry := range filepath.SplitList(origPath) {
221 if pathEntry == "" {
222 // Ignore the current directory
223 continue
224 }
225 // TODO(dwillemsen): remove path entries under TOP? or anything
226 // that looks like an android source dir? They won't exist on
227 // the build servers, since they're added by envsetup.sh.
228 // (Except for the JDK, which is configured in ui/build/config.go)
229
230 execs = append(execs, parsePathDir(pathEntry)...)
231 }
232
Dan Willemsen347ba752020-05-01 16:29:00 -0700233 if config.Environment().IsEnvTrue("TEMPORARY_DISABLE_PATH_RESTRICTIONS") {
234 ctx.Fatalln("TEMPORARY_DISABLE_PATH_RESTRICTIONS was a temporary migration method, and is now obsolete.")
235 }
236
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500237 // Create symlinks from the path_interposer binary to all binaries for each
238 // directory in the original $PATH. This ensures that during the build,
239 // every call to a binary that's expected to be in the $PATH will be
240 // intercepted by the path_interposer binary, and validated with the
241 // LogEntry listener above at build time.
Dan Willemsen18490112018-05-25 16:30:04 -0700242 for _, name := range execs {
Dan Willemsen347ba752020-05-01 16:29:00 -0700243 if !paths.GetConfig(name).Symlink {
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500244 // Ignore host tools that shouldn't be symlinked.
Dan Willemsen18490112018-05-25 16:30:04 -0700245 continue
246 }
247
248 err := os.Symlink("../.path_interposer", filepath.Join(myPath, name))
249 // Intentionally ignore existing files -- that means that we
250 // just created it, and the first one should win.
251 if err != nil && !os.IsExist(err) {
252 ctx.Fatalln("Failed to create symlink:", err)
253 }
254 }
255
256 myPath, _ = filepath.Abs(myPath)
Dan Willemsen417be1f2018-10-30 23:18:54 -0700257
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500258 // We put some prebuilts in $PATH, since it's infeasible to add dependencies
259 // for all of them.
Colin Cross92ac46d2025-02-26 19:25:23 -0800260 prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + config.PrebuiltOS())
Dan Willemsen733547d2019-02-14 20:11:26 -0800261 myPath = prebuiltsPath + string(os.PathListSeparator) + myPath
Dan Willemsen417be1f2018-10-30 23:18:54 -0700262
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500263 // Replace the $PATH variable with the path_interposer symlinks, and
264 // checked-in prebuilts.
Dan Willemsen18490112018-05-25 16:30:04 -0700265 config.Environment().Set("PATH", myPath)
Taylor Santiago8b0bed72024-09-03 13:30:22 -0700266 updatePathForSandbox(config)
Dan Willemsen18490112018-05-25 16:30:04 -0700267 config.pathReplaced = true
268}