Super User

Developing Kernel Extensions (Kexts) for macOS

In this article, we introduce driver development for macOS, including the basics of implementing kernel extensions (kexts). We discuss typical tasks requiring kernel extensions, tools, and environments for creating them, and some aspects of this process.

Written by: Vyacheslav Konstantinov, Software Developer, Mac Development Team

and Vladimir Vashurkin, Software Developer, Mac Development Team

Contents

1. Overview

1.1 What is the macOS kernel?

1.2 Kernel extensions: .kext

1.3 Why might I need a custom kernel extension?

2. Inside a kernel extension

2.1 Kext bundle structure

2.2 Enter/exit routines

2.3 Kernel-user interaction (I/O)

3. Kext development

3.1 Develop a kext using Xcode

3.2 Testing and debugging techniques

3.3 Signing and Installing your kext

3.4. Exporting your kext signing certificates

Conclusion

1. Overview

1.1 What is the macOS kernel?

The kernel is the central part of the operating system, providing applications with coordinated access to computer resources: processor, memory, external hardware, external input/output devices, and so on. Also, the kernel typically provides file system and network protocol services.

As a fundamental element of the operating system, the kernel represents the lowest level of abstraction for applications to access the system resources they need to operate. The kernel usually provides such access to the executable processes of applications using mechanisms for interprocess communication and application access to operating system calls.

The macOS kernel is XNU: a hybrid core developed by Apple and used in the macOS family.

1.2 Kernel extensions: .kext

A kernel extension (kext) is a special application bundle that extends the functionality of the macOS kernel.

A kext is the minimum unit of executable code that can be loaded and used in the kernel.

1.3 Why might I need a custom kernel extension?

When developing for macOS, usually there's no need for creating a kernel extension. The functionality available in user mode is sufficient for most tasks.

Another restriction of creating a kext is that the code of the kext itself should be close to ideal. This is indicated in Apple's official documentation.

The reason is simple enough: the worst-case scenario for an application is a crash and emergency exit. But if a kernel module fails, the worst-case scenario is a crash of the entire operating system and a reboot of the device. If a kext is loaded at system startup and contains an error, it will crash the system each time it starts, further complicating system recovery.

However, despite possible inconveniences and dangers, there are tasks that can’t be implemented without a kernel extension:

supporting a certain type of file system (including creating a new one)

providing quick access for a large number of concurrent applications to the same resource provided by the kext code

intercepting and substituting an API system call for the purposes of intercepting traffic, I/O, and other system calls as well as for hiding files and processes

writing a device driver (for network controllers, graphics/audio devices, etc.)

Related services macOS Driver Development

2. Inside a kernel extension

2.1 Kext bundle structure

A kext, like any other macOS application, is a bundle, only with the .kext extension. A bundle is a special folder that encapsulates application resources (in this case, kext resources).

A kext bundle must contain two files:

a compiled binary file with executable code

an Info.plist file containing information about the kernel extension: name, version, identifier, dependencies from kernel libraries, etc.

Sometimes, the bundle.kext contains additional files (resources, plugins, etc.):

device firmware

resources (including those localized for use by user mode applications)

plugins, including other kexts

2.2 Enter/exit routines

Depending on the type of extension, a kext can be written in C or C ++ and has its own peculiarities when loading to and unloading to/from the kernel:

Type Generic kernel extension IOKit driver Programming language Usually C C++ Implementation C functions registered as callbacks with relevant subsystems Subclasses of one or more I/O Kit driver classes, such as IOGraphicsDevice Entry points Start and stop functions with C linkage C++ static constructors and destructors Loading behavior Must be loaded explicitly Loaded automatically by IOKit when needed Unloading behavior Must be unloaded explicitly Unloaded automatically by IOKit after a fixed interval when no longer needed

Since this article is devoted to usual kexts, let's take a closer look at loading and unloading kernel extensions.

