Explorar el Código

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
Dan Willemsen hace 5 años
padre
commit
63663c6bc9
Se han modificado 3 ficheros con 151 adiciones y 16 borrados
  1. 1 0
      ui/build/ninja.go
  2. 6 6
      ui/build/sandbox_darwin.go
  3. 144 10
      ui/build/sandbox_linux.go

+ 1 - 0
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())
 	}

+ 6 - 6
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

+ 144 - 10
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)
 }