// Copyright 2017 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package build import ( "bytes" "os" "os/exec" "os/user" "path/filepath" "strings" "sync" ) type Sandbox struct { Enabled bool DisableWhenUsingGoma bool AllowBuildBrokenUsesNetwork bool } var ( noSandbox = Sandbox{} basicSandbox = Sandbox{ Enabled: true, } dumpvarsSandbox = basicSandbox katiSandbox = basicSandbox soongSandbox = basicSandbox ninjaSandbox = Sandbox{ Enabled: true, DisableWhenUsingGoma: true, AllowBuildBrokenUsesNetwork: true, } ) const nsjailPath = "prebuilts/build-tools/linux-x86/bin/nsjail" var sandboxConfig struct { once sync.Once working bool group string srcDir string outDir string distDir string } func (c *Cmd) sandboxSupported() bool { if !c.Sandbox.Enabled { return false } // Goma is incompatible with PID namespaces and Mount namespaces. b/122767582 if c.Sandbox.DisableWhenUsingGoma && c.config.UseGoma() { return false } sandboxConfig.once.Do(func() { sandboxConfig.group = "nogroup" if _, err := user.LookupGroup(sandboxConfig.group); err != nil { sandboxConfig.group = "nobody" } // These directories will be bind mounted // so we need full non-symlink paths sandboxConfig.srcDir = absPath(c.ctx, ".") if derefPath, err := filepath.EvalSymlinks(sandboxConfig.srcDir); err == nil { sandboxConfig.srcDir = absPath(c.ctx, derefPath) } sandboxConfig.outDir = absPath(c.ctx, c.config.OutDir()) if derefPath, err := filepath.EvalSymlinks(sandboxConfig.outDir); err == nil { sandboxConfig.outDir = absPath(c.ctx, derefPath) } sandboxConfig.distDir = absPath(c.ctx, c.config.DistDir()) if derefPath, err := filepath.EvalSymlinks(sandboxConfig.distDir); err == nil { sandboxConfig.distDir = absPath(c.ctx, derefPath) } sandboxArgs := []string{ "-H", "android-build", "-e", "-u", "nobody", "-g", sandboxConfig.group, "-R", "/", // Mount tmp before srcDir // srcDir is /tmp/.* in integration tests, which is a child dir of /tmp // nsjail throws an error if a child dir is mounted before its parent "-B", "/tmp", c.config.sandboxConfig.SrcDirMountFlag(), sandboxConfig.srcDir, "-B", sandboxConfig.outDir, } if _, err := os.Stat(sandboxConfig.distDir); !os.IsNotExist(err) { //Mount dist dir as read-write if it already exists sandboxArgs = append(sandboxArgs, "-B", sandboxConfig.distDir) } sandboxArgs = append(sandboxArgs, "--disable_clone_newcgroup", "--", "/bin/bash", "-c", `if [ $(hostname) == "android-build" ]; then echo "Android" "Success"; else echo Failure; fi`) cmd := exec.CommandContext(c.ctx.Context, nsjailPath, sandboxArgs...) cmd.Env = c.config.Environment().Environ() c.ctx.Verboseln(cmd.Args) data, err := cmd.CombinedOutput() if err == nil && bytes.Contains(data, []byte("Android Success")) { sandboxConfig.working = true return } c.ctx.Println("Build sandboxing disabled due to nsjail error.") for _, line := range strings.Split(strings.TrimSpace(string(data)), "\n") { c.ctx.Verboseln(line) } if err == nil { c.ctx.Verboseln("nsjail exited successfully, but without the correct output") } else if e, ok := err.(*exec.ExitError); ok { c.ctx.Verbosef("nsjail failed with %v", e.ProcessState.String()) } else { c.ctx.Verbosef("nsjail failed with %v", err) } }) return sandboxConfig.working } func (c *Cmd) wrapSandbox() { wd, _ := os.Getwd() sandboxArgs := []string{ // The executable to run "-x", c.Path, // Set the hostname to something consistent "-H", "android-build", // Use the current working dir "--cwd", wd, // No time limit "-t", "0", // Keep all environment variables, we already filter them out // in soong_ui "-e", // Mount /proc read-write, necessary to run a nested nsjail or minijail0 "--proc_rw", // Use a consistent user & group. // Note that these are mapped back to the real UID/GID when // doing filesystem operations, so they're rather arbitrary. "-u", "nobody", "-g", sandboxConfig.group, // Set high values, as nsjail uses low defaults. "--rlimit_as", "soft", "--rlimit_core", "soft", "--rlimit_cpu", "soft", "--rlimit_fsize", "soft", "--rlimit_nofile", "soft", // For now, just map everything. Make most things readonly. "-R", "/", // Mount a writable tmp dir "-B", "/tmp", // Mount source c.config.sandboxConfig.SrcDirMountFlag(), sandboxConfig.srcDir, //Mount out dir as read-write "-B", sandboxConfig.outDir, // Disable newcgroup for now, since it may require newer kernels // TODO: try out cgroups "--disable_clone_newcgroup", // Only log important warnings / errors "-q", } // Mount srcDir RW allowlists as Read-Write if len(c.config.sandboxConfig.SrcDirRWAllowlist()) > 0 && !c.config.sandboxConfig.SrcDirIsRO() { errMsg := `Product source tree has been set as ReadWrite, RW allowlist not necessary. To recover, either 1. Unset BUILD_BROKEN_SRC_DIR_IS_WRITABLE #or 2. Unset BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST` c.ctx.Fatalln(errMsg) } for _, srcDirChild := range c.config.sandboxConfig.SrcDirRWAllowlist() { sandboxArgs = append(sandboxArgs, "-B", srcDirChild) } if _, err := os.Stat(sandboxConfig.distDir); !os.IsNotExist(err) { //Mount dist dir as read-write if it already exists sandboxArgs = append(sandboxArgs, "-B", sandboxConfig.distDir) } if c.Sandbox.AllowBuildBrokenUsesNetwork && c.config.BuildBrokenUsesNetwork() { c.ctx.Printf("AllowBuildBrokenUsesNetwork: %v", c.Sandbox.AllowBuildBrokenUsesNetwork) c.ctx.Printf("BuildBrokenUsesNetwork: %v", c.config.BuildBrokenUsesNetwork()) sandboxArgs = append(sandboxArgs, "-N") } else if dlv, _ := c.config.Environment().Get("SOONG_DELVE"); dlv != "" { // The debugger is enabled and soong_build will pause until a remote delve process connects, allow // network connections. sandboxArgs = append(sandboxArgs, "-N") } // Stop nsjail from parsing arguments sandboxArgs = append(sandboxArgs, "--") c.Args = append(sandboxArgs, c.Args[1:]...) c.Path = nsjailPath env := Environment(c.Env) if _, hasUser := env.Get("USER"); hasUser { env.Set("USER", "nobody") } c.Env = []string(env) }