Interprocess communication on iOS with Mach messages

In my last article I mentioned that CFMessagePortCreateLocal() was not usable on iOS, quoting the documentation as evidence. However, I should have known better since I had recently experimented (and succeeded, more about this soon) with registering a Mach port name with the Bootstrap Server on iOS by using XPC. And, as pointed out by Ari Weinstein, it looks like one can indeed use CFMessagePortCreateLocal() on iOS!

There are a few things at play here so let’s first review the API documentation.

Documentation deep dive

The documentation for CFMessagePort on the iOS Developer Library states the following:

Special Considerations

This method is not available on iOS 7 and later—it will return NULL and log a sandbox violation in syslog. See Concurrency Programming Guide for possible replacement technologies.

This sounds like a pretty clear statement and it sure feels like one should stay away for this API on iOS even though its header is fully available. So why do I believe this is actually a red-herring?

Well, while this consideration makes sense on iOS 7, things have changed quite a bit since the introduction of iOS 8. Starting with iOS 8, an iOS application looks a lot more like an OS X sandboxed application than it used to. The main reason for this, as previously discussed, are application groups.

There is not a lot of documentation regarding sandboxing rules for iOS but luckily the Mac Developer Library has a bunch. Since the technology behind sandboxing on both platforms is pretty identical we can get a good understanding about how things work on iOS by reading the OS X one.

To step back a little, let’s discuss what being sandboxed means for an application. In short, it means no access to the file system outside of the sandbox, no sharing of ports with other processes, no ingoing or outgoing connection, etc… Obviously, since applications wouldn’t do much if all these rules were enforced, Apple provide some entitlements that can restore targeted capabilities to the sandboxed target.

Among these, the com.apple.security.application-groups entitlement was introduced with 10.8 (well technically with 10.7.4 but things didn’t work too well back then) and iOS 8. As previously discussed, application groups give access to a shared container directory on disk and shared user defaults for applications in the same group.

But the extra capabilities provided by application groups are not limited to the file system. In particular, the section about the Application Group Container Directory in the Mac Sandbox Design Guide also has some very interesting information about IPC:

Note: Applications that are members of an application group also gain the ability to share Mach and POSIX semaphores and to use certain other IPC mechanisms in conjunction with other group members. See IPC and POSIX Semaphores and Shared Memory for more details.

Looking at the IPC and Posix Semaphores and Shared Memory section we learn that:

Normally, sandboxed apps cannot use Mach IPC, POSIX semaphores and shared memory, or UNIX domain sockets (usefully). However, by specifying an entitlement that requests membership in an application group, an app can use these technologies to communicate with other members of that application group.

We’ve already covered the UNIX domain sockets but the Mach IPC statement is definitely intriguing and luckily there’s some good news a few paragraphs below:

Any semaphore or Mach port that you wish to access within a sandboxed app must be named according to a special convention:

  • Mach port names must begin with the application group identifier, followed by a period (.), followed by a name of your choosing.

Boom!

So it sure looks like multiple applications in the same application group can send Mach messages through a Mach port communication channel, assuming the port name is carefully chosen to start with the application group identifier.

Remember that these docs are for OS X but I don’t see any reason why it wouldn’t work on iOS, despite what the CFMessagePort docs have to say.

OK, that was quite a lot of theory. Let’s take a look at the code and see if these assumptions actually hold in practice.

In practice

Let’s try to use CFMessagePortCreateLocal() to create a port with a dynamically created name (i.e. not prefixed by the application group identifier) and confirmed that it fails.

CFDataRef message_callback(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info) {
    return NULL;
}

void create_port(void) {
    CFStringRef port_name = (__bridge CFStringRef)[[NSUUID UUID] UUIDString];
    CFMessagePortRef port = CFMessagePortCreateLocal(kCFAllocatorDefault, port_name, &message_callback, NULL, NULL);
}

Note that you will have to run this on a device since the iOS Simulator doesn’t seem to respect any sandboxing rule.

As expected, the call to CFMessagePortCreateLocal() returns NULL and the following error is printed to the console.

*** CFMessagePort: bootstrap_register(): failed 1100 (0x44c) 'Permission denied', port = 0x450f, name = 'E030B15B-3EC6-4A21-A415-668AD0CB1B52'
See /usr/include/servers/bootstrap_defs.h for the error codes.

(lldb) po port
<nil>

Looking at the bootstrap_defs.h header (which is really just a symbolic link to /usr/include/bootstrap.h) as suggested by the error message, we can see that the 1100 error code is BOOTSTRAP_NOT_PRIVILEGED.

So that’s confirmed, we definitely cannot register random dynamic port name in our sandboxed iOS application.

But if we change the port name to be prefixed with the application group identifier (such as com.ddeville.app.group.port) a port is returned and no error is printed to the console, as previously inferred from the docs.

We did also confirm that a local port can indeed be created on iOS when the port name is prefixed by the application group identifier. However, what does make that prefixed name so special and why does CFMessagePortCreateLocal() fail to create a port for names that do not match this requirement?

In order to figure this out we first need to discuss Mach bootstrap registration.

Mach bootstrap registration

Mach ports are used everywhere on OS X. Due to the fact that Apple treats the Mach layer as the very bottom layer in the kernel – with the BSD layer and other subsequent components being implemented on top of it – many operations on OS X eventually translate to Mach ports being involved down the stack.

Mach ports 101

In my opinion, the Kernel Programming Guide has the clearest documentation about Mach ports.

A port is an endpoint of a unidirectional communication channel between a client who requests a service and a server who provides the service. If a reply is to be provided to such a service request, a second port must be used. This is comparable to a (unidirectional) pipe in UNIX parlance.

and

Tasks have permissions to access ports in certain ways (send, receive, send-once); these are called port rights. A port can be accessed only via a right. Ports are often used to grant clients access to objects within Mach. Having the right to send to the object’s IPC port denotes the right to manipulate the object in prescribed ways. As such, port right ownership is the fundamental security mechanism within Mach. Having a right to an object is to have a capability to access or manipulate that object.

and

Traditionally in Mach, the communication channel denoted by a port was always a queue of messages.

and

Ports and port rights do not have systemwide names that allow arbitrary ports or rights to be manipulated directly. Ports can be manipulated by a task only if the task has a port right in its port namespace. A port right is specified by a port name, an integer index into a 32-bit port namespace. Each task has associated with it a single port namespace.

and finally

Tasks acquire port rights when another task explicitly inserts them into its namespace, when they receive rights in messages, by creating objects that return a right to the object, and via Mach calls for certain special ports (mach_thread_self, mach_task_self, and mach_reply_port.)

(Note: in Mach, a task is basically what you would call a process in the BSD world).

Phew! That’s a lot of information. To summarize, a port is a communication channel that lets multiple tasks send messages to each other, assuming that they have the appropriate right to do so and that the port right, specified by the port name, is available in the task namespace.

In user space, this basically translates to mach_port_t, mach_port_name_t and calls to mach_msg(). Since using Mach ports directly can be pretty hard, Core Foundation (CFMachPort and CFMessagePort) and Cocoa (NSMachPort and NSPortMessage) wrappers were created.

Bootstrap registration

When it is created, a Mach task is given a set of special ports including one called the bootstrap port whose purpose is to send messages to the bootstrap server.

Let’s discuss what is the bootstrap server (for more information about this section – and really about the whole article – make sure to read chapter 9 of Amit Singh’s Mac OS X Internals: A Systems Approach).

The Bootstrap Server

In short, the bootstrap server allows tasks to publish ports that other tasks on the same machine can send messages to. It achieves this by managing a list of name-port bindings. The bootstrap server’s functionality is provided by the bootstrap task, whose program encapsulation nowadays is the launchd program.

The reason why a bootstrap server is necessary is because Mach port namespaces are local to tasks. The bootstrap server allows service names and associated ports to be registered and looked up, across tasks.

Registration

In the pre-launchd days (before Mac OS X 10.4 Tiger), one would register a port name by means of the bootstrap_register() function:

kern_return_t
bootstrap_register(mach_port_t bootstrap_port,
                   name_t      service_name,
                   mach_port_t service_port);

The server side of the connection would thus register a name for the port it will read from. With this call, the bootstrap server would provide send rights for the bound port to the client.

