1// 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" 21 "os/exec" 22 "path/filepath" 23 "runtime" 24 "strings" 25 26 "github.com/google/blueprint/microfactory" 27 28 "android/soong/ui/build/paths" 29 "android/soong/ui/metrics" 30) 31 32// 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. 35func 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 60// 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. 64func SetupLitePath(ctx Context, config Config, tmpDir string) { 65 // Don't replace the path twice. 66 if config.pathReplaced { 67 return 68 } 69 70 ctx.BeginTrace(metrics.RunSetupTool, "litepath") 71 defer ctx.EndTrace() 72 73 origPath, _ := config.Environment().Get("PATH") 74 75 // If tmpDir is empty, the default TMPDIR is used from config. 76 if tmpDir == "" { 77 tmpDir, _ = config.Environment().Get("TMPDIR") 78 } 79 myPath := filepath.Join(tmpDir, "path") 80 ensureEmptyDirectoriesExist(ctx, myPath) 81 82 os.Setenv("PATH", origPath) 83 // Iterate over the ACL configuration of host tools for this build. 84 for name, pathConfig := range paths.Configuration { 85 if !pathConfig.Symlink { 86 // Excludes 'Forbidden' and 'LinuxOnlyPrebuilt' PathConfigs. 87 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 99 // Symlink allowed host tools into a directory for hermeticity. 100 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 108 // Set up the checked-in prebuilts path directory for the current host OS. 109 prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86") 110 myPath = prebuiltsPath + string(os.PathListSeparator) + myPath 111 112 // Set $PATH to be the directories containing the host tool symlinks, and 113 // the prebuilts directory for the current host OS. 114 config.Environment().Set("PATH", myPath) 115 config.pathReplaced = true 116} 117 118// 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. 126func SetupPath(ctx Context, config Config) { 127 // Don't replace $PATH twice. 128 if config.pathReplaced { 129 return 130 } 131 132 ctx.BeginTrace(metrics.RunSetupTool, "path") 133 defer ctx.EndTrace() 134 135 origPath, _ := config.Environment().Get("PATH") 136 // The directory containing symlinks from binaries in $PATH to the interposer. 137 myPath := filepath.Join(config.OutDir(), ".path") 138 interposer := myPath + "_interposer" 139 140 // Bootstrap the path_interposer Go binary with microfactory. 141 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 148 // Save the original $PATH in a file. 149 if err := ioutil.WriteFile(interposer+"_origpath", []byte(origPath), 0777); err != nil { 150 ctx.Fatalln("Failed to write original path:", err) 151 } 152 153 // Communication with the path interposer works over log entries. Set up the 154 // listener channel for the log entries here. 155 entries, err := paths.LogListener(ctx.Context, interposer+"_log") 156 if err != nil { 157 ctx.Fatalln("Failed to listen for path logs:", err) 158 } 159 160 // Loop over all log entry listener channels to validate usage of only 161 // allowed PATH tools at runtime. 162 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 } 171 // Compute the error message along with the process tree, including 172 // parents, for this log line. 173 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 183 // Validate usage against disallowed or missing PATH tools. 184 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 199 // Create the .path directory. 200 ensureEmptyDirectoriesExist(ctx, myPath) 201 202 // Compute the full list of binaries available in the original $PATH. 203 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 217 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 221 // 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. 226 for _, name := range execs { 227 if !paths.GetConfig(name).Symlink { 228 // Ignore host tools that shouldn't be symlinked. 229 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) 241 242 // We put some prebuilts in $PATH, since it's infeasible to add dependencies 243 // for all of them. 244 prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86") 245 myPath = prebuiltsPath + string(os.PathListSeparator) + myPath 246 247 // Replace the $PATH variable with the path_interposer symlinks, and 248 // checked-in prebuilts. 249 config.Environment().Set("PATH", myPath) 250 config.pathReplaced = true 251} 252