The App Sandbox, originally introduced in Mac OS X Leopard as “the Seatbelt”, is a macOS security feature modeled after FreeBSD’s Mandatory Access Control (left unabbreviated for clarity) that serves as a way to restrict the abilities of an application beyond the usual user- and permission-based systems that UNIX offers. The full extent of the capabilities the sandbox manages is fairly broad, ranging from file operations to Mach calls, and is specified in a custom Scheme implementation called the Sandbox Profile Language (SBPL). The sandbox profiles that macOS ships with can be found in /System/Library/Sandbox/Profiles, and while their format is technically SPI (as the header comment on them will tell you) there is fairly extensive third-party documentation. The implementation details of sandboxing are not intended to be accessed by third-party developers, but applications on Apple’s platforms can request (and in some cases, such as new applications distributed on the Mac App Store and all applications for Apple’s embedded platforms, must function in) a sandbox specified by a fixed, system-defined profile (on macOS, application.sb). Barring a few exceptions (which usually require additional review and justification for their use) this system-provided sandbox provide an effective way to prevent applications from accessing user data without consent or performing undesired system modifications.
In January I discovered a flaw in the implementation of the sandbox initialization procedure on macOS that would allow malicious applications distributed through the Mac App Store to circumvent the enforcement of these restrictions and silently perform unauthorized operations, including actions such as accessing sensitive user data. Apple has since implemented changes in the Mac App Store to address this issue and the technique outlined below should no longer be effective.
IMO the app sandbox was a grievous strategic mistake for the Mac. Cocoa-based Mac apps are rapidly being eaten by web apps and Electron psuedo-desktop apps. For Mac apps to survive, they must capitalize on their strengths: superior performance, better system integration, better dev experience, more features, and higher general quality. So here’s a quick overview of what sandboxing is, why it’s crucial for security, and how to manually run apps in a sandbox on Mac. What is Sandboxing? The term “sandbox” is what it sounds like — keeping apps separate by giving each its sandbox area to cavort around in. A sandbox area is a directory that an app uses to store information.
Sandbox initialization on macOS
Sandboxing is enforced by the kernel and present on both macOS and Apple’s iOS-based operating systems, but it is important to note that third party code is not required to run in a sandbox on macOS. While the use of the platform sandbox is mandatory for third-party software running on embedded devices, on Macs it is rarely used by applications distributed outside of the Mac App Store; even on the store there are still a couple of unsandboxed applications that have been grandfathered into being allowed to remain for sale as they were published prior to the 2012 sandboxing deadline. A lesser known, but likely related fact is that processes are not born sandboxed on macOS: unlike iOS, where the sandbox is applied by the kernel before the first instruction of a program executes, on macOS a process must elect to place itself into the sandbox using the “deprecated”
sandbox_init(3) family of functions. These themselves are wrappers around the __sandbox_ms function, an alias for __mac_syscall from libsystem_kernel.dylib in /usr/lib/system. This design raises an important question: if a process chooses to place itself in a sandbox, how does Apple require it for apps distributed through the Mac App Store?
Apple helps you keep your Mac secure with software updates. The best way to keep your Mac secure is to run the latest software. When new updates are available, macOS sends you a notification — or you can opt in to have updates installed automatically when your Mac is not in use. MacOS checks for new updates every day, so it’s easy to always have the latest and safest version.
Experienced Mac developers already know the answer: Apple checks for the presence of the
com.apple.security.app-sandbox entitlement in all apps submitted for review, and its mere existence magically places the process in a sandbox by the time code execution reaches main . But the process isn’t actually magic at all: it’s performed by a function called _libsecinit_initializer inside the library libsystem_secinit.dylib, also located at /usr/lib/system:
_libsecinit_initializer calls _libsecinit_appsandbox , which (among other things) copies the current process’s entitlements, checks for the com.apple.security.app-sandbox in them, and calls __sandbox_ms after consulting with the secinitd daemon. So this answers where the sandbox is applied, but doesn’t explain how: for that, we need to look inside libSystem.
libSystem is the standard C library on macOS (see
intro(3) for more details). While it vends system APIs, by itself it does very little; instead, it provides this functionality by re-exporting all the libraries inside of /usr/lib/system:
Like most standard libraries, the compiler will automatically (and dynamically) link it into your programs even if you don’t specify it explicitly:
When a program is started, the dynamic linker will ensure that libSystem’s initializer functions are called, which includes the function that calls
_libsecinit_initializer . As dyld ensures that libSystem’s initializer is run prior to handing off control to the app’s code, this ensures that any application that links against it will have sandboxing applied to it before it can execute its own code.
Bypassing sandbox initialization
As you may have guessed, this process is problematic. In fact, there are actually multiple issues, each of which allows an application with the
com.apple.security.app-sandbox entitlement to bypass the sandbox initialization process.
dyld interposing
dyld interposing is a neat little feature that allows applications to tell the dynamic linker to “interpose” an exported function and replace it with another by including a special
__DATA,__interpose section in their binary. Since _libsecinit_appsandbox is exported by libsystem_secinit.dylib so that it can be called by libSystem, we can try interposing it with a function that does nothing:
When interposing was first introduced, it would only be applied when a library was preloaded into a process using the
DYLD_INSERT_LIBRARIES environment variable. However, on newer OSes this functionality has been improved to work for any linked libraries as well, which means all we have to do to take advantage of this feature is put this code in a framework and link against it in our main app. Since interposing is applied before image initializers we will be able to prevent the real _libsecinit_initializer from running and thus __sandbox_ms being called. Success!
As this technique allowed an application that appears to be sandboxed (possessing the
com.apple.security.app-sandbox entitlement) to interfere with its own initialization process, I reported this issue to Apple on January 20th and explained that such an app might be able to be submitted to the App Store and get past app review. On March 19th, I received a reply from Apple stating that App Store applications are prevented from being interposed, which was news to me. Apparently right after I submitted my original report Apple added an additional check in dyld, one so new that it’s still not in any public sources:
While the dyld source for
configureProcessRestrictions only shows five flags being read from amfi_check_dyld_policy_self , the binary clearly checks a sixth: 1 << 6 . (configureProcessRestrictions has been inlined here into its caller, dyld::_main .) I still do not know what its real name is but it’s used later in dyld::_main to control whether interposing is allowed. This means we can’t interpose _libsecinit_initializer –we’ll have to prevent it from from being called instead.
Update, 6/10/19
With the code to dyld released, we can see that the flag is called
AMFI_DYLD_OUTPUT_ALLOW_LIBRARY_INTERPOSING . Interestingly, there are some applications that are exempted from the check in AMFI’s macos_dyld_policy_library_interposing , meaning that they are still susceptible to this issue:
Static linking
Linking against libSystem causes dyld to call
_libsecinit_initializer , so it’s logical to try to avoid having anything to do with dyld at all. This is fairly strange to do on macOS, as it does not have a stable syscall interface, but with the right set of compiler flags we can make a fully static binary that needs to no additional support to run.
Fun fact
We know that this must be possible because Go used to do it, until they got tired of new versions of macOS breaking everything and gave up trying to make system calls themselves. How to make an image file from .dmg. Go programs as of Go 1.11 now use libSystem.
Unfortunately, macOS does not ship with a crt0.o that we can statically link, so using just the
-static flag does not work:
But if we’re jettisoning the standard library, we might as well get rid of the C runtime as well, defining our own
start symbol:
No dyld means no code that can arrange a call
_libsecinit_initializer , so we’re free to do whatever we like without restriction. However, not having libSystem and dyld to support us means we cannot use dynamic linking and need to make raw system calls for everything, which is a bit of a pain. One way to resolve this would be to keep the unsandboxed code short–just a couple of calls to acquire a handle on restricted resources–then stash that away before execve ing a new dynamically linked binary, restoring the process to a sane state. When responding to Apple with a new sample program based on this idea, I simply open ed a file descriptor for the home directory (you can locate the directory without any syscalls by pulling the current username from the apple array on the stack during process initialization) and then once that succeeds executed an inner binary. The new file descriptor was preserved for across the execve call and became accessible to the inner application, even though that one was dynamically linked and had the sandbox applied to it as usual.
Dynamically linking against nothing
Statically linking works, but it’s somewhat inconvenient: either you perform the work of the dynamic linker yourself if you want to do anything non-trivial, or you
execve a new binary. It’s actually worse than that though, because there’s an additional complication: executing a new binary causes a hook in the AppleMobileFileIntegrity kernel extension to run, and when System Integrity Protection is enabled this hook (for reasons unknown to me) checks to see if the process has a valid dyld signature:
The strange pointer arithmetic and mask is really a check for
CS_DYLD_PLATFORM , which the comment helpfully states is set if the “dyld used to load this is a platform binary”. Since we didn’t use dyld at all, this isn’t set and we can’t execve . While malicious applications willing to do a bit of work can still “fix” their process without blowing it away, I figured I might as well figure out a way to construct a new one.
Since the hook wants us to have a valid dyld, we should probably just link dynamically. As we mentioned before, this makes the compiler automatically bring in libSystem (and with it, the libsystem_secinit.dylib initializers), which we don’t want. I couldn’t find out a way to get the linker to not automatically insert the load command for libSystem, but we can get essentially the same result by modifying the binary ourselves afterwards to delete that specific command. I found a Mach-O editor online that was slightly crashy but worked well enough for this purpose. Unfortunately, removing the load command isn’t enough: dyld specifically checks for libSystem “glue” before running our code, and as we don’t have a libSystem at all it aborts execution.
However, there’s one way around this: if we use a
LC_UNIXTHREAD rather than a LC_MAIN load command, dyld will pass execution to us without checking for libSystem (as it thinks we have linked against crt1.o instead). Both load commands specify the entrypoint of the executable, but LC_MAIN is the “new” way of doing so. LC_UNIXTHREAD specifies the entire thread state, but LC_MAIN only points to the “entry offset” where code execution should begin–the linker sets this to where main is, unless you’ve used -e to change it. The compiler uses it for dynamically linked binaries because it expects libSystem to set all the thread state prior to calling the entrypoint function.
The linker flag
-no_new_main tells the linker to use LC_UNIXTHREAD instead of LC_MAIN for dynamically linked executables, but it has been silently ignored for years (apparently, this has something to do with rdar://problem/39514191). This means to generate the binary we’ll have to go back in time and download an old toolchain that accepts this flag. The one that Xcode 5.1.1 ships with does nicely.
Fun fact
I have a friend who refers to Xcode 5.1.1 as “the good toolchain” because it supports everything.
Once we use that to create a binary, upon running it we have a valid dyld in our process and unsandboxed code execution so we can just continue as we did in the statically linked case, as this will satisfy AMFI’s checks.
Final thoughts
I submitted the final example to Apple just before the initial 90-day disclosure deadline of April 20th, and when they requested an extension to work on the new information I provided them with an additional 30 days. Apple says it has made changes in the Mac App Store to address this issue during that period, and although I don’t really have a good way to check if or how the change works I would guess that it simply looks for and rejects applications using techniques similar to the ones described above.
![]()
dyld is a fairly complicated system and it has many useful features, but these features along with the fact that it runs in-process makes it nontrivial to protect against control flow subversion early in the initialization process. Applying sandboxing in the kernel itself, as iOS does, is probably a better solution in the long run, as the bugs I found here were fairly straightforwards and exploited logic errors rather than undefined behavior in the language. Perhaps we will see such a change in the future.
The code I submitted to Apple to demonstrate the issue is available online.
Timeline
App Sandbox is an access control technology provided in macOS, enforced at the kernel level. It is designed to contain damage to the system and the user’s data if an app becomes compromised. Apps distributed through the Mac App Store must adopt App Sandbox. Apps signed and distributed outside of the Mac App Store with Developer ID can (and in most cases should) use App Sandbox as well.
At a Glance
Complex systems will always have vulnerabilities, and software complexity only increases over time. No matter how carefully you adopt secure coding practices and guard against bugs, attackers only need to get through your defenses once to succeed. While App Sandbox doesn’t prevent attacks against your app, it does minimize the harm a successful one can cause.
A non-sandboxed app has the full rights of the user who is running that app, and can access any resources that the user can access. If that app or any framework it is linked against contain security holes, an attacker can potentially exploit those holes to take control of that app, and in doing so, the attacker gains the ability to do anything that the user can do.
Designed to mitigate this problem, the App Sandbox strategy is twofold:
App Sandbox is not a silver bullet. Apps can still be compromised, and a compromised app can still do damage. But the scope of potential damage is severely limited when an app is restricted to the minimum set of privileges it needs to get its job done.
App Sandbox is Based on a Few Straightforward Principles
By limiting access to sensitive resources on a per-app basis, App Sandbox provides a last line of defense against the theft, corruption, or deletion of user data, or the hijacking of system hardware, if an attacker successfully exploits security holes in your app. For example, a sandboxed app must explicitly state its intent to use any of the following resources using entitlements:
Access to any resource not explicitly requested in the project definition is rejected by the system at run time. If you are writing a sketch app, for example, and you know your app will never need access to the microphone, you simply don’t ask for access, and the system knows to reject any attempt your (perhaps compromised) app makes to use it.
On the other hand, a sandboxed app has access to the specific resources you request, allows users to expand the sandbox by performing typical actions in the usual way (such as drag and drop), and can automatically perform many additional actions deemed safe, including:
The elements of App Sandbox are entitlements, container directories, user-determined permissions, privilege separation, and kernel enforcement. Working together, these prevent an app from accessing more of the system than is necessary to get its job done.
Relevant chapters:App Sandbox Quick Start, App Sandbox in Depth
Design Your Apps with App Sandbox in Mind
After you understand the basics, look at your app in light of this security technology. First, determine if your app is suitable for sandboxing. (Most apps are.) Then resolve any API incompatibilities and determine which entitlements you need. Finally, consider applying privilege separation to maximize the defensive value of App Sandbox.
Xcode Helps You Migrate an Existing App to App Sandbox
Some file system locations that your app uses are different when you adopt App Sandbox. In particular, you gain a container directory to be used for app support files, databases, caches, and other files apart from user documents. Xcode and macOS support migration of files from their legacy locations to your container.
Relevant chapter:Migrating an App to a Sandbox
Preflight Your App Before Distribution
Download fifa online 2 hack lp fifa. After you have adopted App Sandbox in your app, as a last step each time you distribute it, double check that you are following best practices.
How to Use This Document
To get up and running with App Sandbox, perform the tutorial in App Sandbox Quick Start. Before sandboxing an app you intend to distribute, be sure you understand App Sandbox in Depth. When you’re ready to start sandboxing a new app, or to convert an existing app to adopt App Sandbox, read Designing for App Sandbox. If you’re providing a new, sandboxed version of your app to users already running a version that is not sandboxed, read Migrating an App to a Sandbox. Finally, before distributing your app, work through the App Sandbox Checklist to verify that you are following best practices for App Sandbox.
Mac Run App In SandboxPrerequisites
Before you read this document, make sure you understand the overall macOS development process by reading Mac App Programming Guide.
See Also
To complement the damage containment provided by App Sandbox, you must provide a first line of defense by adopting secure coding practices throughout your app. To learn how, read Security Overview and Secure Coding Guide.
![]() Mac Run App In Sandbox Mode
An important step in adopting App Sandbox is requesting entitlements for your app. For details on all the available entitlements, see Entitlement Key Reference.
You can enhance the benefits of App Sandbox in a full-featured app by implementing privilege separation. You do this using XPC, a macOS implementation of interprocess communication. To learn the details of using XPC, read Daemons and Services Programming Guide.
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-09-13
Comments are closed.
|
AuthorWrite something about yourself. No need to be fancy, just an overview. ArchivesCategories |