# **Mac Sandbox V2 Design Doc**
*Status: Final, Authors: [email protected], [email protected], Last Updated: 2020-05-14*
# **Objective**
To improve security on macOS by sandboxing the currently unsandboxed
warmup phase of Chromium child processes, and to remove legacy artifacts
in the sandbox profiles by rewriting them to use the most modern
profile features.
# **Background**
Chromium historically ran an unsandboxed warm up routine to acquire
system resources, before entering the sandbox. This design doc
provides a full implementation design and deployment strategy to
sandbox the warmup phase. This document also provides a high level
overview of the macOS provided sandbox.
In the warm up phase, Chromium called system frameworks which
acquired an unspecified number of resources before being sandboxed,
and those resources change with every new OS update from Apple.
This [2009 Chromium blog
post](https://blog.chromium.org/2009/06/google-chrome-sandboxing-and-mac-os-x.html&sa=D&ust=1492473048358000&usg=AFQjCNGEbmCLUqoH9-BeudDcNf5NmW-UcQ)
explains that the warmup phase exists because it was unknown how
to determine what resources those APIs used at the time. By explicitly
enumerating all resources in the sandbox profiles, it is possible
to accurately audit Chromium's attack surface for each new macOS
version.
Anyone wishing to know more about the macOS sandbox profile language
should read the [Apple Sandbox
Guide](http://reverse.put.as/wp-content/uploads/2011/09/Apple-Sandbox-Guide-v1.0.pdf)
from reverse.put.as, or see the Appendix of this doc.
# **The V2 Sandbox Implementation**
The V2 sandbox continues to use the OS provided sandboxing
framework and the "deny resource access by default" policy that the
V1 sandbox used. The major difference under the V2 sandbox architecture
is the removal of the unsandboxed warmup phase.
# **Compatibility and Security Risk**
Chromium's sandbox incurs two levels of risk which must be weighed:
compatibility risk and security risk. Compatibility risk is the
risk that Apple changes the resources that a system framework
accesses, causing Chromium's sandbox to block the access. Security
risk is the possibility that Chromium will be compromised because the
sandbox allowed access to dangerous resources.
A more permissive sandbox profile reduces compatibility risk but
increases security risk and vice versa. See
[crbug.com/619981](https://bugs.chromium.org/p/chromium/issues/detail?id=619981)
for an example of how even given the V1 unsandboxed warmup phase, the
implementation already dealt with compatibility risk.
The V1 sandbox also has greater security risk at the warmup
time, since that phase is unsandboxed. Rather than incurring both
compatibility and security risk during execution, the V2 sandbox
shifts the risk profile to take on more compatibility risk, since
the warmup phase is eliminated, but it eliminates security risk.
# **Success Criteria**
The V2 sandbox will be deemed a success when it has been deployed
to stable users for all processes in Chromium, and Chromium does not
lose functionality due to the new sandbox rules.
# **Design Overview**
The Chromium Helper executable will now receive a sandbox profile
and list of sandbox parameters (for example, the location of the
user's home directory). The Helper executable will then apply the
sandbox profile, with the parameters, to the process and then
continue its execution into the ChromeMain function.
# **Detailed Design**
## Executable Structure
Both the main Chromium executable and all of the bundled Chromium Helper
executables contain [a minimal amount of
code](https://source.chromium.org/chromium/chromium/src/+/main:chrome/app/chrome_exe_main_mac.cc;drc=05219ddeb8130389da9ad634ba3e021a70bff393).
The bulk of the code lives in the Chromium Framework library, which is
`dlopen()`ed at runtime to call `ChromeMain()`, after applying the sandbox.
This design choice has beneficial security properties: many system frameworks
run static initializers, and some of these static initializers acquire and
maintain access to files or Mach service ports. If the executables directly
linked the Chromium Framework or the system frameworks, then those initializers
would be able to run outside the sandbox, since static initializers run before
`main()`. The Chromium and Helper executables instead only link
`/usr/lib/libSystem.dylib` and the very small `//sandbox/mac` target, to bring
in `/usr/lib/libsandbox.dylib`. After the executable applies the sandbox, the
program then loads the Framework, which causes all the dependent system
frameworks to load.
## Sandbox Profile Principles
The sandbox profiles will use the following principles when
implementing the profiles for each process type.
- The profiles deny resource access to anything not explicitly allowed.
- Processes may access all system libraries and frameworks.
- Explicitly list which system resources (files, folders, mach services,
sysctls, IPC, IOKit calls) Chromium may access. Analyze each resource access
for safety first.
## Sandbox Design
The V1 sandbox code lives in
[sandbox_mac.mm](https://source.chromium.org/chromium/chromium/src/+/main:services/service_manager/sandbox/mac/sandbox_mac.mm;l=1;drc=efd8e880522dc1df3b8883648513016fab3d3956).
This file will continue to exist until the V1 sandbox is removed for all process
types. Chromium now uses the V2 sandbox for all process types except the GPU
process.
## Code Structure
Chromium's architecture is multi-process with a main browser process
and some number of helper processes which performs tasks such as
rendering web pages, running GPU processes, or various utility
functions. The main browser process launches the other processes
using the "Google Chromium Helper" executable. The browser passes
command line flags to the Helper executable indicating what type
of process it should execute as.
The browser creates a pipe to the Helper executable, and
using that pipe, the Helper executable reads a protobuf message
from the browser process. The profobuf message contains the
profile string and a map of the sandbox parameters. Using the
lightweight protobuf prevents the error prone parsing code for a
whole parameters list from being re-implemented. This must happen
in the main executable itself, as the process must be sandboxed
before the framework is loaded.
The Helper executable will immediately initialize the sandbox, using
the profile and parameters, before continuing execution to the
`ChromeMain()` function.
# **Design Alternatives**
One alternative design is the current design where process acquires
numerous system resources early in the execution phase and then
applies the sandbox. However, this means that we do not have a
consistent and auditable way to track what resources Chromium is using
over time. Chromium's attack surface can increase significantly with
a new OS release, and we would not know. The new explicit profiles
make the attack surface very auditable and easy to understand.
Another alternative would have been the [Bootstrap
Sandbox](https://docs.google.com/document/d/108sr6gBxqdrnzVPsb_4_JbDyW1V4-DRQUC4R8YvM40M/view)
plan, which proposed intercepting all mach connections to limit the
attack surface. However, with the rewritten launchd released in macOS
10.10 Yosemite, messages could no longer be intercepted and forward.
This design solves the same problem by sandboxing the warmup phase,
and allowing the existing system sandbox to enforce the security.
# **Appendix**
## Sandbox Profile Language
This document has referenced sandbox profiles, which are written
in a subset of Scheme. This is a macOS supplied format called Sandbox
Profile Language (SBPL). As there is no official OS-provided
documentation for the SBPL, this design doc provides a short
description below so that other engineers can understand the
constructs that Chromium is leveraging.
SBPL files accept parameters. A parameter is a way to pass variable
information into the sandbox profile that is not known at compile-time,
for example the location of the user's home directory. A short
sample profile which uses parameters is below.
```
(version 1)
(deny default)
(allow file-read* (subpath (param "USER_HOME_DIR")))
```
This profile will allow the application to read all files in the
user's home directory, and nothing else. The (subpath) directive
says that it applies to all paths underneath the specified path,
and not just the actual path. The following code will apply the
sandbox, pass the parameters, and access a file in the home directory.
```
#import <Foundation/Foundation.h>
#import <sandbox.h>
#import <sys/stat.h>
// sandbox_init* functions are not in the public header.
int sandbox_init_with_parameters(const char *profile,
uint64_t flags,
const char *const parameters[],
char **errorbuf);
int main(int argc, const char * argv[]) {
const char profile[] = "(version 1)" \
"(deny default)" \
"(allow file-read* (subpath (param \"USER_HOME_DIR\")))";
const char* home_dir = [NSHomeDirectory() UTF8String];
// Parameters are passed as an array containing key,value,NULL.
const char* parameters[] = { "USER_HOME_DIR", home_dir, NULL };
if (sandbox_init_with_parameters(profile, 0, parameters, NULL))
exit(1);
const char* vim_rc =
[[NSHomeDirectory() stringByAppendingString:@"/.vimrc"]
UTF8String];
struct stat sb;
if (stat(vim_rc, &sb) == 0)
printf(".vimrc file exists\n");
return 0;
}
```
The following information must be collected as parameters for the
sandbox profiles to function:
* Whether or not the sandbox should log failures
* The user's home directory
* Information about the specific OS version (10.9, 10.10, etc.)
* The path to the main `Google Chromium.app` bundle
* The PID of the Chromium process
* If applicable, the path to the permitted directory that the process can read and write to.
The sandbox directives most commonly used in Chromium's V2 profiles
are documented here for future reference.
```(deny default)``` - All resource access is denied by default. The
sandbox must list allow rules for all resources.
```(path "/path")``` - Match exactly this path.
```(subpath "/path")``` - Match this path and all subpaths of this
path. ("/path", "/path/foo", etc.)
```(allow process-exec*)``` - Allows the specified binary to be
executed as a process.
```(allow mach-lookup (global-name "com.apple.system.logger"))``` -
Allows the process to communicate with the specified system service.
```(allow sysctl-read)``` - Allow access to sysctl() to read property
values.