From 63663c6bc9ee270e02b06ff3d4f8812f1ef4d49b Mon Sep 17 00:00:00 2001 From: Dan Willemsen Date: Wed, 2 Jan 2019 12:24:44 -0800 Subject: [PATCH] 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 --- ui/build/ninja.go | 1 + ui/build/sandbox_darwin.go | 12 +-- ui/build/sandbox_linux.go | 154 ++++++++++++++++++++++++++++++++++--- 3 files changed, 151 insertions(+), 16 deletions(-) diff --git a/ui/build/ninja.go b/ui/build/ninja.go index 835f82030..cb41579c5 100644 --- a/ui/build/ninja.go +++ b/ui/build/ninja.go @@ -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()) } diff --git a/ui/build/sandbox_darwin.go b/ui/build/sandbox_darwin.go index 7e75167af..43c5480c7 100644 --- a/ui/build/sandbox_darwin.go +++ b/ui/build/sandbox_darwin.go @@ -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 diff --git a/ui/build/sandbox_linux.go b/ui/build/sandbox_linux.go index f2bfac293..b87637f7d 100644 --- a/ui/build/sandbox_linux.go +++ b/ui/build/sandbox_linux.go @@ -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) }