Sharing user application memory with an OS X kernel extension
The contents and solution to this problem may seem obvious, but it wasn’t to me, so I decided to write about my research, in-case I’m not alone.
If you didn’t notice the repetition of the word “THE” in the header image above, you too may have missed the blindingly obvious which I discuss below!
The problem
When developing an OS X kernel extension (kext), it is sometimes a requirement that we develop a user-land application in tandem, in order to control the tasks performed in the kext, or receive notifications from the kernel.
If, in a kernel extension, we wish to share data with a user-land application, how can we do this without copying the data via notifications?
Several methods exist to share memory, but I recently encountered, what I believe to be one of the simplest methods that is briefly documented and I wonder why I don’t see it used more often.
*** There’s more to a kext than IOKit
The first method I encountered is demonstrated in Singh’s Mac OS X Internals, in which a kext is used to create an application to monitor Vnode access and stream out the results to a command-line program.
At the time, a few years ago and being new to OS X development and especially kernel extensions, the use of IOKit to share memory seemed rather complex as it involves an understanding of concepts such as service matching for drivers. Here’s an excerpt of the user-land code used to setup a connection to the kext driver
Once a connection is made, a data queue of memory is shared with the kext; which again requires numerous steps and various complex concepts to understand for the novice kext programmer
Briefly, for those not familiar with the concepts, Apple’s kernel is composed of two parts, the mach kernel and BSD layer. In order to communicate with a process, a mach task is required, but we can’t get the task itself and must acquire a ‘task port’, which is a uni-directional comms channel, rather like using RPC.
The application acquires a notification port, in order to be notified of messages by the kernel and registers it with a function in the kernel, before setting up the shared memory with a call to IOConnectMapMemory. All well and good, but IOKit can be overkill for a kext that isn’t a driver.
If you want to learn more, then explore Singh’s book
Needless to say, it works as expected, but I’ve since found a simpler method, which is far easier understand and utilise.
Kext without IOKit
In order to provide communications between a kext and a user-land process, without IOKit, various methods are possible as outlined in Apple’s documentation on “Boundary Crossings”. These include: .
- BSD syscall API
- BSD ioctl API
- BSD sysctl API
- Memory Mapping and Block Copying
Another possible method is to use socket-style programming which is documented in the “Network Kernel Extensions Programming Guide” and uses the Kernel Control API. This is great for anyone who already has experience with sockets,
Examples of the Kern Ctl API in action can be seen in the source code to Patrick Wardle’s excellent Block Block application, which he blogs about here and the source code to the impressive tool Rootfool, by Pedro Vilaça. (I recommend everyone with OS X installs Block Block and Patrick’s other useful security tool Ostiarius). As these clearly demonstrate the Kern Ctl API, I shall refrain from repeating it here.
Hiding in plain site
Let’s assume we’ve setup communication using one of the methods described above. Our kernel extension has a requirement to process some data and pass it to a connected user-land application.
Most methods require setting up a structure that is shared between both the user-level app and the kext and keeping the two structures carefully in sync.
Wouldn’t it be great if a user-land application could simply allocate memory and pass a pointer to that memory for the kernel, along with the size of the memory allocated? The kernel then simply copies data and notifies the user-land app of the copy.
Let’s go back to Apple’s documentation on “Boundary Crossings” and re-read the section on “Memory Mapping and Block Copying”. It states
In the OS X kernel, data is most easily copied into kernel space with the BSD copyin function, and back out to user space with the copyout function
And that’s it!
All we need to do is use copyout to copy from the kernel and copyin from the user-land application!
Now, perhaps this may be obvious to others, but when I first read this, I didn’t realise its simplicity and my brain chose to ignore it and research other methods.
On returning to the document I read it again and thought “Hang on! What about the Virtual Memory Space for applications and how does one handle this? Surely we need to work out the mapping between the kernel’s VM and the user-land application’s, don’t we?!”
It turns out we don’t; the functions take care of the mapping for us. Let’s look at the copyout declaration
The first argument kaddr is the kernel address (the source), the 2nd uaddr is the user-land’s application address (the destination) and the third is the length of data.
So, if the user-land application creates a block of memory and notifies the kernel of the address of that memory, the copyout function will take care of the mapping during the copy.
Simple when you know how and I can’t believe I didn’t realise this sooner. If it didn’t occur to you either, I’d be interested to know.
The devil really is in the detail!