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 nsjailPath = "prebuilts/build-tools/linux-x86/bin/nsjail" 52 53var sandboxConfig struct { 54 once sync.Once 55 56 working bool 57 group string 58 srcDir string 59 outDir string 60 distDir string 61} 62 63func (c *Cmd) sandboxSupported() bool { 64 if !c.Sandbox.Enabled { 65 return false 66 } 67 68 // Goma is incompatible with PID namespaces and Mount namespaces. b/122767582 69 if c.Sandbox.DisableWhenUsingGoma && c.config.UseGoma() { 70 return false 71 } 72 73 sandboxConfig.once.Do(func() { 74 sandboxConfig.group = "nogroup" 75 if _, err := user.LookupGroup(sandboxConfig.group); err != nil { 76 sandboxConfig.group = "nobody" 77 } 78 79 // These directories will be bind mounted 80 // so we need full non-symlink paths 81 sandboxConfig.srcDir = absPath(c.ctx, ".") 82 if derefPath, err := filepath.EvalSymlinks(sandboxConfig.srcDir); err == nil { 83 sandboxConfig.srcDir = absPath(c.ctx, derefPath) 84 } 85 sandboxConfig.outDir = absPath(c.ctx, c.config.OutDir()) 86 if derefPath, err := filepath.EvalSymlinks(sandboxConfig.outDir); err == nil { 87 sandboxConfig.outDir = absPath(c.ctx, derefPath) 88 } 89 sandboxConfig.distDir = absPath(c.ctx, c.config.DistDir()) 90 if derefPath, err := filepath.EvalSymlinks(sandboxConfig.distDir); err == nil { 91 sandboxConfig.distDir = absPath(c.ctx, derefPath) 92 } 93 94 sandboxArgs := []string{ 95 "-H", "android-build", 96 "-e", 97 "-u", "nobody", 98 "-g", sandboxConfig.group, 99 "-R", "/", 100 // Mount tmp before srcDir 101 // srcDir is /tmp/.* in integration tests, which is a child dir of /tmp 102 // nsjail throws an error if a child dir is mounted before its parent 103 "-B", "/tmp", 104 c.config.sandboxConfig.SrcDirMountFlag(), sandboxConfig.srcDir, 105 "-B", sandboxConfig.outDir, 106 } 107 108 if _, err := os.Stat(sandboxConfig.distDir); !os.IsNotExist(err) { 109 //Mount dist dir as read-write if it already exists 110 sandboxArgs = append(sandboxArgs, "-B", 111 sandboxConfig.distDir) 112 } 113 114 sandboxArgs = append(sandboxArgs, 115 "--disable_clone_newcgroup", 116 "--", 117 "/bin/bash", "-c", `if [ $(hostname) == "android-build" ]; then echo "Android" "Success"; else echo Failure; fi`) 118 119 cmd := exec.CommandContext(c.ctx.Context, nsjailPath, sandboxArgs...) 120 121 cmd.Env = c.config.Environment().Environ() 122 123 c.ctx.Verboseln(cmd.Args) 124 data, err := cmd.CombinedOutput() 125 if err == nil && bytes.Contains(data, []byte("Android Success")) { 126 sandboxConfig.working = true 127 return 128 } 129 130 c.ctx.Println("Build sandboxing disabled due to nsjail error.") 131 132 for _, line := range strings.Split(strings.TrimSpace(string(data)), "\n") { 133 c.ctx.Verboseln(line) 134 } 135 136 if err == nil { 137 c.ctx.Verboseln("nsjail exited successfully, but without the correct output") 138 } else if e, ok := err.(*exec.ExitError); ok { 139 c.ctx.Verbosef("nsjail failed with %v", e.ProcessState.String()) 140 } else { 141 c.ctx.Verbosef("nsjail failed with %v", err) 142 } 143 }) 144 145 return sandboxConfig.working 146} 147 148func (c *Cmd) wrapSandbox() { 149 wd, _ := os.Getwd() 150 151 sandboxArgs := []string{ 152 // The executable to run 153 "-x", c.Path, 154 155 // Set the hostname to something consistent 156 "-H", "android-build", 157 158 // Use the current working dir 159 "--cwd", wd, 160 161 // No time limit 162 "-t", "0", 163 164 // Keep all environment variables, we already filter them out 165 // in soong_ui 166 "-e", 167 168 // Mount /proc read-write, necessary to run a nested nsjail or minijail0 169 "--proc_rw", 170 171 // Use a consistent user & group. 172 // Note that these are mapped back to the real UID/GID when 173 // doing filesystem operations, so they're rather arbitrary. 174 "-u", "nobody", 175 "-g", sandboxConfig.group, 176 177 // Set high values, as nsjail uses low defaults. 178 "--rlimit_as", "soft", 179 "--rlimit_core", "soft", 180 "--rlimit_cpu", "soft", 181 "--rlimit_fsize", "soft", 182 "--rlimit_nofile", "soft", 183 184 // For now, just map everything. Make most things readonly. 185 "-R", "/", 186 187 // Mount a writable tmp dir 188 "-B", "/tmp", 189 190 // Mount source 191 c.config.sandboxConfig.SrcDirMountFlag(), sandboxConfig.srcDir, 192 193 //Mount out dir as read-write 194 "-B", sandboxConfig.outDir, 195 196 // Disable newcgroup for now, since it may require newer kernels 197 // TODO: try out cgroups 198 "--disable_clone_newcgroup", 199 200 // Only log important warnings / errors 201 "-q", 202 } 203 204 // Mount srcDir RW allowlists as Read-Write 205 if len(c.config.sandboxConfig.SrcDirRWAllowlist()) > 0 && !c.config.sandboxConfig.SrcDirIsRO() { 206 errMsg := `Product source tree has been set as ReadWrite, RW allowlist not necessary. 207 To recover, either 208 1. Unset BUILD_BROKEN_SRC_DIR_IS_WRITABLE #or 209 2. Unset BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST` 210 c.ctx.Fatalln(errMsg) 211 } 212 for _, srcDirChild := range c.config.sandboxConfig.SrcDirRWAllowlist() { 213 sandboxArgs = append(sandboxArgs, "-B", srcDirChild) 214 } 215 216 if _, err := os.Stat(sandboxConfig.distDir); !os.IsNotExist(err) { 217 //Mount dist dir as read-write if it already exists 218 sandboxArgs = append(sandboxArgs, "-B", sandboxConfig.distDir) 219 } 220 221 if c.Sandbox.AllowBuildBrokenUsesNetwork && c.config.BuildBrokenUsesNetwork() { 222 c.ctx.Printf("AllowBuildBrokenUsesNetwork: %v", c.Sandbox.AllowBuildBrokenUsesNetwork) 223 c.ctx.Printf("BuildBrokenUsesNetwork: %v", c.config.BuildBrokenUsesNetwork()) 224 sandboxArgs = append(sandboxArgs, "-N") 225 } else if dlv, _ := c.config.Environment().Get("SOONG_DELVE"); dlv != "" { 226 // The debugger is enabled and soong_build will pause until a remote delve process connects, allow 227 // network connections. 228 sandboxArgs = append(sandboxArgs, "-N") 229 } 230 231 // Stop nsjail from parsing arguments 232 sandboxArgs = append(sandboxArgs, "--") 233 234 c.Args = append(sandboxArgs, c.Args[1:]...) 235 c.Path = nsjailPath 236 237 env := Environment(c.Env) 238 if _, hasUser := env.Get("USER"); hasUser { 239 env.Set("USER", "nobody") 240 } 241 c.Env = []string(env) 242} 243