Extensions are moving away from the kernel

From the outset, Mac OS X and macOS have been designed around a relatively small kernel which is given additional capabilities by kernel extensions. The kernel itself runs at a highly privileged level, giving it most direct access to resources such as the processor, memory and hardware devices, often known as Ring 0. Kernel extensions operate at a close level of privilege (Ring 1) so that they too can make hardware such as ethernet and Thunderbolt ports work, and they’re loaded once the kernel itself is running, before the rest of macOS. Big Sur’s kernel has just over 550 standard kernel extensions which extend it to make everything else work.

Early versions of Mac OS X used to load each of their kernel extensions individually, which made the boot process interminably long. Apple therefore changed to prelinking the kernel with all its extensions, which makes loading much quicker but puts kernel extensions in an even more privileged position.

Kernel extensions have long been one of the most powerful and dangerous features of macOS. They enable Apple and third-party developers to support the rich range of hardware available both within and connected to Macs, to add new features such as software firewalls and security protection, and to modify the behaviour of macOS by rerouting sound output to apps, and so on. With those comes the price that kernel extensions can readily cause the kernel to panic, can conflict with one another and with macOS, and most of all are a security nightmare. For those who develop malicious software, they’re the next best thing to installing their own malicious kernel.

For some years now, Apple has been encouraging third-party developers to move away from kernel extensions to equivalents which run at a user level rather than in Ring 1. However, it has only been in the last year or so that Apple has provided sufficient support for this to be feasible. Coupled with the fact that M1 Macs have to be run at a reduced level of security to be able to load third-party kernel extensions, almost all software and hardware which used to rely on kernel extensions should now be switching to Apple’s new alternatives such as system extensions. This article explains the differences these make to the user.

Extending the kernel

Third-party kernel extensions have traditionally been installed in /Library/Extensions but don’t have to be, and several popular apps such as Parallels Desktop keep theirs in a Library/Extensions folder within the app itself. Big Sur’s Sealed System Volume only stores system-supplied kernel extensions on the System volume, in /System/Library/Extensions, but that’s firmlinked to three additional kernel extensions stored on the Data volume in /Library/Apple/System/Library/Extensions so that they can be updated outside of full macOS updates.

In Big Sur, the previous scheme of prelinked kernels has become complicated with the use of kernel collections, which pool kernel extensions to be loaded into one of three:

  • The Boot Kext Collection (BKC), on the Sealed System Volume in /System/Library/KernelCollections (Intel Macs) or the Preboot volume (M1 Macs). This is the equivalent of the old prelinked kernel, and contains the kernel itself, and all the major system kernel extensions required for a Mac to function. This is typically about 65 MB in size (Intel).
  • The System Kext Collection (SKC), also on the Sealed System Volume of Intel Macs in /System/Library/KernelCollections, but not used on M1 models. This contains all the other system kernel extensions, which are loaded after booting with the BKC. This is typically around 470 MB.
  • The Auxiliary Kext Collection (AKC), stored on the Data volume in /Library/KernelCollections when it exists, is built and managed by the service kernelmanagerd. This contains all installed third-party kernel extensions, and is loaded after the other two collections.

By default, in Apple Silicon Full Security, there is no AKC. For an M1 Mac to load its AKC it must be running at reduced security, with AKC explicitly enabled in the Startup Security Utility (available only in the primary Recovery system on the internal SSD). Another important limitation with Apple Silicon models is that they can only load kernel extensions which run native on their ARM processors, as Rosetta 2 translation isn’t available to convert Intel code.

The standard way to discover which third-party kernel extensions are installed is in System Information: select the Extensions item in Software, then order the list using the Obtained From column (click in that column header). That also tells you whether each kernel extension is loaded, and whether it has been notarized. All kernel extensions have to be signed specially, and all recent ones also have to be notarized.

Controlling kernel extensions

Apps that install kernel extensions trigger a process in Big Sur which should result in them being added to the AKC so they can be loaded during each startup. On an M1 Mac, this is only allowed when explicitly enabled in the system security LocalPolicy for the boot disk.

The user is prompted to open the Security & Privacy pane and to consent to the newly installed kernel extension being installed on that system. Prior to that, macOS copies the kernel extension into a folder within /Library/StagedExtensions, where it’s protected by SIP. Only if the user has agreed will that kernel extension be incorporated into a rebuilt AKC, and will then be available for loading following the next restart. Note that even unapproved (thus unloadable) kernel extensions are also copied into that folder, so the presence of a kernel extension in staging doesn’t indicate that it is active or can be loaded.

Previous tools for the management of kernel extensions included kextload, kextunload and others. In Big Sur and later, these have been replaced by a single command tool kmutil, which is inevitably complex to use. Full details are given in its man page, which is extensive and an excellent source of additional information.

There are four kmutil commands which may be most useful to users:

  • kmutil unload -p /path/kextname.kext unloads the kernel extension specified by /path/kextname.kext. This terminates and unloads it, but may not remove the original kernel extension or a staged copy. Unless you also remove the kernel extension and remove it from its collection, it could load again at the next boot.
  • kmutil clear-staging clears the contents of the staging directory /Library/StagedExtensions.
  • kmutil trigger-panic-medic, which is only available in recoveryOS, clears the AKC at /Library/KernelCollections and forces it to be rebuilt, which requires each kernel extension to be re-approved before it can be loaded.
  • kmutil inspect lists all currently installed kernel extensions according to their collection.

In theory, removing the original kernel extension by removing the app which contains it, or deleting it from /Library/Extensions, should trigger kernelmanagerd to remove it from the AKC and the staging directory /Library/StagedExtensions. However, that won’t take effect until after the next reboot. If the kernel extension isn’t then removed, it may be worth using kmutil clear-staging, and if necessary kmutil trigger-panic-medic in recoveryOS. Remember that kernel extensions may be left unused in staging, and are protected there by SIP, making manual removal tedious at best, and possibly pointless.

User-level extensions

Instead of using kernel extensions, modern apps are supposed to use one of the replacements which run at a user-level rather than in Ring 0 or 1. That should isolate them from the kernel, making it impossible for them to cause kernel panics, and strictly limit the power of any malicious extensions. These come in several varieties, including System, Driver, Endpoint Security and Network Extensions, depending on their purpose and the framework they use.

Although some of the user-level extensions do have folders (such as /Library/DriverExtensions) in which the extension may be stored, and some may also be staged (at /Library/StagedDriverExtensions, for example), user-level extensions are managed by the app responsible for installing them, and are found in the app’s Contents/Library/SystemExtensions folder. Neither do they need M1 Macs to be run at reduced security, nor approval in the Security & Privacy pane.

Controlling user-level extensions

These extensions should be completely managed by the apps which rely on them. Early in its launch process, the app should check whether its required extensions are installed. If they aren’t, the app should then guide you through that process, which usually involves authentication but doesn’t require consent in the Security & Privacy pane or restarting the Mac.

One of the system requirements for the activation of user-level extensions is that the app is installed in one of the Applications folders. Activation may therefore fail if the app hasn’t yet been properly installed or moved into /Applications or ~/Applications.

Removing or uninstalling the app should deactivate and remove all its user-level extensions. Apps may also provide a command or feature for doing that manually.

The command tool used to manage system extensions is systemextensionsctl, but its man page is devoid of information, so you’ll have to view its usage information using
systemextensionsctl -h
for example. You can use
systemextensionsctl list
to list all known system extensions and their status.

Further reading

Installing a kernel extension (Apple)
Installing system extensions and drivers (Apple)