On the client side, the bootstrap_look_up() function can be used to retrieve send rights for the service port of the service specified by the service name. Obviously, the service must have been previously registered under this name by the server.

kern_return_t
bootstrap_look_up(mach_port_t     bootstrap_port,
                  name_service_t  service_name,
                  mach_port_t    *service_port);

The register_service() function in the helper application source for mDNSResponder (Rest In Peace) provides a nice demonstration of this technique.

However, the bootstrap_register() function was deprecated with Mac OS X 10.5 Leopard and Apple now recommends to use launchd instead. I won’t go into the details of this decision here (there was a great discussion about it on the darwin-dev mailing list a while ago) but Apple was essentially trying to encourage a launch-on-demand pattern with launchd and this API just didn’t fit with it.

Since using a launchd service or submitting a job via the ServiceManagement is not always appropriate (or possible), there are Cocoa and Core Foundation APIs that take care of registering the name with the bootstrap server by means of an SPI: bootstrap_register2(). These are NSMachBootstrapServer and CFMessagePort.

Since Core Foundation is open source, one can check the implementation of CFMessagePortCreateLocal() and double check that the port name is indeed being registered. It’s also easy to disassemble -[NSMachBootstrapServer registerPort:name:] and realize that it’s essentially wrapping bootstrap_register2(). Remember that NSMachBootstrapServer is only available on OS X so it’s not actually useful to this discussion but it’s still worth keeping in mind.

The circle is now complete.

CFMessagePortCreateLocal and the magic name prefix

Now that we understand the process of registering the port name with the bootstrap server we can look into why using the application group identifier as a prefix for the port name magically works.

By calling into CFMessagePortCreateLocal() with a random name that doesn’t meet the sandbox criteria and setting a symbolic breakpoint on the function we can step through the instructions and find out where it fails.

We can quickly individuate the point of failure (remember that CFMessagePort.c is open source):

kern_return_t ret = bootstrap_register2(bs, (char *)utfname, mp, perPID ? BOOTSTRAP_PER_PID_SERVICE : 0);
if (ret != KERN_SUCCESS) {
    CFLog(kCFLogLevelDebug, CFSTR("*** CFMessagePort: bootstrap_register(): failed %d (0x%x) '%s', port = 0x%x, name = '%s'\nSee /usr/include/servers/bootstrap_defs.h for the error codes."), ret, ret, bootstrap_strerror(ret), mp, utfname);
    CFMachPortInvalidate(native);
    CFRelease(native);
    CFAllocatorDeallocate(kCFAllocatorSystemDefault, utfname);
    CFRelease(memory);
    return NULL;
}

It looks like bootstrap_register2() itself is failing leading CFMessagePortCreateLocal() to print the error and return NULL.

bootstrap_register2() probably ends up being implemented somewhere between launchd and the kernel so we can take a look at the launchd source to try and figure out why it would fail. launchd was not open sourced as part of 10.10 but the 10.9.5 source will do (remember, the source between iOS and OS X will likely be extremely similar if not identical and application groups were introduced on OS X 10.8).

I was not entirely sure where to look for but job_mig_register2 in core.c looks like a good candidate.

These few lines in particular are definitely interesting:

bool per_pid_service = flags & BOOTSTRAP_PER_PID_SERVICE;
#if HAVE_SANDBOX
    if (unlikely(sandbox_check(ldc->pid, "mach-register", per_pid_service ? SANDBOX_FILTER_LOCAL_NAME : SANDBOX_FILTER_GLOBAL_NAME, servicename) > 0)) {
        return BOOTSTRAP_NOT_PRIVILEGED;
    }
#endif

Once again, I had no idea where that sandbox_check() function was implemented so I poked around the included headers to see if anything jump to my eyes. sandbox.h definitely seemed promising but the version in /usr/include/sandbox.h doesn’t declare the function. After some more poking around /usr and disassembling a few libraries I found the implementation in /usr/lib/system/libsystem_sandbox.dylib!

sandbox_check() is pretty lame and is basically a proxy into sandbox_check_common(). The latter does the actual work of checking whether the process requesting the mach-register action can use the provided service name. We could spend another article going through the disassembly of the function so let’s just assume that it does a few checks based on the entitlements of the process and returns whether the service name is allowed or not. In our case, it’s obvious that the function checks whether the service name is prefixed with the application group identifier retrieved from the process entitlements and denies it if it doesn’t.

It’s worth noting that it would take Apple a simple change to the sandbox check to disallow that API on iOS. However, given how many APIs rely on the current mach-register behavior (XPC being one of them) I don’t see this happen any time soon.

Using Mach messages on iOS

Now that we’ve uncovered the conditions under which the API worked, let’s see how one would use it to do IPC on iOS.

Creating the ports

Similarly as Berkeley sockets, we will have a process acting as the server and another one acting as the client. The server will be in charge of registering the port name by creating a local port while the client will simply connect to it by creating a remote port for the same port name. Ordering is important since the remote port creation will fail if the server hasn’t had a chance to register the name yet.

As shown above, the server would create the port by doing:

CFDataRef callback(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info)
{
    return NULL;
}

void create_port(void)
{
    CFStringRef port_name = CFSTR("com.ddeville.myapp.group.port");
    CFMessagePortRef port = CFMessagePortCreateLocal(kCFAllocatorDefault, port_name, &callback, NULL, NULL);
    CFMessagePortSetDispatchQueue(port, dispatch_get_main_queue());
}

We schedule the message callbacks to happen on the main queue so that we don’t need to setup a runloop source for the callbacks and manually having to run the runloop while waiting for a reply to a message.

Similarly, on the client side we create the remote message port:

void create_port(void)
{
    CFStringRef port_name = CFSTR("com.ddeville.myapp.group.port");
    CFMessagePortRef port = CFMessagePortCreateRemote(kCFAllocatorDefault, port_name));
}

Since the port creation will fail if the server hasn’t registered the local port yet, an appropriate solution would be to retry every few seconds until it succeeds.

Sending messages

It is important to note that the connection is somewhat unidirectional. While the client can send messages to the server, the server can only reply to the messages synchronously when they are received (you have probably noted that the client doesn’t have a way to set up a message callback).

In the simple case (that is where no reply is expected), the client can send a message by doing:

void send_message(void)
{
    SInt32 messageIdentifier = 1;
    CFDataRef messageData = (__bridge CFDataRef)[@"Hello server!" dataUsingEncoding:NSUTF8StringEncoding];
    CFMessagePortSendRequest(port, messageIdentifier, messageData, 1000, 0, NULL, NULL);
}

As you can see, any data can be sent in the message so LLBSDMessaging could be re-implemented on top of Mach messages. The message identifier integer is also a nice API to distinguish between message types.

Upon sending, on the server side, the callback function will be invoked and the message identifier and data passed through. Nice!

Replying to a message

As previously noted, the server can optionally reply to the message by returning some data synchronously in the callback function. For it to work client side, we need to slightly change the way we send the message.

// On the client
void send_message(void)
{
    SInt32 messageIdentifier = 1;
    CFDataRef messageData = (__bridge CFDataRef)[@"Hello server!" dataUsingEncoding:NSUTF8StringEncoding];

    CFDataRef response = NULL;
    SInt32 status = CFMessagePortSendRequest(port, messageIdentifier, messageData, 1000, 1000, kCFRunLoopDefaultMode, &response);
}

// On the server
CFDataRef callback(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info)
{
    return (__bridge CFDataRef)[@"Hello client!" dataUsingEncoding:NSUTF8StringEncoding];
}

Upon return, if no error has happened (you can check the returned status integer) the response reference will point to the data that was sent back by the server.

It’s important to note that CFMessagePortSendRequest() will run the runloop in the specified mode (here kCFRunLoopDefaultMode) thus blocking until the response comes through. We can assume that IPC is pretty fast but the server might still be taking some time to reply. This is where the timeout becomes important: using an appropriate timeout will prevent a thread from being blocked for too long. It’s also probably not a great idea to block the main thread but should you use a background thread remember that it will need to have a serviced runloop ( threads created by a dispatch queue do not have one for example). Another option could be to provide a custom mode on the main thread but be extremely cautious if you need to do this.

