Scott Knight
10 min read



One of the most exciting things announced at this years WWDC was System Extensions. From a security perspective I think this is a really important advancedment for macOS. It means less third party code running in kernel space which should mean more security and stability. From a programmers perspective I think this is even more important. It means that the code developers previously had to write in C++ can now be written in a more modern language like Swift. Apple has been attempting to wrangle in kexts for a while now and this seems to be the final nail in the coffin. They have said macOS 10.15 will be the last release to fully support kexts without compromises and that in future releases of macOS Kernel Extensions with System Extension equivalents will not load at all. I thought it might be interesting to look at the internals of how System Extensions work.

First a disclaimer: everything that follows is based on the Catalina betas so these details might change by the time of the final release. Additionally my goal with this article is to provide an extremely high level overview. There are a lot of moving parts to System Extensions and it wouldn’t be possible to cover all the internals in one post. With that out of the way let’s take a look at how all the different subsystems communicate with each other.

System Extension Internals

Conceptually there’s really two big chunks to this. A user program calling into system services to activate or deactivate an extension and then the system itself running the extensions. The extensions then are what communicate with the kernel. Let’s take a look at some of the different subsystems.


This is one of the new frameworks in Catalina. It’s written in Objective-C and is pretty small. You can read through the official documentation on it here. Its sole purpose is to provide end user applications a way to install and uninstall system extensions. Installation is one area that I hope continues to evolve because for critical security extensions it doesn’t make sense that a user application needs to run once before you’re protected. Regardless there are a couple internal classes that are worth calling out.


This class represents the full information pertaining to a system extension. This object conforms to NSSecureCoding and is used when making requests to OSSystemExtensionPointListener instances.


The main System Extension daemon has friend daemons that it uses to handle the different types of system extension categories. Both endpointsecuritydand nesessionmanager use this class at start up to create a XPC service that listens for system extension start and stop events.


This class is the main way for the different applications to talk to sysextd. You can see a list of apps using this class by searching for it with ripgrep

$ rg -l -a -M 80 OSSystemExtensionClient 2> /dev/null


This is a small command line utility provided by Apple to “list and control System Extensions installed”. It’s also written in Objective-C. It makes direct use of the OSSystemExtensionClient class to call into sysextd. While this isn’t pictured in the diagram above it’s worth calling out because it’s not well documented and is useful for anyone playing around with System Extensions. In the first Catalina beta it was actually non-functional but now if you run the command you can see some basic help information about it.

$ systemextensionctl
systemextensionsctl: usage:
    systemextensionsctl developer [on|off]
    systemextensionsctl list [category]
    systemextensionsctl reset  - reset all System Extensions state
    systemextensionsctl uninstall <teamId> <bundleId>; can also accept '-' for teamID

There are some other test commands not documented. They all basically allow further testing of calls into sysextd

    systemextensionsctl disable <teamID> <bundleID>; can also accept '-' or 'plat' for teamID
    systemextensionsctl enable <teamID> <bundleID>; can also accept '-' or 'plat' for teamID
    systemextensionsctl test <test_routine_name>
    systemextensionsctl testauthz <test_routine_name>
    systemextensionsctl testshouldmove <fromPath> <toPath>; n.b. paths not URLs. To test moving to trash, specify \\\"Trash\\\" as toPath.
    systemextensionsctl testwillmove <fromPath> <toPath>; n.b. paths not URLs. To test moving to trash, specify \\\"Trash\\\" as toPath.

Finally I think it’s useful to see what entitlements applications like these have to get some idea of whether or not the functionality is locked down or not.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "">
<plist version="1.0">


This is a new system daemon. It’s mostly written in swift, which is exciting to see. Most of the existing system daemons are written in Objective-C. It’s great to see Apple adopting Swift more and more for internal uses other than just end user applications. This daemon is responsible for installation, approval, and uninstalling of all the system extensions. It has the following entitlements:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "">
<plist version="1.0">

It delegates out actions to each of the relevant subsystems: kextd, nesessionmanager and endpointsecurityd. There are a couple internal classes that are worth calling out.


This class forwards requests off to kextd through IOKit for DriverKit loading.


This class provides common actions for forwarding requests off to any friend daemon that implements the OSSystemExtensionPointListener class.



These two classes leverage the StandardExtensionDelegate logic for the most part. They’re both responsible for claiming their respective extension types.


DriveKit extensions have a new .dext file type. There’s a lot that goes on to make user space drivers work so again I won’t go into it in great detail. The DriverKit extension binaries are a bit weird. They have no LC_MAIN command in the Mach-O binary. This means if you wanted to try to run the extension yourself in user space you couldn’t. In fact if you try to run it in LLDB you’ll get the following error: error: Bad executable (or shared library). Ultimately the binaries do get loaded and run in user space. It would be interesting to dig a little further into the loading process of .dext files. Overall, I like the architecture and find it very interesting. The user mode drivers basically set up a connection to IOKit and get forwarded events happening and can send back responses as well.


There’s a couple new internal functions in the IOKit.framework.




These three functions are used by kextd to install and uninstall DriverKit extensions.


There were changes to kextd in order for it to understand what drivers have a corresponding user executable. I’m hoping that Apple will release Catalina source shortly after final release in order to be able to look into this more.


The kernel side of IOKit had changes to support sending events of to user space. It looks like in the kernel IOUserServer corresponds to the user space version of the IOUserServer class in the DriverKit.framework

Endpoint Security

This is one of the most interesting parts of System Extensions in my opinion. From what I can tell Apple took their standard pattern for kext to system daemon communication and attempted to turn it into a framework. That way third parties would only have to worry about writing the system daemon side of things and Apple could control the kernel side. Ideally this should lead to much better stability for third party security tools as they’ll be kicked out of doing nasty things inside of kernel space.


This is another new system daemon. Written in Objective-C. It’s responsible for the installing and uninstalling of endpoint security extensions. libEndpointSecurity.dylib doesn’t communicate with endpointsecurityd in any way. There are a handful of entitlements the daemon has:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "">
<plist version="1.0">


When you write an Endpoint Security extension this is the main library that you’ll use. It makes use of the IOKit.framework to register itself as a client with the EndpointSecurity.kext. From there it can opt in or out of listening for certain events from the Kernel Extension and send responses back.


If you’ve ever looked at any third party antivirus kernel extensions this kext looks very similar. It uses the MACF and kauth kernel frameworks to listen to events happening and then sends messages out to any registerd clients. Those registered clients can then make decision on whether to allow or deny the actions happening.

Network Extensions

This is probably where I’ve spent the least amount of time looking into things. The NetworkExtension.framework was something that already existed. As part of Catalina some of the functionality that already existed on iOS is now ported over to macOS. With Network Extensions you can now create things like firewall applications that run completely in user space rather than having to use Network Kernel Extensions


The daemon listens for messages from sysextd to take care of validating, starting and stopping network extensions


This framework existed but there is a new public function that should be noted.


This method is responsible for starting up a listening XPC server. This allows the kernel to send out relevant messages to the user space Network Extensions.


So as you can see there are a lot of different pieces involved in “System Extensions” and I only barely managed to scratch the surface. I’m excited to see how this evolves and the increased security it should bring.