blob: ce0d174b9dca26a09dbbe435aaf5030c8ae7816b [file] [log] [blame]
// 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)
}