Bidirectional communication

As mentioned above, while the server can reply to messages sent by the client, it cannot initiate a new message.

A way to workaround this issue would be to create another pair or ports where the current client act as the registrar. Upon the initial connection from the server, the client would register an additional local port with a new name and send the name to the server. Upon receiving, it would create a remote port matching that name.

This solution is slightly more complicated than the bidirectional-by-nature one provided by Berkeley sockets but it should work as expected. Also, most server-client architectures don’t actually require the server to ever initiate a request since it almost always acts as a response provider.

Conclusion

We were able to register a Mach port with the bootstrap server by creating a service name prefixed by the application group identifier and using CFMessagePortCreateLocal() to take care of the registration. With this set up, it was trivial to send messages between applications.

It wouldn’t be too hard to rewrite LLBSDConnection on top of Mach ports rather than Berkeley sockets. It would also likely be slightly faster. But I guess this post was already long enough so we’ll leave this for another time :-)

Interprocess communication on iOS with Berkeley sockets

With iOS 8, Apple introduced App Extensions. App Extensions are self-contained apps that developers can ship along with their main application. They can be launched on demand by the system to perform things such as sharing, editing a photo, displaying a widget in the Notification Center, presenting a custom keyboard or even provide content to an app running on the Apple Watch.

In order to allow an extension and its hosting application to share data and resources, application groups were also introduced (It’s worth noting that they were originally introduced on OS X in 10.7.4 to support sharing data between an application and a Login Item application shipped within its bundle).

An application group is an identifier shared between various applications in the same group (it can be enabled through an entitlement). Applications in the same group can share a group container directory that is located outside of their own sandbox but accessible by all applications in the group. Think of it as another sandbox shared between applications. An application and its extensions can share this group container but so can multiple applications released by the same developer (assuming they have all specified the same identifier for the com.apple.security.application-groups entitlement).

While it might not seem much at first, this means that applications belonging to the same group can now:

  • Share a directory on the file system. Multiple applications can read and write files in this directory (but also making sure that access is coordinated, more about this later) and could for example share a Core Data store or a sqlite database.
  • Share preferences. When creating an instance of NSUserDefaults with a suite name equal to the application group identifier, cfprefsd will store the preferences plist in the group container directory and all applications in the group will be able to share its content (A couple of years ago I built shared user defaults on the Mac before the application group suite was made available, it was fun).

While this is pretty cool and is a big step forward (previously, multiple apps by the same developer could only share keychain entries) there are times where you wish you could just send regular messages between applications without having to store them somewhere on the file system. Also, while NSUserDefaults, Core Data or sqlite support concurrent access neither actually notify other processes when one makes some changes to the shared data. What this means is that you could be inserting an object in one process but the other process wouldn’t be aware of the change until it attempts fetching the data again.

If we had an IPC mechanism to communicate between applications in the same group we could make sure that other processes are notified of changes in real-time without the need for polling.

This article will describe a complete and general solution to this problem on iOS. However, before diving into it I’d like to briefly discuss the IPC situation on OS X. If you can’t wait and just want to see the code, you can find the project on GitHub.

Current state of IPC on OS X

Mike Ash did an excellent job discussing the state of IPC on the Mac a few years ago so I will not repeat everything but will rather recommend that you read his post (it’s from 2009 but apart from XPC not yet being a thing it’s still a very good overview).

As you probably already know, both OS X and iOS are built on top of Darwin. Darwin itself is built around XNU, a hybrid kernel that combines the Mach 3 microkernel and various elements of BSD, itself a Unix derivative. The dual nature of XNU means that many features of both Mach and Unix are available on Darwin, including several IPC mechanisms.

Mach ports

Mach ports are the fundamental IPC mechanism on Mach. Using them directly is hard but there are Core Foundation (CFMachPort) and Foundation (NSMachPort) wrappers available that make things slightly easier. You rarely use a Mach port directly but assuming you have a sending and receiving ports available you should be able to construct a NSPortMessage and send it over.

While creating a local port is just a matter of initializing a new NSMachPort instance, retrieving the remote one requires the Mach bootstrap server. On OS X this usually means having the server side of the connection registering the port for the name with the shared NSMachBootstrapServer such as:

NSMachPort *port = [NSMachPort port];
NSString *name = @"com.ddeville.myapp.myport";
[[NSMachBootstrapServer sharedInstance] registerPort:port name:name];

On the client side of the connection, one could retrieve the remote port by doing:

NSString *name = @"com.ddeville.myapp.myport";
NSMachPort *port = [[NSMachBootstrapServer sharedInstance] portForName:name];

Alternatively, one could use CFMessagePortCreateLocal that registers the service with the bootstrap server under the cover and CFMessagePortCreateRemote that uses the bootstrap server to look up a registered service by name.

Note that if the application is sandboxed, the system will not let you register service names that aren’t starting with the application group identifier. When creating a Login Item application, LaunchServices implicitly registers a mach service for the login item whose name is the name as the login item’s bundle identifier. See the App Sandbox Design Guide and the iDecide sample project for more info.

Since working directly with Mach messages can be cumbersome, Distributed Objects, Distributed Notifications and Apple Events were built on top of them and simplify things a lot. I won’t discuss these here so go read Mike Ash’s post and the Distributed Objects Architecture if you want more info.

It’s also worth noting that with OS X 10.7 Lion, Apple shipped a revolutionary API built on top of Mach messages and libdispatch: XPC. As per its man page, XPC is “a structured, asynchronous interprocess communication library”. With 10.8, a new NSXPCConnection class was also released. NSXPCConnection brings back the Distributed Objects idea with an XPC flavor by letting you send messages to a remote proxy object through an XPC connection. Since the introduction of XPC, using Mach ports or one of its derivative is essentially unnecessary. However, it’s important to keep in mind that service name bootstrapping is still required with XPC. In particular, the docs for xpc_connection_create_mach_service clearly state that “the service name must exist in a Mach bootstrap that is accessible to the process and be advertised in a launchd.plist”.

POSIX file descriptors

By being a BSD derivative Darwin has all the Unix IPC goodies, in particular Berkeley sockets a.k.a. Unix Domain Sockets. Since these will be the core part of the article I’ll save their discussion for a later section.

Current state of IPC on iOS

As you’re probably aware, a big part of App Extensions on iOS is built around XPC. An XPC connection is set up between the hosting application and the extension in order to communicate. Most of the methods in NSExtensionRequestHandling and NSExtensionContext end up sending some message to the host application through an XPC connection.

However, XPC is also private on iOS and third-party developers cannot use it directly (yet). Similarly, Distributed Objects, Distributed Notifications and Apple Events are not available on the platform.

Regarding using Mach ports directly, while the Mach port API is indeed available on iOS, NSMachBootstrapServer isn’t and creating a remote port with CFMessagePortCreateLocal will return NULL and log a sandbox violation to the console.

[Update: This was proven incorrect. See this follow-up post for more info.]

<notify.h>

OS X has had support for the <notify.h> Core OS notification mechanism since 10.3. Similarly, iOS has had support for these mechanism since its inception. <notify.h> allows processes to exchange stateless notification events. As explained in the <notify.h> header:

These routines allow processes to exchange stateless notification events. Processes post notifications to a single system-wide notification server, which then distributes notifications to client processes that have registered to receive those notifications, including processes run by other users.

Notifications are associated with names in a namespace shared by all clients of the system. Clients may post notifications for names, and may monitor names for posted notifications. Clients may request notification delivery by a number of different methods.

In a nutshell, processes can post and receive system-wide notifications with the notifyd daemon acting as the server by receiving and broadcasting notifications. Observers can be registered on a dispatch queue, a signal, a mach port or a file descriptor!

A simple example would be registering for lock notifications on iOS:

#import <notify.h>

const char *notification_name = "com.apple.springboard.lockcomplete";
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
int registration_token;
notify_register_dispatch(notification_name, &registration_token, queue, ^ (int token) {
    // do something now that the device is locked
});

Pretty straightforward.

CFNotificationCenterGetDarwinNotifyCenter

Core Foundation offers a notification center based on <notify.h>: CFNotificationCenterGetDarwinNotifyCenter. By retrieving this notification center, one can post and observe system-level notifications that originate in <notify.h>.

