Moving from classic Mac OS to Mac OS X was an interesting experience. Although early versions of the latter weren’t particularly stable in themselves, one of the more obvious differences was that apps usually crashed without taking the whole system down. One of the reasons for this is that OS X and macOS are built to withstand most catastrophes which can happen in user processes, as they have a kernel which runs in a different world.
From the outset, Mac OS X and macOS have been designed around a relatively small kernel. That 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, corresponding to the most privileged level 0 in Intel processors. Some processor instructions can only be used from this level of privilege.
The macOS kernel is known as XNU (X is Not Unix, just to remind us), and has three main functional units. At its heart is Mach, which provides low-level services including inter-process communication, memory management including virtual memory, and scheduling. Relying on that are BSD features including basic networking support and core file systems. The third unit is I/O Kit, which originated in NeXTSTEP, providing extensive support for device drivers.
Although it’s all-powerful, the kernel itself can’t do everything on its own. Kernel extensions operate at a close level of privilege (Ring 1) so that they can make hardware such as ethernet and Thunderbolt ports work, and many are loaded once the kernel itself is running, before the rest of macOS. Mojave’s kernel has over 500 standard kernel extensions which extend it to make everything else in your Mac work. Kernel extensions can also be loaded and unloaded while the kernel is running.
Other processes, whether they are run with root or user privileges, all run in Ring 3, where they have controlled access to the features provided in Rings 1 and 0, but shouldn’t be able to interfere with those more privileged levels. These layers aren’t just for security protection, but bring stability to the whole operating system by protecting the kernel from the chaos taking place in Ring 3. When it all works correctly, apps can crash and burn in Ring 3 without affecting the kernel and its extensions.
When that goes wrong, the kernel gets into a problem which it can only solve by rebooting, so it ‘panics’ and your Mac restarts. Kernel panics should thus reflect a failure in the protection rings within macOS. Of course, bugs in the kernel itself can cause panics, and even more importantly, so can problems in kernel extensions, because of their privileged position in Ring 1.
Kernel extensions have long been one of the most powerful and dangerous features of macOS. They have enabled 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, 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 worst of all are a security nightmare: for those who develop malicious software, they’re the next best thing to installing their own malicious kernel.
Using kernel extensions
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 into a kernelcache file, which makes loading much quicker but puts kernel extensions in an even more privileged position.
In this traditional model, kernel extensions were normally installed together in /System/Library/Extensions, but more recently Apple reserved that folder for its own bundled kernel extensions, leaving those installed by third-parties to go into /Library/Extensions. They can also be stored outside those folders, for example in an app’s bundle, but that requires the app to handle their loading using a LaunchDaemon property list or a shell script calling the commands
kextunload, or their successor
kextutil, with root privileges, when required. Drivers using I/O Kit are different, as they’re automatically loaded on demand when properly registered in the I/O Registry.
There are several pitfalls lurking in kernel extensions that are well-known for causing problems. Sleep and wake are common events which can cause kernel panics, and kernel extensions must ensure they handle these events and don’t keep the CPU awake. Memory leaks are another serious issue which can render a Mac unstable.
One of Apple’s most ambitious design goals in Apple Silicon is Secure Boot, in which everything loaded and run during boot and in Rings 0 and 1 is immutable, sealed and signed. This transition started long before Mojave, with developers being warned that kernel extensions were ‘deprecated’, and should be replaced. Unfortunately, suitable alternatives didn’t arrive until the transition was well under way.
Even with Monterey, this still isn’t complete. Apple has three remaining kernel extensions, AppleKextExcludeList, AppleMobileDevice and RemoteVirtualInterface, which are stored on the Data volume to allow for change outside of macOS updates. More importantly for many users, Monterey still allows the user to install and use third-party kernel extensions, although that is accompanied by a reduced security level. In the next article I will explain how this interim period works.