1// Copyright 2017 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 "bytes" 19 "os" 20 "os/exec" 21 "os/user" 22 "path/filepath" 23 "strings" 24 "sync" 25) 26 27type Sandbox struct { 28 Enabled bool 29 DisableWhenUsingGoma bool 30 31 AllowBuildBrokenUsesNetwork bool 32} 33 34var ( 35 noSandbox = Sandbox{} 36 basicSandbox = Sandbox{ 37 Enabled: true, 38 } 39 40 dumpvarsSandbox = basicSandbox 41 katiSandbox = basicSandbox 42 soongSandbox = basicSandbox 43 ninjaSandbox = Sandbox{ 44 Enabled: true, 45 DisableWhenUsingGoma: true, 46 47 AllowBuildBrokenUsesNetwork: true, 48 } 49) 50 51const ( 52 nsjailPath = "prebuilts/build-tools/linux-x86/bin/nsjail" 53) 54 55var sandboxConfig struct { 56 once sync.Once 57 58 working bool 59 group string 60 srcDir string 61 outDir string 62 distDir string 63} 64 65func (c *Cmd) sandboxSupported() bool { 66 if !c.Sandbox.Enabled { 67 return false 68 } 69 70 // Goma is incompatible with PID namespaces and Mount namespaces. b/122767582 71 if c.Sandbox.DisableWhenUsingGoma && c.config.UseGoma() { 72 return false 73 } 74 75 sandboxConfig.once.Do(func() { 76 sandboxConfig.group = "nogroup" 77 if _, err := user.LookupGroup(sandboxConfig.group); err != nil { 78 sandboxConfig.group = "nobody" 79 } 80 81 // These directories will be bind mounted 82 // so we need full non-symlink paths 83 sandboxConfig.srcDir = absPath(c.ctx, ".") 84 if derefPath, err := filepath.EvalSymlinks(sandboxConfig.srcDir); err == nil { 85 sandboxConfig.srcDir = absPath(c.ctx, derefPath) 86 } 87 sandboxConfig.outDir = absPath(c.ctx, c.config.OutDir()) 88 if derefPath, err := filepath.EvalSymlinks(sandboxConfig.outDir); err == nil { 89 sandboxConfig.outDir = absPath(c.ctx, derefPath) 90 } 91 sandboxConfig.distDir = absPath(c.ctx, c.config.DistDir()) 92 if derefPath, err := filepath.EvalSymlinks(sandboxConfig.distDir); err == nil { 93 sandboxConfig.distDir = absPath(c.ctx, derefPath) 94 } 95 96 sandboxArgs := []string{ 97 "-H", "android-build", 98 "-e", 99 "-u", "nobody", 100 "-g", sandboxConfig.group, 101 "-R", "/", 102 // Mount tmp before srcDir 103 // srcDir is /tmp/.* in integration tests, which is a child dir of /tmp 104 // nsjail throws an error if a child dir is mounted before its parent 105 "-B", "/tmp", 106 c.config.sandboxConfig.SrcDirMountFlag(), sandboxConfig.srcDir, 107 "-B", sandboxConfig.outDir, 108 } 109 110 if _, err := os.Stat(sandboxConfig.distDir); !os.IsNotExist(err) { 111 //Mount dist dir as read-write if it already exists 112 sandboxArgs = append(sandboxArgs, "-B", 113 sandboxConfig.distDir) 114 } 115 116 sandboxArgs = append(sandboxArgs, 117 "--disable_clone_newcgroup", 118 "--", 119 "/bin/bash", "-c", `if [ $(hostname) == "android-build" ]; then echo "Android" "Success"; else echo Failure; fi`) 120 121 cmd := exec.CommandContext(c.ctx.Context, nsjailPath, sandboxArgs...) 122 123 cmd.Env = c.config.Environment().Environ() 124 125 c.ctx.Verboseln(cmd.Args) 126 data, err := cmd.CombinedOutput() 127 if err == nil && bytes.Contains(data, []byte("Android Success")) { 128 sandboxConfig.working = true 129 return 130 } 131 132 c.ctx.Println("Build sandboxing disabled due to nsjail error.") 133 134 for _, line := range strings.Split(strings.TrimSpace(string(data)), "\n") { 135 c.ctx.Verboseln(line) 136 } 137 138 if err == nil { 139 c.ctx.Verboseln("nsjail exited successfully, but without the correct output") 140 } else if e, ok := err.(*exec.ExitError); ok { 141 c.ctx.Verbosef("nsjail failed with %v", e.ProcessState.String()) 142 } else { 143 c.ctx.Verbosef("nsjail failed with %v", err) 144 } 145 }) 146 147 return sandboxConfig.working 148} 149 150// Assumes input path is absolute, clean, and if applicable, an evaluated 151// symlink. If path is not a subdirectory of src dir or relative path 152// cannot be determined, return the input untouched. 153func (c *Cmd) relFromSrcDir(path string) string { 154 if !strings.HasPrefix(path, sandboxConfig.srcDir) { 155 return path 156 } 157 158 rel, err := filepath.Rel(sandboxConfig.srcDir, path) 159 if err != nil { 160 return path 161 } 162 163 return rel 164} 165 166func (c *Cmd) dirArg(path string) string { 167 if !c.config.UseABFS() { 168 return path 169 } 170 171 rel := c.relFromSrcDir(path) 172 173 return path + ":" + filepath.Join(abfsSrcDir, rel) 174} 175 176func (c *Cmd) srcDirArg() string { 177 return c.dirArg(sandboxConfig.srcDir) 178} 179 180func (c *Cmd) outDirArg() string { 181 return c.dirArg(sandboxConfig.outDir) 182} 183 184func (c *Cmd) distDirArg() string { 185 return c.dirArg(sandboxConfig.distDir) 186} 187 188// When configured to use ABFS, we need to allow the creation of the /src 189// directory. Therefore, we cannot mount the root "/" directory as read-only. 190// Instead, we individually mount the children of "/" as RO. 191func (c *Cmd) readMountArgs() []string { 192 if !c.config.UseABFS() { 193 // For now, just map everything. Make most things readonly. 194 return []string{"-R", "/"} 195 } 196 197 entries, err := os.ReadDir("/") 198 if err != nil { 199 // If we can't read "/", just use the default non-ABFS behavior. 200 return []string{"-R", "/"} 201 } 202 203 args := make([]string, 0, 2*len(entries)) 204 for _, ent := range entries { 205 args = append(args, "-R", "/"+ent.Name()) 206 } 207 208 return args 209} 210 211func (c *Cmd) workDir() string { 212 if !c.config.UseABFS() { 213 wd, _ := os.Getwd() 214 return wd 215 } 216 217 return abfsSrcDir 218} 219 220func (c *Cmd) wrapSandbox() { 221 wd := c.workDir() 222 223 var sandboxArgs []string 224 sandboxArgs = append(sandboxArgs, 225 // The executable to run 226 "-x", c.Path, 227 228 // Set the hostname to something consistent 229 "-H", "android-build", 230 231 // Use the current working dir 232 "--cwd", wd, 233 234 // No time limit 235 "-t", "0", 236 237 // Keep all environment variables, we already filter them out 238 // in soong_ui 239 "-e", 240 241 // Mount /proc read-write, necessary to run a nested nsjail or minijail0 242 "--proc_rw", 243 244 // Use a consistent user & group. 245 // Note that these are mapped back to the real UID/GID when 246 // doing filesystem operations, so they're rather arbitrary. 247 "-u", "nobody", 248 "-g", sandboxConfig.group, 249 250 // Set high values, as nsjail uses low defaults. 251 "--rlimit_as", "soft", 252 "--rlimit_core", "soft", 253 "--rlimit_cpu", "soft", 254 "--rlimit_fsize", "soft", 255 "--rlimit_nofile", "soft", 256 ) 257 258 sandboxArgs = append(sandboxArgs, 259 c.readMountArgs()..., 260 ) 261 262 sandboxArgs = append(sandboxArgs, 263 // Mount a writable tmp dir 264 "-B", "/tmp", 265 266 // Mount source 267 c.config.sandboxConfig.SrcDirMountFlag(), c.srcDirArg(), 268 269 //Mount out dir as read-write 270 "-B", c.outDirArg(), 271 272 // Disable newcgroup for now, since it may require newer kernels 273 // TODO: try out cgroups 274 "--disable_clone_newcgroup", 275 276 // Only log important warnings / errors 277 "-q", 278 ) 279 if c.config.UseABFS() { 280 sandboxArgs = append(sandboxArgs, "-B", "{ABFS_DIR}") 281 } 282 283 // Mount srcDir RW allowlists as Read-Write 284 if len(c.config.sandboxConfig.SrcDirRWAllowlist()) > 0 && !c.config.sandboxConfig.SrcDirIsRO() { 285 errMsg := `Product source tree has been set as ReadWrite, RW allowlist not necessary. 286 To recover, either 287 1. Unset BUILD_BROKEN_SRC_DIR_IS_WRITABLE #or 288 2. Unset BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST` 289 c.ctx.Fatalln(errMsg) 290 } 291 for _, srcDirChild := range c.config.sandboxConfig.SrcDirRWAllowlist() { 292 sandboxArgs = append(sandboxArgs, "-B", srcDirChild) 293 } 294 295 if _, err := os.Stat(sandboxConfig.distDir); !os.IsNotExist(err) { 296 //Mount dist dir as read-write if it already exists 297 sandboxArgs = append(sandboxArgs, "-B", c.distDirArg()) 298 } 299 300 if c.Sandbox.AllowBuildBrokenUsesNetwork && c.config.BuildBrokenUsesNetwork() { 301 c.ctx.Printf("AllowBuildBrokenUsesNetwork: %v", c.Sandbox.AllowBuildBrokenUsesNetwork) 302 c.ctx.Printf("BuildBrokenUsesNetwork: %v", c.config.BuildBrokenUsesNetwork()) 303 sandboxArgs = append(sandboxArgs, "-N") 304 } else if dlv, _ := c.config.Environment().Get("SOONG_DELVE"); dlv != "" { 305 // The debugger is enabled and soong_build will pause until a remote delve process connects, allow 306 // network connections. 307 sandboxArgs = append(sandboxArgs, "-N") 308 } 309 310 // Stop nsjail from parsing arguments 311 sandboxArgs = append(sandboxArgs, "--") 312 313 c.Args = append(sandboxArgs, c.Args[1:]...) 314 c.Path = nsjailPath 315 316 env := Environment(c.Env) 317 if _, hasUser := env.Get("USER"); hasUser { 318 env.Set("USER", "nobody") 319 } 320 c.Env = []string(env) 321} 322