static void notificationCallback(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
{
    // do something now that the device is locked
}

CFStringRef notificationName = CFSTR("com.apple.springboard.lockcomplete");
CFNotificationCenterRef notificationCenter = CFNotificationCenterGetDarwinNotifyCenter();
CFNotificationCenterAddObserver(notificationCenter, NULL, notificationCallback, notificationName, NULL, CFNotificationSuspensionBehaviorDeliverImmediately);

As you can see, this is actually more code since it requires adding a callback function. So, unless you need the behavior provided by CFNotificationSuspensionBehavior, I’d say stick with <notify.h> if you want to post or receive these notifications.

It’s also important noting that the userInfo parameter of the notification is not supported for the Darwin notify center. What this means is that even if you specify a userInfo dictionary when sending the notification from one process, it will not be available on the receiving side (Notification Center simply ignores it).

This drastically reduces the appeal of this method and essentially restricts its usage to simple stateless system-wide notifications – as it was designed for.

One use case I can think of is to reduce polling. You could for example post a notification whenever one process changes a user default so that other process can fetch the new value without having to constantly check whether the value has changed. However, this method is racy by nature since it requires the receiver to fetch the value after the notification was received and its value could have changed or even be reverted. Ideally the new value would be sent with the notification but as we’ve seen the API prevents this from happening.

NSFilePresenter

I previously discussed NSFilePresenter and NSFileCoordinator extensively in this article. In a few words, NSFileCoordinator class coordinates the reading and writing of files and directories among multiple processes and objects in the same process. Similarly, by adopting the NSFilePresenter protocol, an object can be notified whenever the file is changed on disk by another process.

I have used this method to implement messaging between process on OS X and people have been attempting the same on iOS.

Unfortunately, with TN2408 Apple made it clear that using file coordination to coordinate reads and writes between an app extension and its containing app is a very bad idea:

Important: When you create a shared container for use by an app extension and its containing app in iOS 8, you are obliged to write to that container in a coordinated manner to avoid data corruption. However, you must not use file coordination APIs directly for this. If you use file coordination APIs directly to access a shared container from an extension in iOS 8.0, there are certain circumstances under which the file coordination machinery deadlocks.

I suspect file coordination is not handling the case where the app extension is killed by the system while the containing app is blocked waiting to read or write. This will likely be fixed soon but for now this means using this API is not an option.

Unix Domain Sockets

As previously stated, Darwin has its foundations in BSD. BSD being a Unix derivative, Darwin inherited many Unix goodies, one of them being Berkeley sockets, also known as Unix domain sockets or BSD sockets.

You might be familiar with the “Everything in Unix is a file” adage. Well, turns out it’s quite true, in Unix pretty much everything is a file descriptor!

As you probably remember, we previously said that an application group provided a shared container directory to multiple applications in the same group. What if we could use a file to send messages between processes without having to deal with file coordination? Well, turns out a socket is a file and sockets are a pretty good channel for sending and receiving messages. As a side note, Apple wrote a nice section in favor of Unix Domain Sockets vs Mach Messages in TN2083.

In the next section we will discuss how to build a general solution for interprocess communication on iOS based on Berkeley sockets.

IPC on iOS based on Berkeley sockets

In order to build a general solution for IPC, we need to narrow down some requirements. I have used XPC as a model so the feature set I’m aiming at will be a subset of the one offered by XPC. Without further ado, here’s the list of requirements for our solution:

  1. A communication channel
  2. A client-server connection architecture
  3. Non-blocking communication (i.e. Asynchronous I/O)
  4. Message framing for data that is sent through the channel
  5. Support for complex data to be sent through the channel
  6. Secure encoding and decoding of data on each end of the channel
  7. Error and invalidation handling

Let’s now discuss each requirement individually and let’s try to figure out a way to solve each of them.

A communication channel

By creating a stream socket of Unix local type at a given path in the application group container directory we can support multiple processes connecting and sending data to each other. By specifying a unique name for the socket we can even support multiple simultaneous connections. It is however important for the client and server to agree on the name so that they can find each other.

Creating such a socket is as simple as:

dispatch_fd_t fd = socket(AF_UNIX, SOCK_STREAM, 0);

AF_UNIX creates a Unix domain socket (vs an Internet socket for example) and SOCK_STREAM makes the socket a stream socket (other options could be datagram or raw).

Note that creating a socket doesn’t even require a path to be specified. The path is usually specified later so we’ll discuss it in the next section. For now, we can just remember that calling socket returns a file descriptor.

A client-server connection architecture

For our hosts to communicate with each other we need a particular architecture where we can make sure that multiple hosts can connect to each other. In order to achieve this, we use a client-server architecture where one host acts as the server by creating the socket on disk and waiting for connections and the other host(s) act as clients by connecting to the server.

In socket parlance, the server binds to the socket and listens for connections while the client connects. For the client to fully connect, the server has to accept the connection. Let’s see how that translates to code.

We can construct the socket path by appending a unique identifier to the path of the application group container directory that all applications in the group have read/write access to. As long as both server and clients agree on the unique identifier, we now have a channel through which they can communicate.

First, on the server side we would start the connection by doing (error handling removed for brevity):

const char *socket_path = ...

dispatch_fd_t fd = socket(AF_UNIX, SOCK_STREAM, 0);

struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;

unlink(socket_path);
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);

bind(fd, (struct sockaddr *)&addr, sizeof(addr));

listen(fd, kLLBSDServerConnectionsBacklog);

Similarly, on the client side we would connect to the server with:

const char *socket_path = ...

dispatch_fd_t fd = socket(AF_UNIX, SOCK_STREAM, 0);

struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;

strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);

connect(fd, (struct sockaddr *)&addr, sizeof(addr));

A couple notes. On the server, we call unlink() before binding to the socket. This is to make sure that a previous socket that was not correctly cleaned up is correctly removed before creating a new one. Also, note that connect() will fail if the server is not listening for connection. Usually, one should make sure that the server is running before attempting to connect a client (ideally, the server runs all the time, like a daemon process for example). Given that applications are short-lived on iOS, a typical scenario would involve attempting to connect the client multiple times until it succeeds.

Note that whereas the client has already called connect() and returned, the server hasn’t yet accepted the connection. Since this could potentially involve a blocking call we will discuss this in the next section.

Since accepting a new client is really a decision that the server application should make, the connection also has a -server:shouldAcceptNewConnection:; delegate method that the application can implement in order to decide whether a client should connect. It is also the perfect opportunity for the server to keep track of which client have connected.

Since there could potentially be multiple client connected to the server, sending a message from the server comes in two flavors:

  • Broadcasting a message. This will send the message to every connected client.
  • Sending a message to a particular client, assuming that the application knows its identity.

Non-blocking communication (Asynchronous I/O)

In order to accept the client connection, the server needs to call accept(). By default, if no pending connections are present on the queue, accept() blocks the caller until a connection is present. Since one of our requirement is non-blocking communication this is clearly not acceptable. Luckily, we can use the O_NONBLOCK property and a dispatch source to solve the problem. Since we can retrieve the pending connection by issuing a read we can set up a dispatch source for DISPATCH_SOURCE_TYPE_READ:

dispatch_source_t listeningSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, NULL);
dispatch_source_set_event_handler(listeningSource, ^ {
    struct sockaddr client_addr;
    socklen_t client_addrlen = sizeof(client_addr);
    dispatch_fd_t client_fd = accept(self.fd, &client_addr, &client_addrlen);
});
dispatch_resume(listeningSource);

Now that we have both server and client correctly connected, we need a way to send and receive messages. Like with any other socket, this can be achieved by mean of read() and write(). Remember that these two calls are blocking by default so not acceptable for our solutions. Luckily both also support a non-blocking variant and dispatch IO provides a very nice API to deal with such reads and writes asynchronously.

We can first create a dispatch IO channel with the following:

dispatch_io_t channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, NULL, ^ (int error) {});
dispatch_io_set_low_water(channel, 1);
dispatch_io_set_high_water(channel, SIZE_MAX);

To read on the channel asynchronously:

