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
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
SandboxProfileData key holds the base64-encoded binary sandbox profile compiled by
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 symlinks
SandboxProfileDataValidationEntitlementsKey— 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.
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:
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?!