| // 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") |
| } |
| |
| if ccacheExec := os.Getenv("CCACHE_EXEC"); ccacheExec != "" { |
| bytes, err := exec.Command(ccacheExec, "-k", "cache_dir").Output() |
| if err == nil { |
| sandboxArgs = append(sandboxArgs, "-B", strings.TrimSpace(string(bytes))) |
| } |
| } |
| |
| // 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) |
| } |