dispatch_io_read(channel, 0, SIZE_MAX, NULL, ^ (bool done, dispatch_data_t data, int error) {
    if (error) {
        return;
    }
    // read data
    if (done) {
        // cleanup
    }
});

And to write on the channel asynchronously:

dispatch_data_t message_data = ...
dispatch_io_write(self.channel, 0, message_data, NULL, ^ (bool done, dispatch_data_t data, int write_error) {
    // check for errors
});

Message framing for data that is sent through the channel

As seen in the previous section, we can now read and write data asynchronously on the connection. However, this data is raw bytes and we have no idea where it starts and ends (remember, we are using a stream socket so the data arrives in order but it also arrives broken into pieces).

We need some mechanism in which we could wrap our raw data and that would make it easy to know where it starts and ends. This is known as message framing.

We could implement a new format that has some sort of delimiters for the start and end of a message. It would also probably have some kind of a header that lets one specify the content length of the message as a whole, the encoding to expect, etc… Finally, our framing should support binary data and not simply text since we want to be able to send anything through our connection.

As you can imagine, this is a somewhat solved problem. Many message framing “formats” have been created along the years and HTTP is definitely the most well-known and maybe most widely used. Also, HTTP support binary data (one misconception about HTTP is that it only supports a text body. This is actually not true: the HTTP headers have to be text but the body itself can be any binary data). Since CFNetwork has a very support for HTTP we’ll just use it to frame our messages.

Given a data object to send through the connection, we can create an HTTP message for it.

NSData *contentData = ...
CFHTTPMessageRef response = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 200, NULL, kCFHTTPVersion1_1);
CFHTTPMessageSetHeaderFieldValue(response, (__bridge CFStringRef)@"Content-Length", (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", (unsigned long)[contentData length]]);
CFHTTPMessageSetBody(response, (__bridge CFDataRef)contentData);

NSData *messageData = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(response));
CFRelease(response);

As you can see, this is actually pretty simple. Given a raw NSData we can create an HTTP message by setting the raw data as the body and specifying a Content-Length header of the size of the data. We can then serialize the HTTP message as an NSData instance to send through the connection.

On the other end, we can do a similar thing, by creating a new HTTP message and appending bytes as they come in through the connection. Once the number of bytes is equal to the one we’re expecting from the Content-Length header, we can get our original data by looking at the message body.

CFHTTPMessageRef framedMessage CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false);

while (... get more bytes ...) {
    NSData *data = ...
    CFHTTPMessageAppendBytes(framedMessage, data.bytes, (CFIndex)data.length);

    if (!CFHTTPMessageIsHeaderComplete(framedMessage)) {
        continue;
    }

    NSInteger contentLength = [CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(framedMessage, CFSTR("Content-Length"))) integerValue];
    NSInteger bodyLength = (NSInteger)[CFBridgingRelease(CFHTTPMessageCopyBody(framedMessage)) length];
    if (contentLength != bodyLength) {
        continue;
    }

    NSData *rawData = CFBridgingRelease(CFHTTPMessageCopyBody(message));
}

This is indeed very similar. There’s a one little gotcha to be aware of: since we are receiving the HTTP message in chunks, the header might not actually be complete after receiving the first few bytes. Luckily CFNetwork has a handy CFHTTPMessageIsHeaderComplete function that we can use to check for it. Once the header is complete, for each chunk of data that we receive we can check whether the body length matches the expected Content-Length and just retrieve the body data once it’s been fully received.

Support for complex data to be sent through the channel

As discussed previously, one of the major drawbacks of the current solutions is the lack of support for complex data to be sent through the channel. <notify.h> for example only lets us send a message name.

Since our solution supports sending an NSData instance through the channel we can use an encoding and decoding mechanism to transform data <-> objects on each side of the connection.

Once again, luckily Foundation has very good support for this through NSKeyedArchiver and NSKeyedUnarchiver. By encoding the object graph on one end and decoding on the other end we can give the appearance that real objects are being sent through the connection. As far as the library user is concerned, a message containing objective-c objects is being sent and objective-c objects are similarly being received on the other side. Magical!

One requirement for this to work is that the object has to conform to the NSCoding protocol. Most of the Foundation classes already do and it’s pretty easy to implement for one’s custom classes. Obviously, when using custom classes one must make sure that such class is available on both end of the connection.

Secure encoding and decoding of data on each end of the channel

One concern with encoding and decoding random objects between processes is security. Not only do we have to make sure that the class is available on the client and the server for it to be decoded but we also have to make sure that the right class is being decoded and not one pretending to be.

Along NSXPCConnection in OS X 10.8, Apple introduced NSSecureCoding. This protocol extends NSCoding and by adopting NSSecureCoding an object indicates that it handles encoding and decoding instances of itself in a manner that is robust against object substitution attacks.

As per the NSSecureCoding header:

NSSecureCoding guarantees only that an archive contains the classes it claims. It makes no guarantees about the suitability for consumption by the receiver of the decoded content of the archive. Archived objects which may trigger code evaluation should be validated independently by the consumer of the objects to verify that no malicious code is executed (i.e. by checking key paths, selectors etc. specified in the archive).

By requiring the messaged objects to conform to NSSecureCoding and by providing a whitelist of classes to the connection, we can make sure that our messages are encoded and decoded in a secure fashion.

id content = ...
NSMutableData *contentData = [NSMutableData data];

NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:contentData];
archiver.requiresSecureCoding = YES;

@try {
    [archiver encodeObject:content forKey:NSKeyedArchiveRootObjectKey];
}
@catch (NSException *exception) {
    if ([exception.name isEqualToString:NSInvalidUnarchiveOperationException]) {
        return;
    }
    @throw exception;
}
@finally {
    [archiver finishEncoding];
}
NSSet *allowedClasses = ...
NSData *contentData = ...

NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:contentData];
unarchiver.requiresSecureCoding = YES;

NSSet *classes = [NSSet setWithObjects:[NSDictionary class], [NSString class], [NSNumber class], [LLBSDProcessInfo class], nil];
classes = [classes setByAddingObjectsFromSet:allowedClasses];

@try {
    content = [unarchiver decodeObjectOfClasses:classes forKey:NSKeyedArchiveRootObjectKey];
}
@catch (NSException *exception) {
    if ([exception.name isEqualToString:NSInvalidUnarchiveOperationException]) {
        return;
    }
    @throw exception;
}
@finally {
    [unarchiver finishDecoding];
}

Error and invalidation handling

In an environment involving multiple processes that could start and die at any time, we cannot assume that errors and connection invalidation will never happen.

Luckily, as stated in TN2083, since Berkeley sockets are a connection-oriented API the server automatically learns about the death of a client and it’s easy for the server to asynchronously notify the client.

We can thus easily implement an invalidation handler where the server or client can be notified whenever the connection becomes invalid.

Similarly, sending a message through the connection can take a completion handler that can pass a possible error that occurred while sending the message.

Conclusion

We were able to build a full-fledged IPC mechanism to communicate between applications and extensions within an application group on iOS. Our solution is based on powerful yet simple technologies such as Berkeley sockets, HTTP message framing and libdispatch.

You can find LLBSDMessaging on GitHub. It is built as a framework so that you can easily integrate it in an existing project.

Announcing Spillo

I’ve started using Pinboard a couple of years ago and have soon become a real fan of the website. It does everything I need: storing and organizing links I want to remember in the future, without all the social madness that services seem to be crazy about these days. I also admire Maciej’s business model and Pinboard is one the few services that I’m confident with using for the years to come.

While the website is very minimal it lets you find your stuff in a very efficient manner. The website’s design has practically not changed since its inception and it has actually been for the best. Pinboard does one thing and it does it well!

However, having been in love with the Mac for a long time I was starting to miss having a strong Mac client to create and manage my bookmarks, in an efficient manner that only a native application can provide.

So a couple of months ago, I started to work on a side project that I’m proud to announce today: Spillo for Mac!

Spillo

In a nutshell, Spillo is a powerful, beautiful and amazingly fast Pinboard client for OS X. Spillo lets you browse and organize your bookmarks in a stunning modern interface. Spillo also makes creating a bookmark from anywhere on your Mac as convenient as possible.

Spillo is available today for $9.99 on the Mac App Store or directly from our website.

If you would like to give Spillo a try, a 14-day demo is available on the Bananafish Software website.

