Open Bug 1185084 Opened 9 years ago Updated 1 year ago

Interpose library for reverse engineering Apple's sandboxd

Categories

(Core :: Security: Process Sandboxing, defect)

All
macOS
defect

Tracking

()

People

(Reporter: smichaud, Unassigned)

References

Details

(Whiteboard: sb+)

Attachments

(2 files)

I've started reverse engineering Apple's sandboxd (sandbox daemon) and Sandbox kernel extension, in order to better understand how they work.  Neither has any published source code or more than a bare minimum of documentation.

Beyond just getting a better understanding of a poorly documented subsystem, I have the following two goals.  Others may get added in the future.

1) Explore the possibility of receiving notifications of sandbox violations in the browser.

2) Figure out what the various kinds of sandbox rules mean in practical terms.  For example, what kinds of operations are we likely to embugger if we deny access to particular files or particular kinds of mach-lookup?

This bug concerns sandboxd, and the interpose library I've been using to reverse engineer it.  I also plan to write what you could call a SandboxMirror kernel extension to emulate parts of the Sandbox kernel extension's behavior -- goal #2 will require it.  But that will be the subject of another bug.
Here's the interpose library I've been using to reverse engineer parts of sandboxd -- for now the interaction it has with the Sandbox kernel extension when a sandbox violation takes place in some process.

I've used it successfully on OS X 10.9.5 and 10.10.4.  I haven't yet tested on OS X 10.11, and in any case you need to turn off "rootless mode" to use it there.

The component files contain instructions for building and use.  But some additional steps are required because sandboxd is a service/daemon that runs as root.  And additional complications are introduced by the fact that sandboxd is itself sandboxed.

For an ordinary app, you run it from the command line, using DYLD_INSERT_LIBRARIES to load the interpose library, and watch its logging output in Terminal.  Obviously we can't do that with sandboxd.

Here's what I do instead:

1) I've rewritten my interpose library template to stop it writing to STDOUT or STDERR.  Instead it only uses asl_send() to send messages to syslogd (which end up in the system console).  I've also made some additional changes to its logging method (LogWithFormatV()) to avoid violating sandboxd's own sandbox rules.

2) I copy the interpose library (as sandboxd-interpose.dylib) to /usr/libexec (where sandboxd is also located), and use sandboxd's own launchctl plist file (/System/Library/LaunchDaemons/com.apple.sandboxd.plist) to load the interpose library.

3) I sign the interpose library using my Apple Developer signing certificate.  (This last step isn't absolutely necessary.  But all of Apple's own system binaries and signed, so I think it's probably a good idea.  And it may become necessary in the future.)

Adding the following block to com.apple.sandboxd.plist will make launchd load the interpose library before sandboxd:

  <key>EnvironmentVariables</key>
  <dict>
    <key>DYLD_INSERT_LIBRARIES</key>
    <string>/usr/libexec/sandboxd-interpose.dylib</string>
  </dict>
Assignee: nobody → smichaud
Here's a log I made on OS X 10.9.5 of sandboxd starting up and receiving one notification from the Sandbox kernel extension of a sandbox violation (in airportd, as it happens).

Here's a summary of what happens.  (There are more detailed notes in the interpose library's source code.)

First the interpose library creates a "global symbolicator" as sandboxd is loading (in InitSwizzling()), before its own sandbox is initialized.  It gets used by all the subsequent calls to CoreSymbolication methods.  These methods don't work when we create symbolicators on the fly (after sandboxd's sandbox rules have been imposed).

Then it calls __sandbox_ms() to turn on its sandbox rules (via a call to syscall_set_profile() in the Sandbox kernel extension).

Then it calls bootstrap_check_in() to start receiving service requests on a mach port (its "service port").  It uses this "service port" to call dispatch_source_create() to create a "dispatch source" to which Apple's Grand Central Dispatch will pass incoming service requests.  What actually handles these requests is a handler (dispatch_block_invoke()) attached to the "dispatch source" using a call to dispatch_source_set_event_handler().

When the Sandbox kernel extension detects a sandbox violation, it sends a service request to sandboxd containing whatever information sandboxd needs to construct a report to syslogd (which will end up in the system console).  I haven't yet figured out exactly what this information is, but the handling of it begins with a call to my interpose library's dispatch_block_invoke() from the OS (from Grand Central Dispatch).

dispatch_block_invoke() calls dispatch_mig_server(), which performs all the low-level magic needed to receive a mach message from the Sandbox kernel extension.  This method in turn calls a callback (dispatch_mig_callback(), specified in the call to dispatch_mig_server) which extracts the relevant information from the mach message and uses it to contruct the report it will send to syslogd.

dispatch_mig_callback() does some marshaling of the relevant information -- including the symbolication of an execution stack using CSSymbolicatorCreateWithTaskFlagsAndNotification() and other CoreSymbolication methods.  Then it passes the rest of the work off to another handler using a call to dispatch_async().

The dispatch_async() handler creates a "crash report", then passes it to syslogd with a call to asl_create_auxiliary_file().
Work that remains to be done:

1) As I mentioned in comment #2, I haven't yet figured out the exact nature of the information passed in a mach message by the Sandbox kernel extension to sandboxd.

2) I don't yet know what rules are contained in sandboxd's sandbox's ruleset.  The call to __sandbox_ms() passes a binary "profile" -- not a string.  sandboxd's __cstring section doesn't contain any single string that constitutes a sandbox ruleset.  But it does contain several different strings, each specifying a single rule, which might be components of a ruleset.

3) sandboxd can receive other kinds of service requests, but I don't know what they are.  The original handler called from dispatch_mig_callback() has three different codepaths, which it chooses among based on the value of message->msgh_id.  But in all my tests I've only seen one value for msgh_id -- the one in my log.
(Following up comment #2)

Forgot to mention that, at the end of all that, sandboxd calls notify_post("com.apple.sandbox.violation.*").
(Following up comment #2)

Another thing I forgot to mention:

When the Sandbox kernel extension detects a sandbox violation and sends a service request to sandboxd, it finds its "location" (the mach port it's running on) by calling host_get_special_port() on port '14' (0xe).  This is the predefined value of HOST_SEATBELT_PORT.  sandboxd always runs on this port, and that's how the Sandbox kernel extension knows where to find it.

At first I thought (and hoped) it might be possible to register other mach ports with the Sandbox kernel extension, to also receive information there about sandbox violations.  But that's not how the Sandbox kernel extension knows where sandboxd is, so I think this is unlikely.
See Also: → 1186158
See Also: → 1186187
Assignee: smichaud → nobody
Whiteboard: sb+
Severity: normal → S3
You need to log in before you can comment on or make changes to this bug.