In the kernel extension code, you must implement entry points — functions that are called when a kext is loaded to and unloaded from the kernel.

These functions can have arbitrary names that must be specified in the project file:

These functions have fixed prototypes:

kern_return_t Kext_start(kmod_info_t* ki, void* d);

kern_return_t Kext_stop(kmod_info_t* ki, void* d);

The Kext_start function usually contains initialization code, which must be executed when the extension starts. This code:

initializes mechanisms of communication with user mode

initializes global variables, setting startup parameters

performs other actions at startup

The Kext_stop function usually contains deinitialization code, releasing resources and performing other actions when the extension expires.

Load and unload .kext files — macOS has a number of command-line tools of the kext...xxx family designed to manage kexts. Commands associated with kexts must be run as root, e.g. with sudo.

kextstat — displays the status of loaded kernel extensions (kexts)

kextload — loads kernel extensions (kexts) in the kernel

kextunload — unloads kernel extensions (kexts) from the kernel

2.3 Kernel–user interaction (I/O)

There are different mechanisms for interactions between kexts and the user space. Since a detailed review of all of them would be huge and deserves a separate article, let’s look at a general description of each mechanism:

ioctl/sysctl (kernel control API for controlling kexts)

This method allows for interaction between the user space and kernel via special sockets. A similar socket must be configured in the kext itself (for example, when the kext starts in the Kext_start function).

Different clients in the user space can connect to the kext socket and send commands to it.

kern_event (kernel events notifications)

Kernel events notifications allow applications to receive notifications about a particular event that has occurred in the kernel. This interaction is also based on sockets.

IOKit (only device drivers)

Apple’s large framework designed for communicating with device drivers.

Preference file/Startup info

One of the most frequently asked questions is how to read a settings file when a kext starts. In fact, there is no API that allows a kext to read files directly.

To do this, a kext must work in conjunction with a daemon running in user mode. At launch, the daemon reads data from the configuration file, connects to the kext using the tools described above, and passes settings to it.

3. Kext development

3.1 Develop a kext using Xcode

Compared to developing kernel extensions for other platforms, developing kernel extensions for macOS is much easier and more comfortable. Kext can be developed in Xcode, a powerful development environment that's provided by Apple for free.

In addition to other templates, Xcode has templates for conveniently creating kexts and includes IOKit Driver.

In the kext template, the developer is immediately provided with:

an implementation file, named the same as the project as well as templates for the start/stop functions of the kext itself;

an Info.plist file, which should be supplemented with the necessary information about the kext (name, identifier, library dependencies, etc.);

the project file, which stores various settings for the kernel extension.

In addition to developing kexts themselves, Xcode allows developers to combine and collect all necessary related tools in one project.

For example, a project may contain:

a kernel extension — the kext itself

a daemon — configures the kext according to the saved settings when the system starts

KextController — a macOS application with a graphical interface that allows users to easily manage the kernel extension

an installer/uninstaller — a kext, unlike a simple macOS application, requires a number of specific actions during installation (copying to the right folder, manually starting the kext after installation (so as not to require rebooting), initial setup, etc.).

Using Xcode, it's possible to develop a kext together with a complete set of auxiliary applications directly out of the box.

3.2 Testing and debugging techniques

Since errors in kernel extensions are critical (they can cause the operating system to crash), thoroughly testing and debugging a kernel extension is a must. At the same time, since a kext is in the kernel, it isn't possible to use usual debugging techniques. There are two general kext debugging techniques that can be used either separately or together.

Logs, logs, logs

All kext code, especially critical, complex and questionable parts, should be covered as much as possible with logs. This technique will allow you to accurately identify the place, and probably the cause, of any error in the kext. In addition, in log messages, developers can detect warning messages that didn't lead to an error this time but could in the future. The log messages from a kext can be seen in the standard macOS application – Console.app.

Debugging