I hope you will enjoy using Spillo as much as I do!

Dynamic linking on iOS

This is a repost of an article I published on the Realmac Software blog.

iOS is often criticized for not supporting dynamic libraries. While you might agree or disagree, it is interesting to think about why it is so and how this rule is eventually enforced. In this post we will see what these libraries are, how they come in practice, how they would work if they were fully supported on iOS and what makes it impossible to ship one in your iOS application.

Library linking

Applications are very rarely built as one big monolithic executable but are rather constructed by assembling various blocks usually called libraries. From a practical perspective, a library can be thought as an agglomerate of executable code along with some public headers and resources conveniently packaged to be linked into and consumed by an application.

While this general definition fits for most library types, there is one area where they diverge: linking. Based on this distinction, there are two families of libraries: static and dynamic libraries. I will quickly summarise the differences between the two, however, if you want to know more about the topic, I recommend reading the Dynamic Library Programming Topics guide on Apple’s website.

Static library

A static library can be thought of as an archive of object files. When linking such a library into an application, the static linker will collect the object files from the library and package them along with the application object code into one single executable file. This means that the size of the application executable file will grow with the number of libraries added. Also, when the application launches, the application code (which includes the library code) will be loaded all at once into the program’s address space.

Dynamic library

Dynamic libraries allow an application to load code into its address space when it’s actually needed. This can be either at launch time or at runtime. Dynamic libraries are not part of the application executable file.

When an application is launched, the application’s code is first loaded into the address space of the process. The dynamic loader – usually dyld on Apple’s platforms – takes control of the process and loads the dependent libraries. This involves resolving their location on the file system based on their install name, and then resolving the undefined external symbols needed by the application. The dynamic loader will also load additional libraries at runtime, as they are requested.

Framework

In the Apple world, a Framework is a bundle or package containing a dynamic library, header files and resources. Frameworks are a very neat way to group related resources together, provide an executable along with the public headers in one package that is easy to install.

It is important to note that while a framework would need to contain a dynamic library, one could quite easily create a static framework for iOS. I won’t go into details about this here but I highly recommend reading “iPhone Framework Support - Shipping Libraries” and “iOS Static Libraries Are, Like, Really Bad, And Stuff” by Landon Fuller.

Dynamic libraries on iOS

So, is it true that dynamic libraries cannot be used on iOS? Well, it is actually a bit of a misconception. Every Apple framework that you link into your application contains a dynamic shared library. Can you imagine what the size of the executable would be if you had to statically link UIKit and other frameworks into every single application? As a matter of fact, dynamic libraries are wildly used on iOS. When your code in applicationDidFinishLaunching: starts executing, dyld has already loaded over 150 libraries!

It would be neat if we could find out which libraries are being loaded while the application is running. Luckily dyld offers some hooks for an application to be notified when an image is added or removed. Let’s create a LLImageLogger class and set some callback functions when the class loads. Let’s go through the implementation of this class. You can find the code on GitHub if you want to run it; it comes with a couple of iOS and OS X sample applications.

Logging loaded dynamic libraries

mach-o/dyld.h declares two very useful functions: _dyld_register_func_for_add_image and _dyld_register_func_for_remove_image. These functions are documented as follows:

The following functions allow you to install callbacks which will be called by dyld whenever an image is loaded or unloaded. During a call to _dyld_register_func_for_add_image() the callback func is called for every existing image. Later, it is called as each new image is loaded and bound (but initializers not yet run). The callback registered with _dyld_register_func_for_remove_image() is called after any terminators in an image are run and before the image is un-memory-mapped.

We can easily add a couple of callbacks when our class loads.

#import <mach-o/dyld.h>

@implementation LLImageLogger

+ (void)load
{
	_dyld_register_func_for_add_image(&image_added);
	_dyld_register_func_for_remove_image(&image_removed);
}
@end

We now need to implement both functions. Note that the signature of the callback function is as follows:

void callback_function(const struct mach_header *mh, intptr_t vmaddr_slide);

Ideally, we will want to log some info about the loaded image to the console. The most familiar way will be to try and mimic the format of a crash report. A crash report usually has a list of images that were loaded when the program crashed. The information for each image contains the executable path, the base address, the executable text section size (or the end address) and the image UUID. This information is very useful when it comes to symbolicate a crash report.

0x2fd23000 - 0x2ff0dfff Foundation armv7s  <b75ca4f9d9b739ef9b16e482db277849> /System/Library/Frameworks/Foundation.framework/Foundation

0x31c2c000 - 0x3239ffff UIKit armv7s  <f725ad0982673286911bff834295ec99> /System/Library/Frameworks/UIKit.framework/UIKit

Since the callback functions are passed a pointer to the Mach-O header as first argument it should be fairly easy to retrieve this info. Let’s implement both callback functions and simply call into a common function with an extra argument determining whether the image was added or removed.

#import <mach-o/loader.h>

static void image_added(const struct mach_header *mh, intptr_t slide)
{
	_print_image(mh, true);
}

static void image_removed(const struct mach_header *mh, intptr_t slide)
{
	_print_image(mh, false);
}

We will now look at implementing the _print_image function. Most of the info about the Mach-O header can be retrieved by means of a function defined in dlfcn.h: dladdr. By passing the pointer to the Mach-O header and a reference to a Dl_info struct we can retrieve some key information about the image. The Dl_info struct contains the following members:

typedef struct dl_info {
	const char  *dli_fname;     /* Pathname of shared object */
	void        *dli_fbase;     /* Base address of shared object */
	const char  *dli_sname;     /* Name of nearest symbol */
	void        *dli_saddr;     /* Address of nearest symbol */
} Dl_info;

Keeping this in mind, we can now take a look at the implementation of the _print_image function:

#import <dlfcn.h>

static void _print_image(const struct mach_header *mh, bool added)
{
	Dl_info image_info;
	int result = dladdr(mh, &image_info);

	if (result == 0) {
		printf("Could not print info for mach_header: %p\n\n", mh);
		return;
	}

	const char *image_name = image_info.dli_fname;

	const intptr_t image_base_address = (intptr_t)image_info.dli_fbase;
	const uint64_t image_text_size = _image_text_segment_size(mh);

	char image_uuid[37];
	const uuid_t *image_uuid_bytes = _image_retrieve_uuid(mh);
	uuid_unparse(*image_uuid_bytes, image_uuid);

	const char *log = added ? "Added" : "Removed";
	printf("%s: 0x%02lx (0x%02llx) %s <%s>\n\n", log, image_base_address, image_text_size, image_name, image_uuid);
}

As you can see, it’s nothing too fancy. We start by retrieving the Dl_info struct for the Mach-O header and then populate the required information. While the base address and image path are directly available in the struct, we will have to manually retrieve the image text segment size and the image UUID from the binary. This is exactly what the _image_retrieve_uuid and _image_text_segment_size do.

For these two functions, we will have to walk the Mach-O file load commands. I recommend reading the OS X ABI Mach-O File Format Reference from Apple for a good overview of the Mach-O file format. In a nutshell, a Mach-O file is composed of a header, a series of load commands and data composed of multiple segments. Information about the segments (such as their offset and size) is available in the segment load commands.

Mach-O file format

We will start with creating a visitor function that we’ll be able to reuse across both functions.

static uint32_t _image_header_size(const struct mach_header *mh)
{
	bool is_header_64_bit = (mh->magic == MH_MAGIC_64 || mh->magic == MH_CIGAM_64);
	return (is_header_64_bit ? sizeof(struct mach_header_64) : sizeof(struct mach_header));
}

static void _image_visit_load_commands(const struct mach_header *mh, void (^visitor)(struct load_command *lc, bool *stop))
{
	assert(visitor != NULL);

	uintptr_t lc_cursor = (uintptr_t)mh + _image_header_size(mh);

	for (uint32_t idx = 0; idx < mh->ncmds; idx++) {
		struct load_command *lc = (struct load_command *)lc_cursor;

		bool stop = false;
		visitor(lc, &stop);

		if (stop) {
			return;
		}

		lc_cursor += lc->cmdsize;
	}
}

