// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.sandbox; import com.google.common.collect.ImmutableMap; import com.google.common.io.ByteStreams; import com.google.devtools.build.lib.shell.Command; import com.google.devtools.build.lib.shell.CommandException; import com.google.devtools.build.lib.shell.KillableObserver; import com.google.devtools.build.lib.shell.TimeoutKillableObserver; import com.google.devtools.build.lib.util.OS; import com.google.devtools.build.lib.vfs.Path; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; /** * Helper class for running the namespace sandbox. This runner prepares environment inside the * sandbox, handles sandbox output, performs cleanup and changes invocation if necessary. */ final class DarwinSandboxRunner extends SandboxRunner { private static final String SANDBOX_EXEC = "/usr/bin/sandbox-exec"; private final Path sandboxExecRoot; private final Path argumentsFilePath; private final Set writableDirs; private final Set inaccessiblePaths; private final Path runUnderPath; DarwinSandboxRunner( Path sandboxPath, Path sandboxExecRoot, Set writableDirs, Set inaccessiblePaths, Path runUnderPath, boolean verboseFailures) { super(sandboxPath, sandboxExecRoot, verboseFailures); this.sandboxExecRoot = sandboxExecRoot; this.argumentsFilePath = sandboxPath.getRelative("sandbox.sb"); this.writableDirs = writableDirs; this.inaccessiblePaths = inaccessiblePaths; this.runUnderPath = runUnderPath; } static boolean isSupported() { // Check osx version, only >=10.11 is supported. // And we should check if sandbox still work when it gets 11.x String osxVersion = OS.getVersion(); String[] parts = osxVersion.split("\\."); if (parts.length != 3) { // Currently the format is 10.11.x return false; } try { int v0 = Integer.parseInt(parts[0]); int v1 = Integer.parseInt(parts[1]); if (v0 != 10 || v1 < 11) { return false; } } catch (NumberFormatException e) { return false; } List args = new ArrayList<>(); args.add(SANDBOX_EXEC); args.add("-p"); args.add("(version 1) (allow default)"); args.add("/usr/bin/true"); ImmutableMap env = ImmutableMap.of(); File cwd = new File("/usr/bin"); Command cmd = new Command(args.toArray(new String[0]), env, cwd); try { cmd.execute( /* stdin */ new byte[] {}, Command.NO_OBSERVER, ByteStreams.nullOutputStream(), ByteStreams.nullOutputStream(), /* killSubprocessOnInterrupt */ true); } catch (CommandException e) { return false; } return true; } @Override protected Command getCommand( List arguments, Map environment, int timeout, boolean allowNetwork) throws IOException { writeConfig(allowNetwork); List commandLineArgs = new ArrayList<>(); commandLineArgs.add(SANDBOX_EXEC); commandLineArgs.add("-f"); commandLineArgs.add(argumentsFilePath.getPathString()); commandLineArgs.addAll(arguments); return new Command( commandLineArgs.toArray(new String[0]), environment, sandboxExecRoot.getPathFile()); } private void writeConfig(boolean allowNetwork) throws IOException { try (PrintWriter out = new PrintWriter(argumentsFilePath.getOutputStream())) { // Note: In Apple's sandbox configuration language, the *last* matching rule wins. out.println("(version 1)"); out.println("(debug deny)"); out.println("(allow default)"); if (!allowNetwork) { out.println("(deny network*)"); } out.println("(allow network* (local ip \"localhost:*\"))"); out.println("(allow network* (remote ip \"localhost:*\"))"); out.println("(allow network* (remote unix-socket (subpath \"/\")))"); out.println("(allow network* (local unix-socket (subpath \"/\")))"); for (Path inaccessiblePath : inaccessiblePaths) { out.println("(deny file-read* (subpath \"" + inaccessiblePath + "\"))"); } if (runUnderPath != null) { out.println("(allow file-read* (subpath \"" + runUnderPath + "\"))"); } // Almost everything else is read-only. out.println("(deny file-write* (subpath \"/\"))"); allowWriteSubpath(out, sandboxExecRoot); for (Path path : writableDirs) { allowWriteSubpath(out, path); } } } private void allowWriteSubpath(PrintWriter out, Path path) throws IOException { out.println("(allow file-write* (subpath \"" + path.getPathString() + "\"))"); Path resolvedPath = path.resolveSymbolicLinks(); if (!resolvedPath.equals(path)) { out.println("(allow file-write* (subpath \"" + resolvedPath.getPathString() + "\"))"); } } @Override protected KillableObserver getCommandObserver(int timeout) { return (timeout >= 0) ? new TimeoutKillableObserver(timeout * 1000) : Command.NO_OBSERVER; } @Override protected int getSignalOnTimeout() { return 15; /* SIGTERM */ } }