|  | // 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) | 
|  | } |