This functions takes a pointer to a Mach-O header and a visitor block. It then invokes the block for each load command it finds. Note the helper function to retrieve the size of the Mach-O header which we’ll need in order to find the address of the first load command. This is due to the fact that there are two different structs for the Mach-O header: mach_header and mach_header_64, based on whether the architecture is 64-bit or not. Luckily the first field in the header struct is a magic number giving information about the architecture.

With this helper function written we should be able to implement the _image_retrieve_uuid and _image_text_segment_size functions.

static const uuid_t *_image_retrieve_uuid(const struct mach_header *mh)
{
	__block const struct uuid_command *uuid_cmd = NULL;

	_image_visit_load_commands(mh, ^ (struct load_command *lc, bool *stop) {
		if (lc->cmdsize == 0) {
			return;
		}
		if (lc->cmd == LC_UUID) {
			uuid_cmd = (const struct uuid_command *)lc;
			*stop = true;
		}
	});

	if (uuid_cmd == NULL) {
		return NULL;
	}

	return &uuid_cmd->uuid;
}

This function is very simple too. It looks for the LC_UUID command and retrieves the uuid_t once it finds it. The _print_image will then take care of transforming the uuid_t into a string by mean of uuid_unparse.

Finally, here is the implementation of the _image_text_segment_size function:

static uint64_t _image_text_segment_size(const struct mach_header *mh)
{
	static const char *text_segment_name = "__TEXT";

	__block uint64_t text_size = 0;

	_image_visit_load_commands(mh, ^ (struct load_command *lc, bool *stop) {
		if (lc->cmdsize == 0) {
			return;
		}
		if (lc->cmd == LC_SEGMENT) {
			struct segment_command *seg_cmd = (struct segment_command *)lc;
			if (strcmp(seg_cmd->segname, text_segment_name) == 0) {
				text_size = seg_cmd->vmsize;
				*stop = true;
				return;
			}
		}
		if (lc->cmd == LC_SEGMENT_64) {
			struct segment_command_64 *seg_cmd = (struct segment_command_64 *)lc;
			if (strcmp(seg_cmd->segname, text_segment_name) == 0) {
				text_size = seg_cmd->vmsize;
				*stop = true;
				return;
			}
		}
	});

	return text_size;
}

Nothing too fancy here either. The visitor block simply looks for segment commands (LC_SEGMENT on 32-bit and LC_SEGMENT_64 on 64-bit) and checks whether the current load segment is the __TEXT segment. If it is, it then retrieves the vmsize and returns it as the text size.

By running the application on the iOS Simulator, the following is logged:

Added: 0x10000b000 (0x2a8000) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.1.sdk/System/Library/Frameworks/Foundation.framework/Foundation <C299A741-488A-3656-A410-A7BE59926B13>

Added: 0x110527000 (0x385000) /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.1.sdk/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox <57B61C9C-8767-3B3A-BBB5-8768A682383A>

There are 147 images loaded when launching a very simple iOS application!

We thus showed that dynamic libraries were indeed loaded in our iOS application. So what about the dynamic libraries are not supported on iOS argument? Well, let’s try and build one and see what happens!

Building a dynamic library on iOS

In the following section, we will attempt to build three products that are common on the Mac but not supported on iOS:

  • A simple dynamic library linked by the application
  • A framework (a legit one, containing a dynamic shared library)
  • A plugin (i.e. a bundle containing an executable, not packaged with the application but loaded at runtime)

The Dynamic iOS app

As usual, you can find the sample project on GitHub.

Dynamic library on iOS

Let’s start by creating a dynamic library target for our iOS application. Oh wait…

Library choices on iOS

Xcode (logically) doesn’t provide a configuration template for a dynamic library on iOS. Luckily we can select a library from the OS X section and simply change the deployment target and architectures to build in the Build Settings.

Library choices on OS X

If you do this and create your library however, you will get an error when trying to build

Check dependencies:

Target specifies product type ‘com.apple.product-type.library.dynamic’, but there’s no such product type for the ‘iphoneos’ platform

In order to fix this you will have to update the Xcode Specifications for the PackageTypes and ProductTypes for both the iPhone Simulator and the iPhone OS. Instructions on how to do this can be found here.

With this in mind, we can create a simple class, add a sayHello method which implementation simply creates and shows an alert view and add the class to the library target. We can then add the library to the Link Binary with Library build phase for the application. Finally we have to add a Copy build phase and copy the dynamic library to the application bundle. See the sample project for more info on how to set this up.

We can then import the library header (we marked it as public and made sure that it was in the Header Search Paths), instantiate an object and call the sayHello method. If you run the application in the iOS simulator you should see the alert view popping up. Wait, did we just load and run some code from a dynamic library and iOS? Looks like it… (Don’t attempt to run this on a device just yet, we’ll discuss it later).

Framework on iOS

Now that we‘ve seen that we can load and use a dynamic library, it would be awesome if we could just build a framework around it so that we can pack additional resource easily and don’t have to worry about header search paths.

Similarly to the dynamic library case we can create a framework by using the Mac template and change the deployment target and build architectures. We will also create a plist that will be copied as a resource in the framework bundle. We will load the alert view message from the plist, mostly to demonstrate that resource loading works fine from the framework bundle.

Making sure that the header has been marked as public, we can then simply import the framework in the application:

#import "Framework/Framework.h"

Finally, we can instantiate the class and call the method. We should see the alert which message has been loaded from a resource in the framework bundle. It works!

Plugin on iOS

This last one is the most interesting. Applications on the Mac can be extended in a very neat way by supporting plugins. In simple words, a plugin, just like a framework, is a bundle containing an executable and some resources. The plugin declares its principal class in its Info.plist. Usually the principal class is a subclass of an abstract class defined by the application or conforms to a protocol made available by the application.

In order to load the plugin at runtime, the host application will instantiate an NSBundle instance for the plugin location, preflight the bundle to check that it can indeed be loaded and eventually load it. Loading a bundle will dynamically loads the bundle’s executable code into the running program. Once the bundle has been loaded the host application can retrieve the principal class and instantiate it.

So let’s try and build this for iOS. We can create a new target by using the Bundle template. After changing the deployment target and build architecture we create a new class and set it as the NSPrincipalClass in the Info.plist. Similarly to the framework we will create a plist and load the alert message from it at runtime. We also need to make sure that we specify a custom extension for the bundle – here we will use llplugin.

We can then build the plugin and keep the product somewhere safe. We will then copy it in the Documents directory of the iOS application (you can find the application under ~/Library/Application Support/iPhone Simulator/7.1-64/Applications. You might have to change the SDK version based on your platform)

In the host application, we will need to add an imported UTI for the plugin so that we can recognize its file type.

Plugin imported UTI

With the plugin type declared we can now iterate through the contents of the Documents directory and look for plugins, checking conformance to the type for every found item by using UTTypeConformsTo. For each found plugin we can then load the bundle, retrieve the principal class and instantiate it.

- (void)_loadPluginAtLocation:(NSURL *)pluginLocation
{
	NSBundle *plugin = [[NSBundle alloc] initWithURL:pluginLocation];

	NSError *preflightError = nil;
	BOOL preflight = [plugin preflightAndReturnError:&preflightError];
	if (!preflight) {
		return;
	}

	BOOL loaded = [plugin load];
	if (!loaded) {
		return;
	}

	Class pluginClass = [plugin principalClass];
	if (pluginClass == nil) {
		return;
	}

	id pluginInstance = [[pluginClass alloc] init];
	if (![pluginInstance respondsToSelector:@selector(sayHello)]) {
		return;
	}

	[pluginInstance sayHello];
}

If we copy our built plugin in the Documents directory and run the application we should see an alert saying hello. By using the LLImageLogger we can also note that the LLPlugin image is only loaded by the time -[NSBundle load] is called.

We were able to link and load a dynamic library and a framework in an iOS application running in the simulator. We were also able to dynamically load a plugin at runtime and run some of its code, also in the simulator. As you might have guessed there is a catch. So far we’ve only been running our application in the simulator. What happens if we run it on a device?

What about running on the device?

If you attempt to build for running on an actual device Xcode will likely complain that the libraries need to be codesigned. Even if you codesign them you will run into problems when launching your application (this might not happened while you’re debugging the application due to the fact that applications behave slightly differently while being debugged on a device on which Developer Mode has been enabled).

