• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package build
16
17import (
18	"bytes"
19	"os"
20	"os/exec"
21	"os/user"
22	"path/filepath"
23	"strings"
24	"sync"
25)
26
27type Sandbox struct {
28	Enabled              bool
29	DisableWhenUsingGoma bool
30
31	AllowBuildBrokenUsesNetwork bool
32}
33
34var (
35	noSandbox    = Sandbox{}
36	basicSandbox = Sandbox{
37		Enabled: true,
38	}
39
40	dumpvarsSandbox = basicSandbox
41	katiSandbox     = basicSandbox
42	soongSandbox    = basicSandbox
43	ninjaSandbox    = Sandbox{
44		Enabled:              true,
45		DisableWhenUsingGoma: true,
46
47		AllowBuildBrokenUsesNetwork: true,
48	}
49)
50
51const nsjailPath = "prebuilts/build-tools/linux-x86/bin/nsjail"
52
53var sandboxConfig struct {
54	once sync.Once
55
56	working bool
57	group   string
58	srcDir  string
59	outDir  string
60	distDir string
61}
62
63func (c *Cmd) sandboxSupported() bool {
64	if !c.Sandbox.Enabled {
65		return false
66	}
67
68	// Goma is incompatible with PID namespaces and Mount namespaces. b/122767582
69	if c.Sandbox.DisableWhenUsingGoma && c.config.UseGoma() {
70		return false
71	}
72
73	sandboxConfig.once.Do(func() {
74		sandboxConfig.group = "nogroup"
75		if _, err := user.LookupGroup(sandboxConfig.group); err != nil {
76			sandboxConfig.group = "nobody"
77		}
78
79		// These directories will be bind mounted
80		// so we need full non-symlink paths
81		sandboxConfig.srcDir = absPath(c.ctx, ".")
82		if derefPath, err := filepath.EvalSymlinks(sandboxConfig.srcDir); err == nil {
83			sandboxConfig.srcDir = absPath(c.ctx, derefPath)
84		}
85		sandboxConfig.outDir = absPath(c.ctx, c.config.OutDir())
86		if derefPath, err := filepath.EvalSymlinks(sandboxConfig.outDir); err == nil {
87			sandboxConfig.outDir = absPath(c.ctx, derefPath)
88		}
89		sandboxConfig.distDir = absPath(c.ctx, c.config.DistDir())
90		if derefPath, err := filepath.EvalSymlinks(sandboxConfig.distDir); err == nil {
91			sandboxConfig.distDir = absPath(c.ctx, derefPath)
92		}
93
94		sandboxArgs := []string{
95			"-H", "android-build",
96			"-e",
97			"-u", "nobody",
98			"-g", sandboxConfig.group,
99			"-R", "/",
100			// Mount tmp before srcDir
101			// srcDir is /tmp/.* in integration tests, which is a child dir of /tmp
102			// nsjail throws an error if a child dir is mounted before its parent
103			"-B", "/tmp",
104			c.config.sandboxConfig.SrcDirMountFlag(), sandboxConfig.srcDir,
105			"-B", sandboxConfig.outDir,
106		}
107
108		if _, err := os.Stat(sandboxConfig.distDir); !os.IsNotExist(err) {
109			//Mount dist dir as read-write if it already exists
110			sandboxArgs = append(sandboxArgs, "-B",
111				sandboxConfig.distDir)
112		}
113
114		sandboxArgs = append(sandboxArgs,
115			"--disable_clone_newcgroup",
116			"--",
117			"/bin/bash", "-c", `if [ $(hostname) == "android-build" ]; then echo "Android" "Success"; else echo Failure; fi`)
118
119		cmd := exec.CommandContext(c.ctx.Context, nsjailPath, sandboxArgs...)
120
121		cmd.Env = c.config.Environment().Environ()
122
123		c.ctx.Verboseln(cmd.Args)
124		data, err := cmd.CombinedOutput()
125		if err == nil && bytes.Contains(data, []byte("Android Success")) {
126			sandboxConfig.working = true
127			return
128		}
129
130		c.ctx.Println("Build sandboxing disabled due to nsjail error.")
131
132		for _, line := range strings.Split(strings.TrimSpace(string(data)), "\n") {
133			c.ctx.Verboseln(line)
134		}
135
136		if err == nil {
137			c.ctx.Verboseln("nsjail exited successfully, but without the correct output")
138		} else if e, ok := err.(*exec.ExitError); ok {
139			c.ctx.Verbosef("nsjail failed with %v", e.ProcessState.String())
140		} else {
141			c.ctx.Verbosef("nsjail failed with %v", err)
142		}
143	})
144
145	return sandboxConfig.working
146}
147
148func (c *Cmd) wrapSandbox() {
149	wd, _ := os.Getwd()
150
151	sandboxArgs := []string{
152		// The executable to run
153		"-x", c.Path,
154
155		// Set the hostname to something consistent
156		"-H", "android-build",
157
158		// Use the current working dir
159		"--cwd", wd,
160
161		// No time limit
162		"-t", "0",
163
164		// Keep all environment variables, we already filter them out
165		// in soong_ui
166		"-e",
167
168		// Mount /proc read-write, necessary to run a nested nsjail or minijail0
169		"--proc_rw",
170
171		// Use a consistent user & group.
172		// Note that these are mapped back to the real UID/GID when
173		// doing filesystem operations, so they're rather arbitrary.
174		"-u", "nobody",
175		"-g", sandboxConfig.group,
176
177		// Set high values, as nsjail uses low defaults.
178		"--rlimit_as", "soft",
179		"--rlimit_core", "soft",
180		"--rlimit_cpu", "soft",
181		"--rlimit_fsize", "soft",
182		"--rlimit_nofile", "soft",
183
184		// For now, just map everything. Make most things readonly.
185		"-R", "/",
186
187		// Mount a writable tmp dir
188		"-B", "/tmp",
189
190		// Mount source
191		c.config.sandboxConfig.SrcDirMountFlag(), sandboxConfig.srcDir,
192
193		//Mount out dir as read-write
194		"-B", sandboxConfig.outDir,
195
196		// Disable newcgroup for now, since it may require newer kernels
197		// TODO: try out cgroups
198		"--disable_clone_newcgroup",
199
200		// Only log important warnings / errors
201		"-q",
202	}
203
204	// Mount srcDir RW allowlists as Read-Write
205	if len(c.config.sandboxConfig.SrcDirRWAllowlist()) > 0 && !c.config.sandboxConfig.SrcDirIsRO() {
206		errMsg := `Product source tree has been set as ReadWrite, RW allowlist not necessary.
207			To recover, either
208			1. Unset BUILD_BROKEN_SRC_DIR_IS_WRITABLE #or
209			2. Unset BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST`
210		c.ctx.Fatalln(errMsg)
211	}
212	for _, srcDirChild := range c.config.sandboxConfig.SrcDirRWAllowlist() {
213		sandboxArgs = append(sandboxArgs, "-B", srcDirChild)
214	}
215
216	if _, err := os.Stat(sandboxConfig.distDir); !os.IsNotExist(err) {
217		//Mount dist dir as read-write if it already exists
218		sandboxArgs = append(sandboxArgs, "-B", sandboxConfig.distDir)
219	}
220
221	if c.Sandbox.AllowBuildBrokenUsesNetwork && c.config.BuildBrokenUsesNetwork() {
222		c.ctx.Printf("AllowBuildBrokenUsesNetwork: %v", c.Sandbox.AllowBuildBrokenUsesNetwork)
223		c.ctx.Printf("BuildBrokenUsesNetwork: %v", c.config.BuildBrokenUsesNetwork())
224		sandboxArgs = append(sandboxArgs, "-N")
225	} else if dlv, _ := c.config.Environment().Get("SOONG_DELVE"); dlv != "" {
226		// The debugger is enabled and soong_build will pause until a remote delve process connects, allow
227		// network connections.
228		sandboxArgs = append(sandboxArgs, "-N")
229	}
230
231	// Stop nsjail from parsing arguments
232	sandboxArgs = append(sandboxArgs, "--")
233
234	c.Args = append(sandboxArgs, c.Args[1:]...)
235	c.Path = nsjailPath
236
237	env := Environment(c.Env)
238	if _, hasUser := env.Get("USER"); hasUser {
239		env.Set("USER", "nobody")
240	}
241	c.Env = []string(env)
242}
243