Tuesday, March 22, 2016

Basic Proof of Concept

<< Prev: Introduction       Next: Loading Firmware >>

I figured a good first step would be to create a kext that recognizes the hardware it's supposed to apply to and loads and unloads successfully. That would be the most basic proof of concept. Also a good sanity check — if I can't get that far, I should quit now.

It turns out this part can all be done in OS X-land.

Project Setup

Not wanting to make this any more difficult than I had to, I started with Xcode.

I created a new project, with type OS X / System Plugin / IOKit Driver. That gave me some pretty empty boilerplate code. About all I did to customize it initially was change the header file from .hpp to .h because I haven't really seen .hpp files elsewhere and .h seems to work fine.

Matching Hardware

These cards are all PCI-based (PCIe, m.2, etc.). The PCI device infrastructure lets you specify a list of compatible PCI vendor and device IDs in the Info.plist for the kext, and it will start the driver when matching hardware is present. All I needed for that part was the list of device IDs. I found it here (just scroll past the license). That code actually shows the device ID and sub-device ID, since the vendor ID in both cases is Intel (shockingly, 0x8086).

I used that big list later for matching all the specific cards the code was supposed to recognize, but just to match the driver with the correct product families, I started with the IDs in the Info.plist like this (notice they all end with the vendor ID of 8086):

<key>IOKitPersonalities</key>
    <dict>
        <key>AppleIntelWiFiMVM</key>
        <dict>
            <key>CFBundleIdentifier</key>
            <string>org.opentools.${PRODUCT_NAME:rfc1034identifier}</string>
            <key>IOClass</key>
            <string>AppleIntelWiFiMVM</string>
            <key>IOProviderClass</key>
            <string>IOPCIDevice</string>
            <key>IOPCIPrimaryMatch</key>
            <string>0x08b18086 0x08b28086 0x08b38086
                    0x08b48086 0x31658086 0x31668086 0x095a8086
                    0x095b8086 0x24f38086 0x24f48086</string>
            <key>IOProbeScore</key>
            <integer>1000</integer>
        </dict>
    </dict>

The only challenge was testing it on my MacBook Pro, which doesn’t have a suitable card.

Fortunately, I found a workaround for this stage. Normally, OS X only loads one driver for each device. However, by specifying an IOMatchCategory in the Info.plist, you can get a separate driver to load for each "category". So I could add the device ID of some hardware I actually had in IOJones, with a bogus IOMatchCategory, and then load the driver for my non-Intel WiFi hardware. It would never work for real, but it was enough to let me prove that the driver would load and start, and therefore emit messages to the system log.

The changed plist entries look like this (notice the non-Intel ID at the start of the list):

<key>IOPCIPrimaryMatch</key>
    <string>0x12421b21 0x08b18086 0x08b28086 0x08b38086
            0x08b48086 0x31658086 0x31668086 0x095a8086
            0x095b8086 0x24f38086 0x24f48086</string>
    <key>IOMatchCategory</key>
    <string>Foo</string>

Finding the Kext

Well, I'm getting a little ahead of myself. First, I tried to load the kext with "sudo kextload [kext file]". Rather, I tried to try that. But I couldn't find the kext it built.

I guess I'm used to every other build environment on Earth, which puts the thing you just built right there in your project where you can then go on to try it out or work with it. Xcode, in contrast, stuffs it in a directory you'd never find. It puts a bunch of random characters in the path just to make sure it's unpredictable location. Because, you know, it would be so dangerous if you could actually find the thing you just built.

OK, I guess it just moved my cheese. Anyway, right-clicking the kext name under "Products" in Xcode lets you "reveal" it in Finder. Then I dragged it to someplace more useful.

SIP and Unsigned Kexts

Of course, then when I tried to load it, it wouldn't load or start. I hadn't set up my code signing infrastructure in Xcode. So for now, I disabled System Integrity Protection on my development machine. There's an easy command for that — "csrutil disable". Unfortunately, it has to be run by booting to Recovery with Command-R on startup, an then opening Terminal to run the command. I was annoyed by this reboot, but at least I knew it was coming (more on that part in a later post).

OSBundleLibraries

That got past the signature problems, but the kext still wouldn't load:
$ sudo kextload AppleIntelWiFiMVM.kext
Password:
/Users/ammulder/temp/AppleIntelWiFiMVM.kext failed to load
- (libkern/kext) validation failure (plist/executable);
check the system/kernel logs for errors or try kextutil(8).

The log then reported:
2/23/16 3:49:37.347 PM com.apple.kextd[45]:
/Users/ammulder/temp/AppleIntelWiFiMVM.kext
is invalid; can't resolve dependencies.

That reminded me of the Info.plist section that was supposed to declare dependencies. So I ran the tool to generate that block of code. (If there's a tool for that, why do you have to write it instead of the compiler or kext loading system just using the tool automatically? Good question.)

It gave me this:

$ kextlibs -xml AppleIntelWiFiMVM.kext
 <key>OSBundleLibraries</key>
 <dict>
  <key>com.apple.iokit.IOPCIFamily</key>
  <string>2.9</string>
  <key>com.apple.kpi.iokit</key>
  <string>15.3</string>
  <key>com.apple.kpi.libkern</key>
  <string>15.3</string>
 </dict>

I put that into my Info.plist instead of the empty OSBundleLibraries it defaulted to.

File Permissions

Then I tried again:
$ sudo kextload AppleIntelWiFiMVM.kext
/Users/ammulder/temp/AppleIntelWiFiMVM.kext
failed to load - (libkern/kext) authentication failure
(file ownership/permissions); check the system/kernel
logs for errors or try kextutil(8).

The log wasn't much more helpful:
2/23/16 5:28:51.886 PM com.apple.kextd[45]:
Can't load /Users/ammulder/temp/AppleIntelWiFiMVM.kext
- authentication problems.

But it turns out this means the kext needs different file permissions:
$ sudo chown -R root:wheel AppleIntelWiFiMVM.kext
$ sudo kextload AppleIntelWiFiMVM.kext
$ sudo kextunload AppleIntelWiFiMVM.kext

That did it! Now the kext loads and unloads successfully. Checking Console.app, I see:
Feb 19 14:08:57 sulaco kernel[0]: AppleIntelWiFiMVM::init
Feb 19 14:08:57 sulaco kernel[0]: AppleIntelWiFiMVM::start
Feb 19 14:08:57 sulaco kernel[0]: AppleIntelWiFiMVM Found card
definition for Vendor 0x8086 Device 0x095a
SubVendor 0x8086 SubDevice 0x9010 Revision 0x59
Feb 19 14:09:07 sulaco kernel[0]: AppleIntelWiFiMVM::stop
Feb 19 14:09:07 sulaco kernel[0]: AppleIntelWiFiMVM::free

That 0x095a device is the card in my test machine! It loaded and recognized the hardware and then shut down on command; so far so good.

Code & Configuration

The code and XCode project for the progress described here is in GitHub:
https://github.com/ammulder/KextTest

<< Prev: Introduction       Next: Loading Firmware >>

1 comment:

  1. "Because, you know, it would be so dangerous if you could actually find the thing you just built."

    I'm rolling on the floor :D

    ReplyDelete