Linked dynamic library

When attempting to run the application containing the dynamic library on the device Xcode will prompt you that the dynamic library has to be code signed. We can simply sign it using the same code sign identity as the application’s. When running it on the device however you will get the following message during dyld bootstrap and the program will be halted.

dyld: Library not loaded: @executable_path/Library.dylib Referenced from: /var/mobile/Applications/DC816A37-F0D4-4F72-9EC8-A642A03C0ABC/Dynamic.app/Dynamic Reason: no suitable image found. Did find: /var/mobile/Applications/DC816A37-F0D4-4F72-9EC8-A642A03C0ABC/Dynamic.app/Library.dylib: code signature invalid for ‘/var/mobile/Applications/DC816A37-F0D4-4F72-9EC8-A642A03C0ABC/Dynamic.app/Library.dylib’

So it seems like dyld is denying the load of the library based on the invalidity of its signature.

This happens during the program load, precisely during the dyld bootstrapping. But what about loading a plugin bundle at runtime?

Plugin loaded at runtime

Let’s see what happens when loading our plugin. When attempting to load the plugin at runtime the application will likely crash with the following backtrace:

Exception Type:  EXC_CRASH (SIGKILL - CODESIGNING)

Thread 0 Crashed:
0   dyld 0x2be50c40 ImageLoaderMachO::crashIfInvalidCodeSignature + 72
1   dyld 0x2be5557a ImageLoaderMachOCompressed::instantiateFromFile + 286
2   dyld 0x2be50b44 ImageLoaderMachO::instantiateFromFile + 204
3   dyld 0x2be48036 dyld::loadPhase6 + 390
4   dyld 0x2be4b9b0 dyld::loadPhase5stat + 296
5   dyld 0x2be4b7c6 dyld::loadPhase5 + 390
6   dyld 0x2be4b61c dyld::loadPhase4 + 128
7   dyld 0x2be4b53c dyld::loadPhase3 + 1000
8   dyld 0x2be4afd0 dyld::loadPhase1 + 108
9   dyld 0x2be47e0a dyld::loadPhase0 + 162
10  dyld 0x2be47bb4 dyld::load + 208
11  dyld 0x2be4d1b2 dlopen + 790
12  libdyld.dylib 0x3a09a78a dlopen + 46
13  CoreFoundation 0x2f392754 _CFBundleDlfcnLoadBundle + 120
14  CoreFoundation 0x2f3925a4 _CFBundleLoadExecutableAndReturnError + 328
15  Foundation 0x2fd7f674 -[NSBundle loadAndReturnError:] + 532
16  Foundation 0x2fd8f51e -[NSBundle load] + 18
17  Dynamic 0x000f64be -[LLViewController _loadPluginAtLocation:]

The application is killed while dyld is attempting to load the bundle. We can only see the userland side of things here but the function at the top frame of the stack should give us a good idea: ImageLoaderMachO::crashIfInvalidCodeSignature. It is worth noting that the plugin we copied into the Documents folder was not code signed. Before attempting to codesign it, let’s quickly analyze what leads to the program being killed when loading the plugin.

In User Space

Luckily, dyld is open-source so we can have a quick look at the implementation of the ImageLoaderMachO::crashIfInvalidCodeSignature function to try and figure out what happened. The file in question is ImageLoaderMachO.cpp and its implementation is pretty simple:

int ImageLoaderMachO::crashIfInvalidCodeSignature()
{
// Now that segments are mapped in, try reading from first executable segment
// If code signing is enabled the kernel will validate the code signature
// when paging in, and kill the process if invalid
	for (unsigned int i = 0; i < fSegmentsCount; ++i) {
		if ((segFileOffset(i) == 0) && (segFileSize(i) != 0)) {
		// return read value to ensure compiler does not optimize away load
			int* p = (int*)segActualLoadAddress(i);
			return *p;
		}
	}
	return 0;
}

This is a pretty naïve (but effective!) way to check the signature and crash if it’s invalid or inexistent: attempt to read from the first executable segment in the executable letting the kernel kill the process should it discover that the code signature cannot be validated when paging in.

In the kernel

The kernel itself is also open-source so we can take a look at it and try and figure out where and how the code signature is verified. I should point out here that reading kernel code is slightly out of my confort zone and I found some great help in Charlie Miller’s SyScan’11 talk Don’t Hassle The Hoff: Breaking iOS Code Signing and book iOS Hacker’s Handbook.

While this is a fascinating subject it could get quite lengthy so I won’t go into details here.

In a nutshell, when codesigned, a Mach-O executable will contain a LC_CODE_SIGNATURE load command referencing a code signature segment in the binary. We can verify it with otool on a signed binary:

> otool -l Plugin.llplugin/Plugin

Load command 17
      cmd LC_CODE_SIGNATURE
  cmdsize 16
  dataoff 9968
 datasize 9616

In the kernel, the Mach-O file is loaded and parsed in the parse_machfile function and the signature is loaded in load_code_signature, both in mach_loader.c. Eventually the signature is checked and its validity is stored inside the csflags member of the kernel’s proc struct for the process.

Later on, whenever a page fault occurs the vm_fault function in vm_fault.c is called. During the page fault the signature is validated if necessary. The signature will need to be validated if the page is mapped in user space, if the page belongs to a code-signed object, if the page will be writable or simply if it has not previously been validated. Validation happens in the vm_page_validate_cs function inside vm_fault.c (the validation process and how it is enforced continually and not only at load time is interesting, see Charlie Miller’s book for more details).

If for some reason the page cannot be validated, the kernel checks whether the CS_KILL flag has been set and kills the process if necessary. There is a major distinction between iOS and OS X regarding this flag. All iOS processes have this flag set whereas on OS X, although code signing is checked it is not set and thus not enforced.

In our case we can safely assume that the (missing) code signature couldn’t be verified leading to the kernel killing the process.

Situation when code signing the plugin

It was pretty obvious to see that dyld refused to load the plugin executable due to its lack of code signature. We can change this.

codesign --sign "iPhone Developer" --force --verbose=4 Plugin.llplugin

When code signing the plugin bundle by using the codesign Terminal utility dyld correctly loads the file at runtime and the plugin code executes correctly, even on the device.

Plugin Loaded!

We thus have no problem dynamically loading an external plugin bundle containing executable code at runtime, even on a device. There is a big caveat to this: we were able to sign the plugin with our Developer certificate given that our device is present in the provisioning profile. For this scenario to happen with a shipped application, the plugin would need to be signed by Apple just like any application submitted to the App Store.

While I don’t see Apple supporting (signed and approved) plugins on iOS anytime soon, I think the dynamic library restrictions could potentially be loosened. By requiring any included executable to be signed with the same certificate as the main application – just like for the Mac App Store – Apple could review them as part of the App Store approval process. Dynamic libraries (in particular when bundled in a framework) would make library distribution so much cleaner and would obsolete the multitude of hacks we have created in order to simply share code.

Conclusion

  • Dynamic libraries signed by Apple can be (and are!) loaded by an iOS application
  • A simple iOS application loads over 150 dynamic libraries on launch
  • Xcode doesn’t support creating dynamic libraries, frameworks or plugins on iOS but it is very easy to work around this
  • If code signing were not a thing we could load dynamic libraries, frameworks and load plugins at runtime on iOS just like we do on the Mac
  • In practice, the kernel will kill any application that attempts to load a dynamic library that is not signed or which signature cannot be validated
  • A shipping dynamic library would need to be signed by Apple with the same certificate as the one used for App Store applications
  • Last but not least, the App Store policies don’t allow dynamic libraries and even if it were technically possible it wouldn’t pass App Store validation

You can find the source code for the Image Logger and Dynamic iOS projects on GitHub.

If you have any question, you can find me on Twitter.

Improved mobile experience

Since I redesigned this site three years ago I’ve wanted to update it to be more friendly when browsed on mobile devices. I’ve finally found some time to do this last night and I’m pleased to say that articles are now much more readable from your phone!

alt:ddeville.me on the iPhone

I haven’t updated the stylesheet for tablets since I believe that a desktop-like format is usually a good fit for this kind of device form factor.

Happy reading! ;)