It's Simbple: Peeking Inside App Sandbox Profiles February 21, 2020
I concluded my previous post by motivating that we should be able to audit the sandbox configurations of apps we run. Because of the way the App Sandbox works on macOS, this means we need access to a human-readable version of the sandbox profile generated by libsandbox.dylib
. Unfortunately, this is not something that Apple’s software currently allows for.
There have been numerous projects that decompile sandbox bytecode back to a human-readable representation, most notably Blazakis’s XNUSandbox project, Esser’s sb2dot, work by argp and SandBlaster. These tools are necessary on iOS; Profiles there are only available in binary form. Most of these tools however suffer from two problems. Firstly, they are hard to keep up to date, as even small changes to the bytecode format break the tooling. Secondly, most of the tools do not produce syntactically valid SBPL and therefore cannot be recompiled. As a result, it is not possible to verify their output.
On macOS, things are generally simpler: Rather than reverse engineering the sandbox bytecode format, I reverse engineered how libsandbox
internally handles entitlements, sandbox snippets and parameters to evaluate and compile sandbox profiles.
The result of this research is simbple
, a tool that reimplements the profile evaluation process done in libsandbox
but outputs verifiably correct, human-readable SBPL.
Note that @sdotknight rightfully points out that there is a platform profile that also affects sandboxing on macOS. My approach here only considers the sandbox profile generated in userland and does not consider the platform profile, which is embedded inside of Sandbox.kext
.
The Anatomy of Container.plist files on macOS
simbple
takes as input an apps’ Container.plist
file, which can be found under ~/Library/Containers/bundle_id/
. This binary-encoded plist file contains lots of useful information (note: launch the target app once to generate the file). Follow along using:
# Built-in tools, always available
$ plutil -convert xml1 /path/to/Container.plist -o -
# Levin's excellent tools. Display the information in a simpler format.
$ jlutil /path/to/Container.plist
Likely taking up the most space in any Container.plist
, the SandboxProfileData
key holds the base64-encoded binary sandbox profile compiled by libsandbox
.
Grouped under SandboxProfileDataValidationInfo
are inputs libsandbox
uses to compile sandbox profiles:
SandboxProfileDataValidationParametersKey
— Contains several “global variables” such as the user’s home directory (stored as_HOME
), her username (_USER
) and various (auto-generated) paths to the application bundle and temporary directories.SandboxProfileDataValidationRedirectablePathsKey
— approved paths that may be accessed through symlinksSandboxProfileDataValidationEntitlementsKey
— An app’s entitlements.SandboxProfileDataValidationSnippetDictionariesKey
— A list of sandbox snippets (see previous post) included in the final profile. Each snippet is described by two keys, though only one of them —AppSandboxProfileSnippetPathKey
— is important here. It specifies the file system path to the snippet.
Using simbple
simbple
uses existing Container.plist
files to reuse the same parameters that were initially used by the system. This simplifies the design of the tool.
$ simbple --help
Usage: simbple [OPTION...] CONTAINER_METADATA
Evaluate a SBPL (+ Scheme) profile
-o, --output=FILE Output file
-p, --profile=PROFILE Base profile to evaluate. Defaults to
application.sb profile.
--platforms=PLATFORM sierra, high_sierra, mojave (default), catalina
Output formats:
--json Output as JSON
--scheme Output as SCHEME / SBPL
Misc options:
--patch Patch the output profile to log all statements.
--verify Verify semantic correctness of generated results
-?, --help Give this help list
--usage Give a short usage message
-V, --version Print program version
Mandatory or optional arguments to long options are also mandatory or optional
for any corresponding short options.
The output is a simplified SBPL profile that can be analysed, modified and
compiled as is.
In its simplest form, simply invoke simbple
with a path to the Container.plist
file of the app you are interested in. Doing so will spit out the SBPL profile of the target app:
$ simbple ~/Library/Containers/com.apple.calculator/Container.plist
(version 1)
(deny
default
)
(allow
mach-register
(local-name-prefix "")
)
(allow
mach-lookup
(xpc-service-name-prefix "")
)
# > 1800 lines follow on my system
The resulting sandbox profiles can be manually audited and modified, automatically patched or simply be compiled to profile bytecode using Stefan Esser’s tool. The results are useful not only to security researchers interested in studying the sandbox, but also for example to developers debugging their sandboxed applications.
To verify that results are correct — meaning compiling the output results in identical sandbox bytecode to libsandbox
’s result — use the --verify
option. This is yet another benefit of using existing Container.plist
files. We can use the SandboxProfileData
data as ground truth to check against. Sandbox compilation is still a (mostly) deterministic process.
$ simbple ~/Library/Containers/com.apple.calculator/Container.plist -o /dev/null --verify
$ echo $?
0 # Verification succeeded.
A Teaser: The Story of CVE-2018-4184
macOS 10.13.5 fixed CVE-2018-4184, an issue with “the handling of microphone access” I reported in 2018:
*Speech*
Available for: macOS High Sierra 10.13.4
Impact: A sandboxed process may be able to circumvent sandbox restrictions
Description: A sandbox issue existed in the handling of microphone access. This issue was addressed with improved handling of microphone access.
CVE-2018-4184: Jakob Rieck (@0xdead10cc) of the Security in Distributed Systems Group, University of Hamburg
What was the problem? Virtually every app — no matter their entitlements — was able to use the microphone on macOS. How did I figure this out? Well, I was developing simbple
and thought my tool couldn’t possibly work correctly: scrolling through Calculator.app
’s results, I noticed this line in the generated profile:
(allow device-microphone)
Refer back to my previous post to see Calculator.app
’s entitlements, which notably do not (and did not) allow the app access to the microphone. What was going on?!