Implement linux sandboxing with nsjail

This really only initializes the sandbox, it does not attempt to change
the view of the filesystem, nor does it turn off networking.

Bug: 122270019
Test: m
Test: trigger nsjail check failure; lunch; m; cat out/soong.log
Test: USE_GOMA=true m libc
Change-Id: Ib291072dcee8247c7a15f5b6831295ead6e4fc22
This commit is contained in:
Dan Willemsen 2019-01-02 12:24:44 -08:00
parent 073941d780
commit 63663c6bc9
3 changed files with 151 additions and 16 deletions

View file

@ -59,6 +59,7 @@ func runNinja(ctx Context, config Config) {
"-w", "missingdepfile=err")
cmd := Command(ctx, config, "ninja", executable, args...)
cmd.Sandbox = ninjaSandbox
if config.HasKatiSuffix() {
cmd.Environment.AppendFromKati(config.KatiEnvFile())
}

View file

@ -21,12 +21,12 @@ import (
type Sandbox string
const (
noSandbox = ""
globalSandbox = "build/soong/ui/build/sandbox/darwin/global.sb"
dumpvarsSandbox = globalSandbox
soongSandbox = globalSandbox
katiSandbox = globalSandbox
katiCleanSpecSandbox = globalSandbox
noSandbox = ""
globalSandbox = "build/soong/ui/build/sandbox/darwin/global.sb"
dumpvarsSandbox = globalSandbox
soongSandbox = globalSandbox
katiSandbox = globalSandbox
ninjaSandbox = noSandbox
)
var sandboxExecPath string

View file

@ -14,20 +14,154 @@
package build
type Sandbox bool
const (
noSandbox = false
globalSandbox = false
dumpvarsSandbox = false
soongSandbox = false
katiSandbox = false
katiCleanSpecSandbox = false
import (
"bytes"
"os"
"os/exec"
"os/user"
"strings"
"sync"
)
type Sandbox struct {
Enabled bool
DisableWhenUsingGoma bool
}
var (
noSandbox = Sandbox{}
basicSandbox = Sandbox{
Enabled: true,
}
dumpvarsSandbox = basicSandbox
katiSandbox = basicSandbox
soongSandbox = basicSandbox
ninjaSandbox = Sandbox{
Enabled: true,
DisableWhenUsingGoma: true,
}
)
const nsjailPath = "prebuilts/build-tools/linux-x86/bin/nsjail"
var sandboxConfig struct {
once sync.Once
working bool
group string
}
func (c *Cmd) sandboxSupported() bool {
return false
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"
}
cmd := exec.CommandContext(c.ctx.Context, nsjailPath,
"-H", "android-build",
"-e",
"-u", "nobody",
"-g", sandboxConfig.group,
"-B", "/",
"--disable_clone_newcgroup",
"--",
"/bin/bash", "-c", `if [ $(hostname) == "android-build" ]; then echo "Android" "Success"; else echo Failure; fi`)
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. This may become fatal in the future.")
c.ctx.Println("Please let us know why nsjail doesn't work in your environment at:")
c.ctx.Println(" https://groups.google.com/forum/#!forum/android-building")
c.ctx.Println(" https://issuetracker.google.com/issues/new?component=381517")
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",
// 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. Eventually we should limit this, especially to make most things readonly.
"-B", "/",
// Enable networking for now. TODO: remove
"-N",
// Disable newcgroup for now, since it may require newer kernels
// TODO: try out cgroups
"--disable_clone_newcgroup",
// Only log important warnings / errors
"-q",
// Stop parsing arguments
"--",
}
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)
}