ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. 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 | |
| 15 | package com.google.devtools.build.lib.sandbox; |
| 16 | |
ruperts | 5274d8b | 2017-12-20 10:45:58 -0800 | [diff] [blame] | 17 | import com.google.common.collect.ImmutableList; |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 18 | import com.google.common.collect.ImmutableMap; |
| 19 | import com.google.common.collect.ImmutableSet; |
| 20 | import com.google.common.collect.Maps; |
| 21 | import com.google.common.io.ByteStreams; |
frazze-jobb | 11f7d80 | 2021-09-06 08:45:27 -0700 | [diff] [blame] | 22 | import com.google.devtools.build.lib.actions.ActionInput; |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 23 | import com.google.devtools.build.lib.actions.ExecException; |
ulfjack | 4d7f8f7 | 2017-11-29 03:37:04 -0800 | [diff] [blame] | 24 | import com.google.devtools.build.lib.actions.ExecutionRequirements; |
frazze-jobb | 11f7d80 | 2021-09-06 08:45:27 -0700 | [diff] [blame] | 25 | import com.google.devtools.build.lib.actions.FileArtifactValue; |
| 26 | import com.google.devtools.build.lib.actions.FileContentsProxy; |
janakr | 300c11e | 2021-05-18 12:10:43 -0700 | [diff] [blame] | 27 | import com.google.devtools.build.lib.actions.ForbiddenActionInputException; |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 28 | import com.google.devtools.build.lib.actions.Spawn; |
ulfjack | 4d7f8f7 | 2017-11-29 03:37:04 -0800 | [diff] [blame] | 29 | import com.google.devtools.build.lib.actions.Spawns; |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 30 | import com.google.devtools.build.lib.actions.UserExecException; |
frazze-jobb | 11f7d80 | 2021-09-06 08:45:27 -0700 | [diff] [blame] | 31 | import com.google.devtools.build.lib.actions.cache.VirtualActionInput; |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 32 | import com.google.devtools.build.lib.analysis.BlazeDirectories; |
jmmv | 16af94c | 2019-04-16 07:44:34 -0700 | [diff] [blame] | 33 | import com.google.devtools.build.lib.exec.TreeDeleter; |
László Csomor | cfccdf1 | 2017-10-16 17:11:55 +0200 | [diff] [blame] | 34 | import com.google.devtools.build.lib.exec.local.LocalEnvProvider; |
Laszlo Csomor | a495bae | 2018-01-04 08:13:57 -0800 | [diff] [blame] | 35 | import com.google.devtools.build.lib.exec.local.PosixLocalEnvProvider; |
ulfjack | ef5b63d | 2018-06-15 07:19:11 -0700 | [diff] [blame] | 36 | import com.google.devtools.build.lib.profiler.Profiler; |
| 37 | import com.google.devtools.build.lib.profiler.SilentCloseable; |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 38 | import com.google.devtools.build.lib.runtime.CommandEnvironment; |
jmmv | 0367152 | 2020-03-12 08:21:38 -0700 | [diff] [blame] | 39 | import com.google.devtools.build.lib.sandbox.SandboxHelpers.SandboxInputs; |
cushon | 9b8b790 | 2018-10-04 09:38:43 -0700 | [diff] [blame] | 40 | import com.google.devtools.build.lib.sandbox.SandboxHelpers.SandboxOutputs; |
mschaller | 0793388 | 2020-06-24 14:38:23 -0700 | [diff] [blame] | 41 | import com.google.devtools.build.lib.server.FailureDetails.Sandbox.Code; |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 42 | import com.google.devtools.build.lib.shell.Command; |
| 43 | import com.google.devtools.build.lib.shell.CommandException; |
| 44 | import com.google.devtools.build.lib.util.OS; |
frazze-jobb | 11f7d80 | 2021-09-06 08:45:27 -0700 | [diff] [blame] | 45 | import com.google.devtools.build.lib.vfs.FileStatus; |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 46 | import com.google.devtools.build.lib.vfs.FileSystem; |
| 47 | import com.google.devtools.build.lib.vfs.Path; |
lberki | 7149f57 | 2021-02-08 04:15:53 -0800 | [diff] [blame] | 48 | import com.google.devtools.build.lib.vfs.PathFragment; |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 49 | import com.google.devtools.build.lib.vfs.Symlinks; |
| 50 | import java.io.File; |
| 51 | import java.io.IOException; |
ulfjack | deab0cf | 2017-08-08 13:08:24 +0200 | [diff] [blame] | 52 | import java.time.Duration; |
twerth | bc898ca | 2018-07-06 03:23:26 -0700 | [diff] [blame] | 53 | import java.util.HashMap; |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 54 | import java.util.Map; |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 55 | import java.util.SortedMap; |
jmmv | bd923e7 | 2018-04-10 14:01:34 -0700 | [diff] [blame] | 56 | import javax.annotation.Nullable; |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 57 | |
| 58 | /** Spawn runner that uses linux sandboxing APIs to execute a local subprocess. */ |
| 59 | final class LinuxSandboxedSpawnRunner extends AbstractSandboxSpawnRunner { |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 60 | |
twerth | bc898ca | 2018-07-06 03:23:26 -0700 | [diff] [blame] | 61 | // Since checking if sandbox is supported is expensive, we remember what we've checked. |
| 62 | private static final Map<Path, Boolean> isSupportedMap = new HashMap<>(); |
| 63 | |
ulfjack | 6517366 | 2018-06-11 07:01:48 -0700 | [diff] [blame] | 64 | /** |
| 65 | * Returns whether the linux sandbox is supported on the local machine by running a small command |
twerth | bc898ca | 2018-07-06 03:23:26 -0700 | [diff] [blame] | 66 | * in it. |
ulfjack | 6517366 | 2018-06-11 07:01:48 -0700 | [diff] [blame] | 67 | */ |
janakr | ca6d7ac | 2020-08-18 07:42:05 -0700 | [diff] [blame] | 68 | public static boolean isSupported(final CommandEnvironment cmdEnv) throws InterruptedException { |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 69 | if (OS.getCurrent() != OS.LINUX) { |
| 70 | return false; |
| 71 | } |
ruperts | 5274d8b | 2017-12-20 10:45:58 -0800 | [diff] [blame] | 72 | if (!LinuxSandboxUtil.isSupported(cmdEnv)) { |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 73 | return false; |
| 74 | } |
twerth | bc898ca | 2018-07-06 03:23:26 -0700 | [diff] [blame] | 75 | Path linuxSandbox = LinuxSandboxUtil.getLinuxSandbox(cmdEnv); |
janakr | ca6d7ac | 2020-08-18 07:42:05 -0700 | [diff] [blame] | 76 | Boolean isSupported; |
| 77 | synchronized (isSupportedMap) { |
| 78 | isSupported = isSupportedMap.get(linuxSandbox); |
| 79 | if (isSupported != null) { |
| 80 | return isSupported; |
| 81 | } |
| 82 | isSupported = computeIsSupported(cmdEnv, linuxSandbox); |
| 83 | isSupportedMap.put(linuxSandbox, isSupported); |
| 84 | } |
| 85 | return isSupported; |
twerth | bc898ca | 2018-07-06 03:23:26 -0700 | [diff] [blame] | 86 | } |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 87 | |
janakr | ca6d7ac | 2020-08-18 07:42:05 -0700 | [diff] [blame] | 88 | private static boolean computeIsSupported(CommandEnvironment cmdEnv, Path linuxSandbox) |
| 89 | throws InterruptedException { |
Philipp Wollermann | 23e1c5d | 2018-03-23 07:39:27 -0700 | [diff] [blame] | 90 | ImmutableList<String> linuxSandboxArgv = |
Tobias Werth | 83ca7d1 | 2022-05-10 04:50:26 -0700 | [diff] [blame] | 91 | LinuxSandboxUtil.commandLineBuilder(linuxSandbox, ImmutableList.of("/bin/true")) |
| 92 | .setTimeout(Duration.ofSeconds(1)) |
| 93 | .build(); |
aehlig | fb05988 | 2017-12-20 06:02:07 -0800 | [diff] [blame] | 94 | ImmutableMap<String, String> env = ImmutableMap.of(); |
ruperts | 5274d8b | 2017-12-20 10:45:58 -0800 | [diff] [blame] | 95 | Path execRoot = cmdEnv.getExecRoot(); |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 96 | File cwd = execRoot.getPathFile(); |
| 97 | |
ruperts | 5274d8b | 2017-12-20 10:45:58 -0800 | [diff] [blame] | 98 | Command cmd = new Command(linuxSandboxArgv.toArray(new String[0]), env, cwd); |
ulfjack | ef5b63d | 2018-06-15 07:19:11 -0700 | [diff] [blame] | 99 | try (SilentCloseable c = Profiler.instance().profile("LinuxSandboxedSpawnRunner.isSupported")) { |
ulfjack | f2d4595 | 2017-08-09 15:27:49 +0200 | [diff] [blame] | 100 | cmd.execute(ByteStreams.nullOutputStream(), ByteStreams.nullOutputStream()); |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 101 | } catch (CommandException e) { |
| 102 | return false; |
| 103 | } |
| 104 | |
| 105 | return true; |
| 106 | } |
| 107 | |
jmmv | 4e5b5b0 | 2020-03-19 10:02:10 -0700 | [diff] [blame] | 108 | private final SandboxHelpers helpers; |
tomlu | f903eb5 | 2017-10-27 12:12:11 -0400 | [diff] [blame] | 109 | private final FileSystem fileSystem; |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 110 | private final BlazeDirectories blazeDirs; |
| 111 | private final Path execRoot; |
| 112 | private final boolean allowNetwork; |
| 113 | private final Path linuxSandbox; |
philwo | d3de5cc | 2018-04-16 06:40:19 -0700 | [diff] [blame] | 114 | private final Path sandboxBase; |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 115 | private final Path inaccessibleHelperFile; |
| 116 | private final Path inaccessibleHelperDir; |
László Csomor | cfccdf1 | 2017-10-16 17:11:55 +0200 | [diff] [blame] | 117 | private final LocalEnvProvider localEnvProvider; |
Philipp Wollermann | 23e1c5d | 2018-03-23 07:39:27 -0700 | [diff] [blame] | 118 | private final Duration timeoutKillDelay; |
jmmv | d300615 | 2020-03-09 07:54:37 -0700 | [diff] [blame] | 119 | @Nullable private final SandboxfsProcess sandboxfsProcess; |
jmmv | c2ba4a0 | 2019-03-18 14:51:45 -0700 | [diff] [blame] | 120 | private final boolean sandboxfsMapSymlinkTargets; |
jmmv | 16af94c | 2019-04-16 07:44:34 -0700 | [diff] [blame] | 121 | private final TreeDeleter treeDeleter; |
ruperts | da00941 | 2017-12-22 16:59:38 -0800 | [diff] [blame] | 122 | |
| 123 | /** |
| 124 | * Creates a sandboxed spawn runner that uses the {@code linux-sandbox} tool. |
| 125 | * |
jmmv | 4e5b5b0 | 2020-03-19 10:02:10 -0700 | [diff] [blame] | 126 | * @param helpers common tools and state across all spawns during sandboxed execution |
ruperts | da00941 | 2017-12-22 16:59:38 -0800 | [diff] [blame] | 127 | * @param cmdEnv the command environment to use |
| 128 | * @param sandboxBase path to the sandbox base directory |
ruperts | da00941 | 2017-12-22 16:59:38 -0800 | [diff] [blame] | 129 | * @param inaccessibleHelperFile path to a file that is (already) inaccessible |
| 130 | * @param inaccessibleHelperDir path to a directory that is (already) inaccessible |
Philipp Wollermann | 23e1c5d | 2018-03-23 07:39:27 -0700 | [diff] [blame] | 131 | * @param timeoutKillDelay an additional grace period before killing timing out commands |
jmmv | bd923e7 | 2018-04-10 14:01:34 -0700 | [diff] [blame] | 132 | * @param sandboxfsProcess instance of the sandboxfs process to use; may be null for none, in |
| 133 | * which case the runner uses a symlinked sandbox |
jmmv | c2ba4a0 | 2019-03-18 14:51:45 -0700 | [diff] [blame] | 134 | * @param sandboxfsMapSymlinkTargets map the targets of symlinks within the sandbox if true |
ruperts | da00941 | 2017-12-22 16:59:38 -0800 | [diff] [blame] | 135 | */ |
| 136 | LinuxSandboxedSpawnRunner( |
jmmv | 4e5b5b0 | 2020-03-19 10:02:10 -0700 | [diff] [blame] | 137 | SandboxHelpers helpers, |
ruperts | da00941 | 2017-12-22 16:59:38 -0800 | [diff] [blame] | 138 | CommandEnvironment cmdEnv, |
| 139 | Path sandboxBase, |
ruperts | da00941 | 2017-12-22 16:59:38 -0800 | [diff] [blame] | 140 | Path inaccessibleHelperFile, |
| 141 | Path inaccessibleHelperDir, |
jmmv | bd923e7 | 2018-04-10 14:01:34 -0700 | [diff] [blame] | 142 | Duration timeoutKillDelay, |
jmmv | c2ba4a0 | 2019-03-18 14:51:45 -0700 | [diff] [blame] | 143 | @Nullable SandboxfsProcess sandboxfsProcess, |
jmmv | 16af94c | 2019-04-16 07:44:34 -0700 | [diff] [blame] | 144 | boolean sandboxfsMapSymlinkTargets, |
| 145 | TreeDeleter treeDeleter) { |
philwo | d3de5cc | 2018-04-16 06:40:19 -0700 | [diff] [blame] | 146 | super(cmdEnv); |
jmmv | 4e5b5b0 | 2020-03-19 10:02:10 -0700 | [diff] [blame] | 147 | this.helpers = helpers; |
tomlu | f903eb5 | 2017-10-27 12:12:11 -0400 | [diff] [blame] | 148 | this.fileSystem = cmdEnv.getRuntime().getFileSystem(); |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 149 | this.blazeDirs = cmdEnv.getDirectories(); |
| 150 | this.execRoot = cmdEnv.getExecRoot(); |
jmmv | 4e5b5b0 | 2020-03-19 10:02:10 -0700 | [diff] [blame] | 151 | this.allowNetwork = helpers.shouldAllowNetwork(cmdEnv.getOptions()); |
ruperts | 5274d8b | 2017-12-20 10:45:58 -0800 | [diff] [blame] | 152 | this.linuxSandbox = LinuxSandboxUtil.getLinuxSandbox(cmdEnv); |
philwo | d3de5cc | 2018-04-16 06:40:19 -0700 | [diff] [blame] | 153 | this.sandboxBase = sandboxBase; |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 154 | this.inaccessibleHelperFile = inaccessibleHelperFile; |
| 155 | this.inaccessibleHelperDir = inaccessibleHelperDir; |
ruperts | da00941 | 2017-12-22 16:59:38 -0800 | [diff] [blame] | 156 | this.timeoutKillDelay = timeoutKillDelay; |
jmmv | bd923e7 | 2018-04-10 14:01:34 -0700 | [diff] [blame] | 157 | this.sandboxfsProcess = sandboxfsProcess; |
jmmv | c2ba4a0 | 2019-03-18 14:51:45 -0700 | [diff] [blame] | 158 | this.sandboxfsMapSymlinkTargets = sandboxfsMapSymlinkTargets; |
Laszlo Csomor | 5bfa584 | 2018-01-15 06:01:25 -0800 | [diff] [blame] | 159 | this.localEnvProvider = new PosixLocalEnvProvider(cmdEnv.getClientEnv()); |
jmmv | 16af94c | 2019-04-16 07:44:34 -0700 | [diff] [blame] | 160 | this.treeDeleter = treeDeleter; |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 161 | } |
| 162 | |
| 163 | @Override |
ulfjack | 4034965 | 2019-09-02 03:06:42 -0700 | [diff] [blame] | 164 | protected SandboxedSpawn prepareSpawn(Spawn spawn, SpawnExecutionContext context) |
janakr | 300c11e | 2021-05-18 12:10:43 -0700 | [diff] [blame] | 165 | throws IOException, ForbiddenActionInputException, ExecException, InterruptedException { |
philwo | f032268f | 2018-04-25 06:16:05 -0700 | [diff] [blame] | 166 | // Each invocation of "exec" gets its own sandbox base. |
| 167 | // Note that the value returned by context.getId() is only unique inside one given SpawnRunner, |
| 168 | // so we have to prefix our name to turn it into a globally unique value. |
| 169 | Path sandboxPath = |
| 170 | sandboxBase.getRelative(getName()).getRelative(Integer.toString(context.getId())); |
| 171 | sandboxPath.getParentDirectory().createDirectory(); |
philwo | d3de5cc | 2018-04-16 06:40:19 -0700 | [diff] [blame] | 172 | sandboxPath.createDirectory(); |
dannark | 2a5512f | 2018-04-04 14:02:13 -0700 | [diff] [blame] | 173 | |
philwo | d3de5cc | 2018-04-16 06:40:19 -0700 | [diff] [blame] | 174 | // b/64689608: The execroot of the sandboxed process must end with the workspace name, just like |
| 175 | // the normal execroot does. |
jmmv | 45d652a | 2020-03-09 08:48:16 -0700 | [diff] [blame] | 176 | String workspaceName = execRoot.getBaseName(); |
| 177 | Path sandboxExecRoot = sandboxPath.getRelative("execroot").getRelative(workspaceName); |
philwo | d3de5cc | 2018-04-16 06:40:19 -0700 | [diff] [blame] | 178 | sandboxExecRoot.getParentDirectory().createDirectory(); |
| 179 | sandboxExecRoot.createDirectory(); |
László Csomor | cfccdf1 | 2017-10-16 17:11:55 +0200 | [diff] [blame] | 180 | |
laszlocsomor | d2cb96a | 2020-01-14 02:35:04 -0800 | [diff] [blame] | 181 | ImmutableMap<String, String> environment = |
felly | 472320c | 2018-10-29 14:27:00 -0700 | [diff] [blame] | 182 | localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), binTools, "/tmp"); |
Laszlo Csomor | 6ab93a6 | 2018-01-08 00:45:57 -0800 | [diff] [blame] | 183 | |
Philipp Wollermann | 23e1c5d | 2018-03-23 07:39:27 -0700 | [diff] [blame] | 184 | ImmutableSet<Path> writableDirs = getWritableDirs(sandboxExecRoot, environment); |
jmmv | 0367152 | 2020-03-12 08:21:38 -0700 | [diff] [blame] | 185 | |
| 186 | SandboxInputs inputs = |
jmmv | 4e5b5b0 | 2020-03-19 10:02:10 -0700 | [diff] [blame] | 187 | helpers.processInputFiles( |
lberki | 7149f57 | 2021-02-08 04:15:53 -0800 | [diff] [blame] | 188 | context.getInputMapping(PathFragment.EMPTY_FRAGMENT), |
lberki | 7149f57 | 2021-02-08 04:15:53 -0800 | [diff] [blame] | 189 | execRoot); |
jmmv | 4e5b5b0 | 2020-03-19 10:02:10 -0700 | [diff] [blame] | 190 | SandboxOutputs outputs = helpers.getOutputs(spawn); |
jmmv | 0367152 | 2020-03-12 08:21:38 -0700 | [diff] [blame] | 191 | |
tomlu | 29e306d | 2018-04-19 05:41:44 -0700 | [diff] [blame] | 192 | Duration timeout = context.getTimeout(); |
ruperts | da00941 | 2017-12-22 16:59:38 -0800 | [diff] [blame] | 193 | |
| 194 | LinuxSandboxUtil.CommandLineBuilder commandLineBuilder = |
Philipp Wollermann | 23e1c5d | 2018-03-23 07:39:27 -0700 | [diff] [blame] | 195 | LinuxSandboxUtil.commandLineBuilder(linuxSandbox, spawn.getArguments()) |
jmmv | 2bb3b73 | 2020-09-03 10:04:27 -0700 | [diff] [blame] | 196 | .addExecutionInfo(spawn.getExecutionInfo()) |
ruperts | da00941 | 2017-12-22 16:59:38 -0800 | [diff] [blame] | 197 | .setWritableFilesAndDirectories(writableDirs) |
ajurkowski | 3638052 | 2021-06-11 17:16:59 -0700 | [diff] [blame] | 198 | .setTmpfsDirectories(ImmutableSet.copyOf(getSandboxOptions().sandboxTmpfsPath)) |
ruperts | da00941 | 2017-12-22 16:59:38 -0800 | [diff] [blame] | 199 | .setBindMounts(getReadOnlyBindMounts(blazeDirs, sandboxExecRoot)) |
| 200 | .setUseFakeHostname(getSandboxOptions().sandboxFakeHostname) |
Austin Schuh | 520e907 | 2018-08-16 06:50:54 -0700 | [diff] [blame] | 201 | .setCreateNetworkNamespace( |
| 202 | !(allowNetwork |
| 203 | || Spawns.requiresNetwork( |
| 204 | spawn, getSandboxOptions().defaultSandboxAllowNetwork))) |
philwo | d3de5cc | 2018-04-16 06:40:19 -0700 | [diff] [blame] | 205 | .setUseDebugMode(getSandboxOptions().sandboxDebug) |
| 206 | .setKillDelay(timeoutKillDelay); |
ruperts | da00941 | 2017-12-22 16:59:38 -0800 | [diff] [blame] | 207 | |
| 208 | if (!timeout.isZero()) { |
| 209 | commandLineBuilder.setTimeout(timeout); |
| 210 | } |
philwo | d3de5cc | 2018-04-16 06:40:19 -0700 | [diff] [blame] | 211 | |
ruperts | da00941 | 2017-12-22 16:59:38 -0800 | [diff] [blame] | 212 | if (spawn.getExecutionInfo().containsKey(ExecutionRequirements.REQUIRES_FAKEROOT)) { |
| 213 | commandLineBuilder.setUseFakeRoot(true); |
| 214 | } else if (getSandboxOptions().sandboxFakeUsername) { |
| 215 | commandLineBuilder.setUseFakeUsername(true); |
| 216 | } |
| 217 | |
Philipp Wollermann | 23e1c5d | 2018-03-23 07:39:27 -0700 | [diff] [blame] | 218 | Path statisticsPath = null; |
ruperts | da00941 | 2017-12-22 16:59:38 -0800 | [diff] [blame] | 219 | if (getSandboxOptions().collectLocalSandboxExecutionStatistics) { |
Philipp Wollermann | 23e1c5d | 2018-03-23 07:39:27 -0700 | [diff] [blame] | 220 | statisticsPath = sandboxPath.getRelative("stats.out"); |
| 221 | commandLineBuilder.setStatisticsPath(statisticsPath); |
ruperts | da00941 | 2017-12-22 16:59:38 -0800 | [diff] [blame] | 222 | } |
| 223 | |
jmmv | bd923e7 | 2018-04-10 14:01:34 -0700 | [diff] [blame] | 224 | if (sandboxfsProcess != null) { |
ulfjack | 4034965 | 2019-09-02 03:06:42 -0700 | [diff] [blame] | 225 | return new SandboxfsSandboxedSpawn( |
| 226 | sandboxfsProcess, |
| 227 | sandboxPath, |
jmmv | 45d652a | 2020-03-09 08:48:16 -0700 | [diff] [blame] | 228 | workspaceName, |
ulfjack | 4034965 | 2019-09-02 03:06:42 -0700 | [diff] [blame] | 229 | commandLineBuilder.build(), |
| 230 | environment, |
jmmv | 0367152 | 2020-03-12 08:21:38 -0700 | [diff] [blame] | 231 | inputs, |
ulfjack | 4034965 | 2019-09-02 03:06:42 -0700 | [diff] [blame] | 232 | outputs, |
| 233 | ImmutableSet.of(), |
| 234 | sandboxfsMapSymlinkTargets, |
| 235 | treeDeleter, |
| 236 | statisticsPath); |
frazze-jobb | 11f7d80 | 2021-09-06 08:45:27 -0700 | [diff] [blame] | 237 | } else if (getSandboxOptions().useHermetic) { |
| 238 | commandLineBuilder.setHermeticSandboxPath(sandboxPath); |
| 239 | return new HardlinkedSandboxedSpawn( |
| 240 | sandboxPath, |
| 241 | sandboxExecRoot, |
| 242 | commandLineBuilder.build(), |
| 243 | environment, |
| 244 | inputs, |
| 245 | outputs, |
| 246 | writableDirs, |
| 247 | treeDeleter, |
| 248 | statisticsPath, |
| 249 | getSandboxOptions().sandboxDebug); |
jmmv | bd923e7 | 2018-04-10 14:01:34 -0700 | [diff] [blame] | 250 | } else { |
ulfjack | 4034965 | 2019-09-02 03:06:42 -0700 | [diff] [blame] | 251 | return new SymlinkedSandboxedSpawn( |
| 252 | sandboxPath, |
| 253 | sandboxExecRoot, |
| 254 | commandLineBuilder.build(), |
| 255 | environment, |
jmmv | 0367152 | 2020-03-12 08:21:38 -0700 | [diff] [blame] | 256 | inputs, |
ulfjack | 4034965 | 2019-09-02 03:06:42 -0700 | [diff] [blame] | 257 | outputs, |
| 258 | writableDirs, |
| 259 | treeDeleter, |
larsrc | 1adb512 | 2021-06-16 03:39:10 -0700 | [diff] [blame] | 260 | statisticsPath, |
| 261 | getSandboxOptions().reuseSandboxDirectories, |
| 262 | sandboxBase, |
| 263 | spawn.getMnemonic()); |
jmmv | bd923e7 | 2018-04-10 14:01:34 -0700 | [diff] [blame] | 264 | } |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 265 | } |
| 266 | |
| 267 | @Override |
olaola | ec08553 | 2018-02-22 10:33:28 -0800 | [diff] [blame] | 268 | public String getName() { |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 269 | return "linux-sandbox"; |
| 270 | } |
| 271 | |
| 272 | @Override |
Laszlo Csomor | 6cc2ad8 | 2018-01-11 05:30:13 -0800 | [diff] [blame] | 273 | protected ImmutableSet<Path> getWritableDirs(Path sandboxExecRoot, Map<String, String> env) |
| 274 | throws IOException { |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 275 | ImmutableSet.Builder<Path> writableDirs = ImmutableSet.builder(); |
Laszlo Csomor | 6cc2ad8 | 2018-01-11 05:30:13 -0800 | [diff] [blame] | 276 | writableDirs.addAll(super.getWritableDirs(sandboxExecRoot, env)); |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 277 | |
| 278 | FileSystem fs = sandboxExecRoot.getFileSystem(); |
| 279 | writableDirs.add(fs.getPath("/dev/shm").resolveSymbolicLinks()); |
| 280 | writableDirs.add(fs.getPath("/tmp")); |
| 281 | |
| 282 | return writableDirs.build(); |
| 283 | } |
| 284 | |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 285 | private SortedMap<Path, Path> getReadOnlyBindMounts( |
| 286 | BlazeDirectories blazeDirs, Path sandboxExecRoot) throws UserExecException { |
tomlu | f903eb5 | 2017-10-27 12:12:11 -0400 | [diff] [blame] | 287 | Path tmpPath = fileSystem.getPath("/tmp"); |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 288 | final SortedMap<Path, Path> bindMounts = Maps.newTreeMap(); |
| 289 | if (blazeDirs.getWorkspace().startsWith(tmpPath)) { |
| 290 | bindMounts.put(blazeDirs.getWorkspace(), blazeDirs.getWorkspace()); |
| 291 | } |
| 292 | if (blazeDirs.getOutputBase().startsWith(tmpPath)) { |
| 293 | bindMounts.put(blazeDirs.getOutputBase(), blazeDirs.getOutputBase()); |
| 294 | } |
| 295 | for (ImmutableMap.Entry<String, String> additionalMountPath : |
Benjamin Peterson | 54c6f32 | 2017-08-04 15:56:55 +0200 | [diff] [blame] | 296 | getSandboxOptions().sandboxAdditionalMounts) { |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 297 | try { |
tomlu | f903eb5 | 2017-10-27 12:12:11 -0400 | [diff] [blame] | 298 | final Path mountTarget = fileSystem.getPath(additionalMountPath.getValue()); |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 299 | // If source path is relative, treat it as a relative path inside the execution root |
| 300 | final Path mountSource = sandboxExecRoot.getRelative(additionalMountPath.getKey()); |
| 301 | // If a target has more than one source path, the latter one will take effect. |
| 302 | bindMounts.put(mountTarget, mountSource); |
| 303 | } catch (IllegalArgumentException e) { |
| 304 | throw new UserExecException( |
mschaller | 0793388 | 2020-06-24 14:38:23 -0700 | [diff] [blame] | 305 | createFailureDetail( |
| 306 | String.format("Error occurred when analyzing bind mount pairs. %s", e.getMessage()), |
| 307 | Code.BIND_MOUNT_ANALYSIS_FAILURE)); |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 308 | } |
| 309 | } |
| 310 | for (Path inaccessiblePath : getInaccessiblePaths()) { |
| 311 | if (inaccessiblePath.isDirectory(Symlinks.NOFOLLOW)) { |
| 312 | bindMounts.put(inaccessiblePath, inaccessibleHelperDir); |
| 313 | } else { |
| 314 | bindMounts.put(inaccessiblePath, inaccessibleHelperFile); |
| 315 | } |
| 316 | } |
| 317 | validateBindMounts(bindMounts); |
| 318 | return bindMounts; |
| 319 | } |
| 320 | |
| 321 | /** |
larsrc | ef9ea7a | 2021-02-12 06:19:07 -0800 | [diff] [blame] | 322 | * This method does the following things: |
| 323 | * |
| 324 | * <ul> |
| 325 | * <li>If mount source does not exist on the host system, throw an error message |
| 326 | * <li>If mount target exists, check whether the source and target are of the same type |
| 327 | * <li>If mount target does not exist on the host system, throw an error message |
| 328 | * </ul> |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 329 | * |
| 330 | * @param bindMounts the bind mounts map with target as key and source as value |
larsrc | ef9ea7a | 2021-02-12 06:19:07 -0800 | [diff] [blame] | 331 | * @throws UserExecException if any of the mount points are not valid |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 332 | */ |
| 333 | private void validateBindMounts(SortedMap<Path, Path> bindMounts) throws UserExecException { |
larsrc | ef9ea7a | 2021-02-12 06:19:07 -0800 | [diff] [blame] | 334 | for (Map.Entry<Path, Path> bindMount : bindMounts.entrySet()) { |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 335 | final Path source = bindMount.getValue(); |
| 336 | final Path target = bindMount.getKey(); |
| 337 | // Mount source should exist in the file system |
| 338 | if (!source.exists()) { |
mschaller | 0793388 | 2020-06-24 14:38:23 -0700 | [diff] [blame] | 339 | throw new UserExecException( |
| 340 | createFailureDetail( |
| 341 | String.format("Mount source '%s' does not exist.", source), |
| 342 | Code.MOUNT_SOURCE_DOES_NOT_EXIST)); |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 343 | } |
| 344 | // If target exists, but is not of the same type as the source, then we cannot mount it. |
| 345 | if (target.exists()) { |
| 346 | boolean areBothDirectories = source.isDirectory() && target.isDirectory(); |
| 347 | boolean isSourceFile = source.isFile() || source.isSymbolicLink(); |
| 348 | boolean isTargetFile = target.isFile() || target.isSymbolicLink(); |
| 349 | boolean areBothFiles = isSourceFile && isTargetFile; |
| 350 | if (!(areBothDirectories || areBothFiles)) { |
| 351 | // Source and target are not of the same type; we cannot mount it. |
| 352 | throw new UserExecException( |
mschaller | 0793388 | 2020-06-24 14:38:23 -0700 | [diff] [blame] | 353 | createFailureDetail( |
| 354 | String.format( |
larsrc | ef9ea7a | 2021-02-12 06:19:07 -0800 | [diff] [blame] | 355 | "Mount target '%s' is a %s but mount source '%s' is a %s, they must be the" |
| 356 | + " same type.", |
| 357 | target, |
| 358 | (isTargetFile ? "file" : "directory"), |
| 359 | source, |
| 360 | (isSourceFile ? "file" : "directory")), |
mschaller | 0793388 | 2020-06-24 14:38:23 -0700 | [diff] [blame] | 361 | Code.MOUNT_SOURCE_TARGET_TYPE_MISMATCH)); |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 362 | } |
| 363 | } else { |
| 364 | // Mount target should exist in the file system |
| 365 | throw new UserExecException( |
mschaller | 0793388 | 2020-06-24 14:38:23 -0700 | [diff] [blame] | 366 | createFailureDetail( |
| 367 | String.format( |
| 368 | "Mount target '%s' does not exist. Bazel only supports bind mounting on top of " |
| 369 | + "existing files/directories. Please create an empty file or directory at " |
| 370 | + "the mount target path according to the type of mount source.", |
| 371 | target), |
| 372 | Code.MOUNT_TARGET_DOES_NOT_EXIST)); |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 373 | } |
| 374 | } |
| 375 | } |
jmmv | b93e61b | 2019-04-15 10:00:10 -0700 | [diff] [blame] | 376 | |
| 377 | @Override |
frazze-jobb | 11f7d80 | 2021-09-06 08:45:27 -0700 | [diff] [blame] | 378 | public void verifyPostCondition( |
| 379 | Spawn originalSpawn, SandboxedSpawn sandbox, SpawnExecutionContext context) |
| 380 | throws IOException, ForbiddenActionInputException { |
| 381 | if (getSandboxOptions().useHermetic) { |
| 382 | checkForConcurrentModifications(context); |
| 383 | } |
| 384 | } |
| 385 | |
| 386 | private void checkForConcurrentModifications(SpawnExecutionContext context) |
| 387 | throws IOException, ForbiddenActionInputException { |
Googler | 9eb783e | 2022-06-01 03:20:56 -0700 | [diff] [blame] | 388 | for (ActionInput input : context.getInputMapping(PathFragment.EMPTY_FRAGMENT).values()) { |
frazze-jobb | 11f7d80 | 2021-09-06 08:45:27 -0700 | [diff] [blame] | 389 | if (input instanceof VirtualActionInput) { |
| 390 | continue; |
| 391 | } |
| 392 | |
| 393 | FileArtifactValue metadata = context.getMetadataProvider().getMetadata(input); |
| 394 | Path path = execRoot.getRelative(input.getExecPath()); |
| 395 | |
| 396 | try { |
| 397 | if (wasModifiedSinceDigest(metadata.getContentsProxy(), path)) { |
| 398 | throw new IOException("input dependency " + path + " was modified during execution."); |
| 399 | } |
| 400 | } catch (UnsupportedOperationException e) { |
| 401 | throw new IOException( |
| 402 | "input dependency " |
| 403 | + path |
| 404 | + " could not be checked for modifications during execution.", |
| 405 | e); |
| 406 | } |
| 407 | } |
| 408 | } |
| 409 | |
| 410 | private boolean wasModifiedSinceDigest(FileContentsProxy proxy, Path path) throws IOException { |
| 411 | if (proxy == null) { |
| 412 | return false; |
| 413 | } |
| 414 | FileStatus stat = path.statIfFound(Symlinks.FOLLOW); |
| 415 | return stat == null || !stat.isFile() || proxy.isModified(FileContentsProxy.create(stat)); |
| 416 | } |
| 417 | |
| 418 | @Override |
jmmv | 16af94c | 2019-04-16 07:44:34 -0700 | [diff] [blame] | 419 | public void cleanupSandboxBase(Path sandboxBase, TreeDeleter treeDeleter) throws IOException { |
| 420 | // Delete the inaccessible files synchronously, bypassing the treeDeleter. They are only a |
| 421 | // couple of files that can be deleted fast, and ensuring they are gone at the end of every |
| 422 | // build avoids annoying permission denied errors if the user happens to run "rm -rf" on the |
| 423 | // output base. (We have some tests that do that.) |
jmmv | b93e61b | 2019-04-15 10:00:10 -0700 | [diff] [blame] | 424 | if (inaccessibleHelperDir.exists()) { |
| 425 | inaccessibleHelperDir.chmod(0700); |
| 426 | inaccessibleHelperDir.deleteTree(); |
| 427 | } |
| 428 | if (inaccessibleHelperFile.exists()) { |
| 429 | inaccessibleHelperFile.chmod(0600); |
| 430 | inaccessibleHelperFile.delete(); |
| 431 | } |
| 432 | |
jmmv | 16af94c | 2019-04-16 07:44:34 -0700 | [diff] [blame] | 433 | super.cleanupSandboxBase(sandboxBase, treeDeleter); |
jmmv | b93e61b | 2019-04-15 10:00:10 -0700 | [diff] [blame] | 434 | } |
ulfjack | a63da8e | 2017-07-12 16:51:20 +0200 | [diff] [blame] | 435 | } |