The kernel and extensions 2: Secure Boot

In my previous article, I explained the Rings of privilege which are designed to protect the kernel and its extensions in Mac OS X and macOS up to Mojave. By that time, Apple’s project to develop Apple Silicon Macs was well advanced, bringing with it the design intent that everything loaded and run during boot and in Rings 0 and 1 is immutable, sealed and signed, in Secure Boot.

This was the result of bitter experience with UEFI firmware, the proliferation of third-party kernel extensions, and the high failure rate of system software updates and installations. Whereas kernel panics should have been most commonly the result of hardware faults, they were far too often caused by things going wrong in Ring 1.

The goal is for all software loaded and run from the start of the boot process until Ring 3 processes are started, to be demonstrably intact and exactly as installed by Apple’s software. The solution chosen for Apple Silicon Macs is for sealed and signed pre-boot components, and system software loaded from a sealed and signed snapshot on the System volume. This only allows Apple’s bundled kernel extensions, still running in Ring 1. Third-party extensions are thus banished to Ring 3 and userland.

macOS has transitioned in steps. Catalina brought the basic volume group layout, but mounted its System volume read-only rather than as a snapshot. Big Sur changed to use a snapshot, which is retained in Monterey. As these major architectural changes took place, third-party kernel extensions were progressively squeezed out.

First, each had to be explicitly permitted on installation, via the Security & Privacy pane. While that remains sufficient for T2-equipped Intel Macs, Apple Silicon models go one step further: Secure Boot, as set by the Startup Security Utility in Recovery mode, has to be set explicitly to allow the use of third-party kernel extensions, and with that security has to be downgraded in recognition of the impact this has on the new security and integrity model.

Kernel extensions

The need to continue supporting third-party kernel extensions on Apple Silicon is temporary and open-ended. It sadly wasn’t lessened by delays in providing suitable alternatives, and in the approval process for replacements, which could take many months.

For Big Sur and Monterey, Apple changed its previous scheme of prelinked kernelcaches to 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 68 MB in size.
  • The System Kext Collection (SKC), also on the Sealed System Volume of Intel Macs in /System/Library/KernelCollections, but apparently 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 430 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 Startup Security Utility. In Big Sur, that’s available only in the primary Recovery system on the internal SSD, but in Monterey that has moved to the Recovery volume associated with the current boot System Volume Group. For those running multi-boot Macs, this can be profoundly confusing, and mixing Big Sur and Monterey bootable systems on the same Mac is probably unwise.

Another important limitation with Apple Silicon Macs 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 specially signed, and all recent ones (including all those usable on M1 Macs) also have to be notarized.

Controlling kernel extensions

Apps that install kernel extensions trigger a process 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 that 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 then becomes 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’s 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 doesn’t remove the original kernel extension or any staged copy. Unless you also remove the kernel extension and remove it from its collection, it’s likely to 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. This is intended to be used to recover a system following a kernel panic generated by one of the kernel extensions in the AKC.
  • 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 Recovery mode. 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 their replacements running with Ring 3 privileges rather than Ring 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 bundle’s Contents/Library/SystemExtensions folder. Most importantly for M1 Mac users, they don’t need macOS 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, and sometimes these mechanisms appear to fail, leaving an orphaned system extension. That’s currently not an easy problem to solve, and requires disabling SIP first, something that Apple needs to address as system extensions become more widely used.

The command tool used to manage system extensions is systemextensionsctl, but its man page is still 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.

To remove an orphaned system extension, with SIP already disabled, first list those known using
systemextensionsctl list
which provides the teamID and bundleID. Then use those in the command
systemextensionsctl uninstall teamID bundleID
and don’t forget to re-enable SIP immediately afterwards.

What next for extensions?

Apple needs to complete its transition away from third-party kernel extensions on Apple Silicon Macs. It hasn’t revealed how long it will be before the option supporting them in Startup Security Utility is removed, and that should ideally be driven by developing user needs as more migrate to Apple Silicon models. Given that M1 Macs are unable to use kernel extensions which only support Intel models, there can’t be much of a legacy to deal with, and it’s possible that this change will come with macOS 13 later this year. That assumes the current Ring 3 replacements for kernel extensions are proving sufficient for every case.

Apple also has three remaining system kernel extensions, AppleKextExcludeList, AppleMobileDevice and RemoteVirtualInterface, which are still stored on the Data volume to allow for change outside of macOS updates. While the first of those becomes defunct once third-party kernel extensions aren’t allowed, Apple still has to address the latter two.

Finally, as in so many other fields, Apple needs to enhance and document its tools for managing system extensions. I have a feeling we might be waiting rather longer for that miracle.

Further reading

Installing a kernel extension (Apple)
Installing system extensions and drivers (Apple)
How to remove a system extension (Thomas Reed, Malwarebytes)