blob: 29128d81fc1db115c3070caef83e1c0a0c4da257 [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
Jingwen Chen6a9bfa12020-11-18 07:05:12 -050060// SetupLitePath is the "lite" version of SetupPath used for dumpvars, or other
61// places that does not need the full logging capabilities of path_interposer,
62// wants the minimal performance overhead, and still get the benefits of $PATH
63// hermeticity.
Patrice Arrudaae2694b2020-06-04 19:34:41 +000064func SetupLitePath(ctx Context, config Config, tmpDir string) {
Jingwen Chen6a9bfa12020-11-18 07:05:12 -050065 // Don't replace the path twice.
Dan Willemsen4e2456b2019-10-03 16:45:58 -070066 if config.pathReplaced {
67 return
68 }
69
70 ctx.BeginTrace(metrics.RunSetupTool, "litepath")
71 defer ctx.EndTrace()
72
73 origPath, _ := config.Environment().Get("PATH")
Patrice Arrudaae2694b2020-06-04 19:34:41 +000074
Jingwen Chen6a9bfa12020-11-18 07:05:12 -050075 // If tmpDir is empty, the default TMPDIR is used from config.
Patrice Arrudaae2694b2020-06-04 19:34:41 +000076 if tmpDir == "" {
77 tmpDir, _ = config.Environment().Get("TMPDIR")
78 }
79 myPath := filepath.Join(tmpDir, "path")
Dan Willemsen4e2456b2019-10-03 16:45:58 -070080 ensureEmptyDirectoriesExist(ctx, myPath)
81
82 os.Setenv("PATH", origPath)
Jingwen Chen6a9bfa12020-11-18 07:05:12 -050083 // Iterate over the ACL configuration of host tools for this build.
Dan Willemsen4e2456b2019-10-03 16:45:58 -070084 for name, pathConfig := range paths.Configuration {
85 if !pathConfig.Symlink {
Jingwen Chen6a9bfa12020-11-18 07:05:12 -050086 // Excludes 'Forbidden' and 'LinuxOnlyPrebuilt' PathConfigs.
Dan Willemsen4e2456b2019-10-03 16:45:58 -070087 continue
88 }
89
90 origExec, err := exec.LookPath(name)
91 if err != nil {
92 continue
93 }
94 origExec, err = filepath.Abs(origExec)
95 if err != nil {
96 continue
97 }
98
Jingwen Chen6a9bfa12020-11-18 07:05:12 -050099 // Symlink allowed host tools into a directory for hermeticity.
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700100 err = os.Symlink(origExec, filepath.Join(myPath, name))
101 if err != nil {
102 ctx.Fatalln("Failed to create symlink:", err)
103 }
104 }
105
106 myPath, _ = filepath.Abs(myPath)
107
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500108 // Set up the checked-in prebuilts path directory for the current host OS.
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700109 prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86")
110 myPath = prebuiltsPath + string(os.PathListSeparator) + myPath
111
Cole Faustb1fbc792023-02-27 12:16:11 -0800112 if value, _ := config.Environment().Get("BUILD_BROKEN_PYTHON_IS_PYTHON2"); value == "true" {
113 py2Path, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86/py2")
114 if info, err := os.Stat(py2Path); err == nil && info.IsDir() {
115 myPath = py2Path + string(os.PathListSeparator) + myPath
116 }
117 } else if value != "" {
118 ctx.Fatalf("BUILD_BROKEN_PYTHON_IS_PYTHON2 can only be set to 'true' or an empty string, but got %s\n", value)
119 }
120
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500121 // Set $PATH to be the directories containing the host tool symlinks, and
122 // the prebuilts directory for the current host OS.
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700123 config.Environment().Set("PATH", myPath)
124 config.pathReplaced = true
125}
126
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500127// SetupPath uses the path_interposer to intercept calls to $PATH binaries, and
128// communicates with the interposer to validate allowed $PATH binaries at
129// runtime, using logs as a medium.
130//
131// This results in hermetic directories in $PATH containing only allowed host
132// tools for the build, and replaces $PATH to contain *only* these directories,
133// and enables an incremental restriction of tools allowed in the $PATH without
134// breaking existing use cases.
Dan Willemsen18490112018-05-25 16:30:04 -0700135func SetupPath(ctx Context, config Config) {
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500136 // Don't replace $PATH twice.
Dan Willemsen18490112018-05-25 16:30:04 -0700137 if config.pathReplaced {
138 return
139 }
140
Nan Zhang17f27672018-12-12 16:01:49 -0800141 ctx.BeginTrace(metrics.RunSetupTool, "path")
Dan Willemsen18490112018-05-25 16:30:04 -0700142 defer ctx.EndTrace()
143
144 origPath, _ := config.Environment().Get("PATH")
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500145 // The directory containing symlinks from binaries in $PATH to the interposer.
Dan Willemsen18490112018-05-25 16:30:04 -0700146 myPath := filepath.Join(config.OutDir(), ".path")
147 interposer := myPath + "_interposer"
148
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500149 // Bootstrap the path_interposer Go binary with microfactory.
Dan Willemsen18490112018-05-25 16:30:04 -0700150 var cfg microfactory.Config
151 cfg.Map("android/soong", "build/soong")
152 cfg.TrimPath, _ = filepath.Abs(".")
153 if _, err := microfactory.Build(&cfg, interposer, "android/soong/cmd/path_interposer"); err != nil {
154 ctx.Fatalln("Failed to build path interposer:", err)
155 }
156
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500157 // Save the original $PATH in a file.
Dan Willemsen18490112018-05-25 16:30:04 -0700158 if err := ioutil.WriteFile(interposer+"_origpath", []byte(origPath), 0777); err != nil {
159 ctx.Fatalln("Failed to write original path:", err)
160 }
161
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500162 // Communication with the path interposer works over log entries. Set up the
163 // listener channel for the log entries here.
Dan Willemsen18490112018-05-25 16:30:04 -0700164 entries, err := paths.LogListener(ctx.Context, interposer+"_log")
165 if err != nil {
166 ctx.Fatalln("Failed to listen for path logs:", err)
167 }
168
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500169 // Loop over all log entry listener channels to validate usage of only
170 // allowed PATH tools at runtime.
Dan Willemsen18490112018-05-25 16:30:04 -0700171 go func() {
172 for log := range entries {
173 curPid := os.Getpid()
174 for i, proc := range log.Parents {
175 if proc.Pid == curPid {
176 log.Parents = log.Parents[i:]
177 break
178 }
179 }
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500180 // Compute the error message along with the process tree, including
181 // parents, for this log line.
Dan Willemsen18490112018-05-25 16:30:04 -0700182 procPrints := []string{
183 "See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.",
184 }
185 if len(log.Parents) > 0 {
186 procPrints = append(procPrints, "Process tree:")
187 for i, proc := range log.Parents {
188 procPrints = append(procPrints, fmt.Sprintf("%s→ %s", strings.Repeat(" ", i), proc.Command))
189 }
190 }
191
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500192 // Validate usage against disallowed or missing PATH tools.
Dan Willemsen18490112018-05-25 16:30:04 -0700193 config := paths.GetConfig(log.Basename)
194 if config.Error {
195 ctx.Printf("Disallowed PATH tool %q used: %#v", log.Basename, log.Args)
196 for _, line := range procPrints {
197 ctx.Println(line)
198 }
199 } else {
200 ctx.Verbosef("Unknown PATH tool %q used: %#v", log.Basename, log.Args)
201 for _, line := range procPrints {
202 ctx.Verboseln(line)
203 }
204 }
205 }
206 }()
207
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500208 // Create the .path directory.
Dan Willemsen18490112018-05-25 16:30:04 -0700209 ensureEmptyDirectoriesExist(ctx, myPath)
210
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500211 // Compute the full list of binaries available in the original $PATH.
Dan Willemsen18490112018-05-25 16:30:04 -0700212 var execs []string
213 for _, pathEntry := range filepath.SplitList(origPath) {
214 if pathEntry == "" {
215 // Ignore the current directory
216 continue
217 }
218 // TODO(dwillemsen): remove path entries under TOP? or anything
219 // that looks like an android source dir? They won't exist on
220 // the build servers, since they're added by envsetup.sh.
221 // (Except for the JDK, which is configured in ui/build/config.go)
222
223 execs = append(execs, parsePathDir(pathEntry)...)
224 }
225
Dan Willemsen347ba752020-05-01 16:29:00 -0700226 if config.Environment().IsEnvTrue("TEMPORARY_DISABLE_PATH_RESTRICTIONS") {
227 ctx.Fatalln("TEMPORARY_DISABLE_PATH_RESTRICTIONS was a temporary migration method, and is now obsolete.")
228 }
229
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500230 // Create symlinks from the path_interposer binary to all binaries for each
231 // directory in the original $PATH. This ensures that during the build,
232 // every call to a binary that's expected to be in the $PATH will be
233 // intercepted by the path_interposer binary, and validated with the
234 // LogEntry listener above at build time.
Dan Willemsen18490112018-05-25 16:30:04 -0700235 for _, name := range execs {
Dan Willemsen347ba752020-05-01 16:29:00 -0700236 if !paths.GetConfig(name).Symlink {
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500237 // Ignore host tools that shouldn't be symlinked.
Dan Willemsen18490112018-05-25 16:30:04 -0700238 continue
239 }
240
241 err := os.Symlink("../.path_interposer", filepath.Join(myPath, name))
242 // Intentionally ignore existing files -- that means that we
243 // just created it, and the first one should win.
244 if err != nil && !os.IsExist(err) {
245 ctx.Fatalln("Failed to create symlink:", err)
246 }
247 }
248
249 myPath, _ = filepath.Abs(myPath)
Dan Willemsen417be1f2018-10-30 23:18:54 -0700250
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500251 // We put some prebuilts in $PATH, since it's infeasible to add dependencies
252 // for all of them.
Dan Willemsen733547d2019-02-14 20:11:26 -0800253 prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86")
254 myPath = prebuiltsPath + string(os.PathListSeparator) + myPath
Dan Willemsen417be1f2018-10-30 23:18:54 -0700255
Cole Faustb1fbc792023-02-27 12:16:11 -0800256 if value, _ := config.Environment().Get("BUILD_BROKEN_PYTHON_IS_PYTHON2"); value == "true" {
257 py2Path, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86/py2")
258 if info, err := os.Stat(py2Path); err == nil && info.IsDir() {
259 myPath = py2Path + string(os.PathListSeparator) + myPath
260 }
261 } else if value != "" {
262 ctx.Fatalf("BUILD_BROKEN_PYTHON_IS_PYTHON2 can only be set to 'true' or an empty string, but got %s\n", value)
263 }
264
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500265 // Replace the $PATH variable with the path_interposer symlinks, and
266 // checked-in prebuilts.
Dan Willemsen18490112018-05-25 16:30:04 -0700267 config.Environment().Set("PATH", myPath)
268 config.pathReplaced = true
269}