blob: 86e61c0dac1d99de9eb951a3aa192348a609298d [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
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500112 // Set $PATH to be the directories containing the host tool symlinks, and
113 // the prebuilts directory for the current host OS.
Dan Willemsen4e2456b2019-10-03 16:45:58 -0700114 config.Environment().Set("PATH", myPath)
115 config.pathReplaced = true
116}
117
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500118// SetupPath uses the path_interposer to intercept calls to $PATH binaries, and
119// communicates with the interposer to validate allowed $PATH binaries at
120// runtime, using logs as a medium.
121//
122// This results in hermetic directories in $PATH containing only allowed host
123// tools for the build, and replaces $PATH to contain *only* these directories,
124// and enables an incremental restriction of tools allowed in the $PATH without
125// breaking existing use cases.
Dan Willemsen18490112018-05-25 16:30:04 -0700126func SetupPath(ctx Context, config Config) {
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500127 // Don't replace $PATH twice.
Dan Willemsen18490112018-05-25 16:30:04 -0700128 if config.pathReplaced {
129 return
130 }
131
Nan Zhang17f27672018-12-12 16:01:49 -0800132 ctx.BeginTrace(metrics.RunSetupTool, "path")
Dan Willemsen18490112018-05-25 16:30:04 -0700133 defer ctx.EndTrace()
134
135 origPath, _ := config.Environment().Get("PATH")
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500136 // The directory containing symlinks from binaries in $PATH to the interposer.
Dan Willemsen18490112018-05-25 16:30:04 -0700137 myPath := filepath.Join(config.OutDir(), ".path")
138 interposer := myPath + "_interposer"
139
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500140 // Bootstrap the path_interposer Go binary with microfactory.
Dan Willemsen18490112018-05-25 16:30:04 -0700141 var cfg microfactory.Config
142 cfg.Map("android/soong", "build/soong")
143 cfg.TrimPath, _ = filepath.Abs(".")
144 if _, err := microfactory.Build(&cfg, interposer, "android/soong/cmd/path_interposer"); err != nil {
145 ctx.Fatalln("Failed to build path interposer:", err)
146 }
147
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500148 // Save the original $PATH in a file.
Dan Willemsen18490112018-05-25 16:30:04 -0700149 if err := ioutil.WriteFile(interposer+"_origpath", []byte(origPath), 0777); err != nil {
150 ctx.Fatalln("Failed to write original path:", err)
151 }
152
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500153 // Communication with the path interposer works over log entries. Set up the
154 // listener channel for the log entries here.
Dan Willemsen18490112018-05-25 16:30:04 -0700155 entries, err := paths.LogListener(ctx.Context, interposer+"_log")
156 if err != nil {
157 ctx.Fatalln("Failed to listen for path logs:", err)
158 }
159
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500160 // Loop over all log entry listener channels to validate usage of only
161 // allowed PATH tools at runtime.
Dan Willemsen18490112018-05-25 16:30:04 -0700162 go func() {
163 for log := range entries {
164 curPid := os.Getpid()
165 for i, proc := range log.Parents {
166 if proc.Pid == curPid {
167 log.Parents = log.Parents[i:]
168 break
169 }
170 }
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500171 // Compute the error message along with the process tree, including
172 // parents, for this log line.
Dan Willemsen18490112018-05-25 16:30:04 -0700173 procPrints := []string{
174 "See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.",
175 }
176 if len(log.Parents) > 0 {
177 procPrints = append(procPrints, "Process tree:")
178 for i, proc := range log.Parents {
179 procPrints = append(procPrints, fmt.Sprintf("%s→ %s", strings.Repeat(" ", i), proc.Command))
180 }
181 }
182
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500183 // Validate usage against disallowed or missing PATH tools.
Dan Willemsen18490112018-05-25 16:30:04 -0700184 config := paths.GetConfig(log.Basename)
185 if config.Error {
186 ctx.Printf("Disallowed PATH tool %q used: %#v", log.Basename, log.Args)
187 for _, line := range procPrints {
188 ctx.Println(line)
189 }
190 } else {
191 ctx.Verbosef("Unknown PATH tool %q used: %#v", log.Basename, log.Args)
192 for _, line := range procPrints {
193 ctx.Verboseln(line)
194 }
195 }
196 }
197 }()
198
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500199 // Create the .path directory.
Dan Willemsen18490112018-05-25 16:30:04 -0700200 ensureEmptyDirectoriesExist(ctx, myPath)
201
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500202 // Compute the full list of binaries available in the original $PATH.
Dan Willemsen18490112018-05-25 16:30:04 -0700203 var execs []string
204 for _, pathEntry := range filepath.SplitList(origPath) {
205 if pathEntry == "" {
206 // Ignore the current directory
207 continue
208 }
209 // TODO(dwillemsen): remove path entries under TOP? or anything
210 // that looks like an android source dir? They won't exist on
211 // the build servers, since they're added by envsetup.sh.
212 // (Except for the JDK, which is configured in ui/build/config.go)
213
214 execs = append(execs, parsePathDir(pathEntry)...)
215 }
216
Dan Willemsen347ba752020-05-01 16:29:00 -0700217 if config.Environment().IsEnvTrue("TEMPORARY_DISABLE_PATH_RESTRICTIONS") {
218 ctx.Fatalln("TEMPORARY_DISABLE_PATH_RESTRICTIONS was a temporary migration method, and is now obsolete.")
219 }
220
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500221 // Create symlinks from the path_interposer binary to all binaries for each
222 // directory in the original $PATH. This ensures that during the build,
223 // every call to a binary that's expected to be in the $PATH will be
224 // intercepted by the path_interposer binary, and validated with the
225 // LogEntry listener above at build time.
Dan Willemsen18490112018-05-25 16:30:04 -0700226 for _, name := range execs {
Dan Willemsen347ba752020-05-01 16:29:00 -0700227 if !paths.GetConfig(name).Symlink {
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500228 // Ignore host tools that shouldn't be symlinked.
Dan Willemsen18490112018-05-25 16:30:04 -0700229 continue
230 }
231
232 err := os.Symlink("../.path_interposer", filepath.Join(myPath, name))
233 // Intentionally ignore existing files -- that means that we
234 // just created it, and the first one should win.
235 if err != nil && !os.IsExist(err) {
236 ctx.Fatalln("Failed to create symlink:", err)
237 }
238 }
239
240 myPath, _ = filepath.Abs(myPath)
Dan Willemsen417be1f2018-10-30 23:18:54 -0700241
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500242 // We put some prebuilts in $PATH, since it's infeasible to add dependencies
243 // for all of them.
Dan Willemsen733547d2019-02-14 20:11:26 -0800244 prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86")
245 myPath = prebuiltsPath + string(os.PathListSeparator) + myPath
Dan Willemsen417be1f2018-10-30 23:18:54 -0700246
Jingwen Chen6a9bfa12020-11-18 07:05:12 -0500247 // Replace the $PATH variable with the path_interposer symlinks, and
248 // checked-in prebuilts.
Dan Willemsen18490112018-05-25 16:30:04 -0700249 config.Environment().Set("PATH", myPath)
250 config.pathReplaced = true
251}