blob: cc1d7e9c2e80d1097fbf0ac7a88cc8964446c94d [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
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500128 // Set $PATH to be the directories containing the host tool symlinks, and
129 // the prebuilts directory for the current host OS.
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700130 config.Environment().Set("PATH", myPath)
Taylor Santiago8b0bed72024-09-03 13:30:22 -0700131 updatePathForSandbox(config)
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700132 config.pathReplaced = true
133}
134
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500135// SetupPath uses the path_interposer to intercept calls to $PATH binaries, and
136// communicates with the interposer to validate allowed $PATH binaries at
137// runtime, using logs as a medium.
138//
139// This results in hermetic directories in $PATH containing only allowed host
140// tools for the build, and replaces $PATH to contain *only* these directories,
141// and enables an incremental restriction of tools allowed in the $PATH without
142// breaking existing use cases.
Dan Willemsen18490112018-05-25 16:30:04 -0700143func SetupPath(ctx Context, config Config) {
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500144 // Don't replace $PATH twice.
Dan Willemsen18490112018-05-25 16:30:04 -0700145 if config.pathReplaced {
146 return
147 }
148
Nan Zhang17f27672018-12-12 16:01:49 -0800149 ctx.BeginTrace(metrics.RunSetupTool, "path")
Dan Willemsen18490112018-05-25 16:30:04 -0700150 defer ctx.EndTrace()
151
152 origPath, _ := config.Environment().Get("PATH")
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500153 // The directory containing symlinks from binaries in $PATH to the interposer.
Dan Willemsen18490112018-05-25 16:30:04 -0700154 myPath := filepath.Join(config.OutDir(), ".path")
155 interposer := myPath + "_interposer"
156
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500157 // Bootstrap the path_interposer Go binary with microfactory.
Dan Willemsen18490112018-05-25 16:30:04 -0700158 var cfg microfactory.Config
159 cfg.Map("android/soong", "build/soong")
160 cfg.TrimPath, _ = filepath.Abs(".")
161 if _, err := microfactory.Build(&cfg, interposer, "android/soong/cmd/path_interposer"); err != nil {
162 ctx.Fatalln("Failed to build path interposer:", err)
163 }
164
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500165 // Save the original $PATH in a file.
Dan Willemsen18490112018-05-25 16:30:04 -0700166 if err := ioutil.WriteFile(interposer+"_origpath", []byte(origPath), 0777); err != nil {
167 ctx.Fatalln("Failed to write original path:", err)
168 }
169
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500170 // Communication with the path interposer works over log entries. Set up the
171 // listener channel for the log entries here.
Dan Willemsen18490112018-05-25 16:30:04 -0700172 entries, err := paths.LogListener(ctx.Context, interposer+"_log")
173 if err != nil {
174 ctx.Fatalln("Failed to listen for path logs:", err)
175 }
176
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500177 // Loop over all log entry listener channels to validate usage of only
178 // allowed PATH tools at runtime.
Dan Willemsen18490112018-05-25 16:30:04 -0700179 go func() {
180 for log := range entries {
181 curPid := os.Getpid()
182 for i, proc := range log.Parents {
183 if proc.Pid == curPid {
184 log.Parents = log.Parents[i:]
185 break
186 }
187 }
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500188 // Compute the error message along with the process tree, including
189 // parents, for this log line.
Dan Willemsen18490112018-05-25 16:30:04 -0700190 procPrints := []string{
Elliott Hughes10363162024-01-09 22:02:03 +0000191 "See https://android.googlesource.com/platform/build/+/main/Changes.md#PATH_Tools for more information.",
Dan Willemsen18490112018-05-25 16:30:04 -0700192 }
193 if len(log.Parents) > 0 {
194 procPrints = append(procPrints, "Process tree:")
195 for i, proc := range log.Parents {
196 procPrints = append(procPrints, fmt.Sprintf("%s→ %s", strings.Repeat(" ", i), proc.Command))
197 }
198 }
199
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500200 // Validate usage against disallowed or missing PATH tools.
Dan Willemsen18490112018-05-25 16:30:04 -0700201 config := paths.GetConfig(log.Basename)
202 if config.Error {
203 ctx.Printf("Disallowed PATH tool %q used: %#v", log.Basename, log.Args)
204 for _, line := range procPrints {
205 ctx.Println(line)
206 }
207 } else {
208 ctx.Verbosef("Unknown PATH tool %q used: %#v", log.Basename, log.Args)
209 for _, line := range procPrints {
210 ctx.Verboseln(line)
211 }
212 }
213 }
214 }()
215
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500216 // Create the .path directory.
Dan Willemsen18490112018-05-25 16:30:04 -0700217 ensureEmptyDirectoriesExist(ctx, myPath)
218
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500219 // Compute the full list of binaries available in the original $PATH.
Dan Willemsen18490112018-05-25 16:30:04 -0700220 var execs []string
221 for _, pathEntry := range filepath.SplitList(origPath) {
222 if pathEntry == "" {
223 // Ignore the current directory
224 continue
225 }
226 // TODO(dwillemsen): remove path entries under TOP? or anything
227 // that looks like an android source dir? They won't exist on
228 // the build servers, since they're added by envsetup.sh.
229 // (Except for the JDK, which is configured in ui/build/config.go)
230
231 execs = append(execs, parsePathDir(pathEntry)...)
232 }
233
Dan Willemsen347ba752020-05-01 16:29:00 -0700234 if config.Environment().IsEnvTrue("TEMPORARY_DISABLE_PATH_RESTRICTIONS") {
235 ctx.Fatalln("TEMPORARY_DISABLE_PATH_RESTRICTIONS was a temporary migration method, and is now obsolete.")
236 }
237
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500238 // Create symlinks from the path_interposer binary to all binaries for each
239 // directory in the original $PATH. This ensures that during the build,
240 // every call to a binary that's expected to be in the $PATH will be
241 // intercepted by the path_interposer binary, and validated with the
242 // LogEntry listener above at build time.
Dan Willemsen18490112018-05-25 16:30:04 -0700243 for _, name := range execs {
Dan Willemsen347ba752020-05-01 16:29:00 -0700244 if !paths.GetConfig(name).Symlink {
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500245 // Ignore host tools that shouldn't be symlinked.
Dan Willemsen18490112018-05-25 16:30:04 -0700246 continue
247 }
248
249 err := os.Symlink("../.path_interposer", filepath.Join(myPath, name))
250 // Intentionally ignore existing files -- that means that we
251 // just created it, and the first one should win.
252 if err != nil && !os.IsExist(err) {
253 ctx.Fatalln("Failed to create symlink:", err)
254 }
255 }
256
257 myPath, _ = filepath.Abs(myPath)
Dan Willemsen417be1f2018-10-30 23:18:54 -0700258
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500259 // We put some prebuilts in $PATH, since it's infeasible to add dependencies
260 // for all of them.
Dan Willemsen733547d2019-02-14 20:11:26 -0800261 prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86")
262 myPath = prebuiltsPath + string(os.PathListSeparator) + myPath
Dan Willemsen417be1f2018-10-30 23:18:54 -0700263
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500264 // Replace the $PATH variable with the path_interposer symlinks, and
265 // checked-in prebuilts.
Dan Willemsen18490112018-05-25 16:30:04 -0700266 config.Environment().Set("PATH", myPath)
Taylor Santiago8b0bed72024-09-03 13:30:22 -0700267 updatePathForSandbox(config)
Dan Willemsen18490112018-05-25 16:30:04 -0700268 config.pathReplaced = true
269}