Yes, you can debug a kext. But it isn't so simple as with a usual app. For expanding the kernel, the remote debugging technique is used. Using this technique, debugging is performed with the participation of two devices.

The debugging tools are configured and the product code is located on device 1.

The necessary environment is prepared and the kext is loaded on device 2. Device 1 communicates with device 2 so that debugging can be performed.

Remote debugging using two devices is a complex topic, and a detailed description of how it works is beyond the scope of this article.

There are also some materials from Apple that talk about debugging techniques for kernel extensions:

3.3 Signing and installing your kext

Apple and macOS take care of the security of their users. In the latest versions of macOS, all kexts must be signed with a special signature: Developer ID for Signing Kexts. This requirement has become even stronger with System Integrity Protection, which appeared in macOS 10.11 and is enabled by default.

In order to successfully sign your driver to run it on the latest macOS systems, you need to perform the following steps:

Get a Developer ID certificate by registering as a trusted developer at https://developer.apple.com.

Join the Apple Developer Program here, which costs $99 per year.

After joining the Apple Developer program as an individual user, you’ll be automatically assigned the Account Holder role (the role that includes Team Agent and Legal roles). For an organization, only one member can be assigned as the Account Holder. This role allows you to create a Developer ID certificate and be responsible for entering into legal agreements with Apple, including app transfer agreements. Then you need to request the right to sign kernel extensions in order to make your Developer ID certificate compatible not only with applications but with kernel extensions too. To do this, you need to visit https://developer.apple.com/contact/kext/. You’ll have to state why you need to write a kernel extension and which API you’re using and answer some other questions to prove that you really need the right to sign kernel extensions. Then send your request for approval and wait for a response. Only after you get the approval will your certificate be valid for signing kexts. Once you’ve been approved, generate a certificate using the Apple Developer site. Choose Certificates, ID’s & Profiles in the menu on the left.

Choose macOS as the operating system from the drop-down menu in the top left corner:

Select Developer ID and press Continue :

Select Developer ID Application and Kernel Extension and press Continue :

Create a Certificate Signing Request file with Keychain Access on your Mac. Follow the instructions provided on this page:

After creating the request file, upload it to Apple for approval:

To create your Developer ID certificate using your Application Developer account, follow the detailed instructions on the Apple Developer site. You can also create a Developer ID certificate in Xcode by following the instructions on the help page. After the request has been sent, wait for Apple to approve it (this can take some time, even days). After you get approval, a proper certificate will appear in your account menu under Certificates, IDs & Profiles. You’ll be able to download it from there. Then add this certificate to your Keychain Access by double-clicking on it: Actual signing is performed within Xcode (Developer ID certificates must be added to Keychain). Open Xcode kext project settings (double-click the xcodeproj file in Project Navigator). Go to Build Settings , select your current team (or user) in Team and enter your Signing Certificate : "Developer ID Application: Your Certificate Name (ID)".

You can also sign kexts manually using the codesign utility from the Terminal:

codesign -s "Developer ID Application: Your Certificate Name (ID)" --verbose YourKext.kext

3.4. Exporting your kext signing certificates

In this section, we explain how you can export your kext signing certificate as a .p12 file if you want to transfer it to another machine.

On your Mac, launch Keychain Access, select the certificate entry, and right-click it to select Export Certificate from the pop-up menu.

In the window that appears, make sure the file format is set to Personal Information Exchange (.p12) and click Save to save it to your machine.

When asked for a password, enter a password and click OK .

Your .p12 file will be saved to the location you specified. Install the exported certificate on a new Mac by double-clicking on it. Enter the password that you created in the previous step. The certificate will be added to Keychain.

Conclusion

In this article, we explained how to develop kernel extensions for macOS. This is one of the most complicated development tasks, as any flaws in kext code can lead to a crash of the entire system. Hopefully our tips will make kernel extension development easier for you.

Apriorit has a team of dedicated Mac software developers who are ready to implement your most challenging ideas into innovative macOS applications.