2
votes

I am trying to interact with a dext from an application. I am able to find the service using IOServiceOpen and I get a call to NewUserClient of my dext (I can see the type parameter passed being output in the log). After this I am a bit lost. Reading here about NewUserClient I can see that one should use Create to create a new Service object.

The Discussion part here says The keys in the propertiesKey dictionary describe the new service.

Should this dictionary be placed in the plist file for the system extension as a top level entry, or should the dictionary be placed with the key in IOKitPersonalities?

Can I leave the IOServiceDEXTEntitlements key with an empty value to not impose any restrictions about entitlements on the application that is connecting to the system extension?

My plist looks like this (with the MyUserClientProperties key / dict in two places).

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">  
<plist version="1.0">  
<dict>  
  <key>CFBundleDevelopmentRegion</key>  
  <string>$(DEVELOPMENT_LANGUAGE)</string>  
  <key>CFBundleExecutable</key>  
  <string>$(EXECUTABLE_NAME)</string>  
  <key>CFBundleIdentifier</key>  
  <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>  
  <key>CFBundleInfoDictionaryVersion</key>  
  <string>6.0</string>  
  <key>CFBundleName</key>  
  <string>$(PRODUCT_NAME)</string>  
  <key>CFBundlePackageType</key>  
  <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>  
  <key>CFBundleShortVersionString</key>  
  <string>1.0</string>  
  <key>CFBundleVersion</key>  
  <string>1</string>  
  <key>MyUserClientProperties</key>  
       <dict>  
           <key>IOClass</key>  
           <string>MyUserClient</string>  
           <key>IOUserClass</key>  
           <string>MyUserUSBInterfaceDriver</string>  
           <key>IOServiceDEXTEntitlements</key>  
           <string></string>  
       </dict>  
  <key>IOKitPersonalities</key>  
  <dict>  
  <key>example_device</key>  
  <dict>  
      <key>MyUserClientProperties</key>  
           <dict>  
               <key>IOClass</key>  
               <string>MyUserClient</string>  
               <key>IOUserClass</key>  
               <string>MyUserUSBInterfaceDriver</string>  
               <key>IOServiceDEXTEntitlements</key>  
               <string></string>  
           </dict>  
  <key>CFBundleIdentifier</key>  
  <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>  
  <key>IOClass</key>  
  <string>IOUserService</string>  
  <key>IOProviderClass</key>  
  <string>IOUSBHostInterface</string>  
  <key>IOUserClass</key>  
  <string>MyUserUSBInterfaceDriver</string>  
  <key>IOUserServerName</key>  
  <string>sc.example.MyUserUSBInterfaceDriver</string>  
      <key>bConfigurationValue</key>  
      <integer>0x1</integer>  
      <key>bInterfaceNumber</key>  
      <integer>0x0</integer>  
      <key>idVendor</key>  
      <integer>0x123</integer>  
      <key>idProduct</key>  
      <integer>0x08</integer>  
  </dict>  
  </dict>  
    <key>OSBundleUsageDescription</key>  
    <string>Example user space USB driver</string>  
</dict>  
</plist>  

Do I need to pass SUPERDISPATCH as the last argument to Create?

From "OSX and iOS kernel programming" chapter 5 page 81:

The ingenuity of the I/O Kit design is that user client objects are themselves a driver object: the IOUserClient class inherits from IOService and, as with any other IOService instance, each user client has a provider class that, for a user client, is the instance of the driver that the application is controlling.

While the above might only be correct for kext (?) I would assume that things work in the same way for a dext,

From Create documentation: Use the kIOUserClassKey key to specify the name of the custom IOService subclass that you want the system to instantiate.

Why is another IOService class needed to be instantiated? What is the purpose of this class? Is it the provider for my class that inherits from IOUserClient? If so how can I make the instance of my driver (the one that implements NewUserClient) the provider?

From Create documentation: Use the kIOClassKey to specify the name of the custom IOUserClient subclass to return to clients of your service.

Is the type of the class that will be created and assigned to the third argument of Create? If so, is that the one I should assign IOUserClient* pointer to, which is passed to NewUserClient?

kern_return_t IMPL(MyUserUSBInterfaceDriver, NewUserClient) {  
  os_log(OS_LOG_DEFAULT, "%{public}d:", type);  

  IOPropertyName propertiesKey = "MyUserClientProperties";  

  IOService* client;  

  auto ret = Create(this, propertiesKey, &client, SUPERDISPATCH);  
  // Need to do more things here...  
  return ret;  
}  

No matter what I try I always get an assert, but I cannot see what is causing it.

3   com.apple.DriverKit            0x0000000102f2b24b __assert_rtn + 102  
4   com.apple.DriverKit            0x0000000102f2c20a IOService::Create_Impl(IOService*, char const*, IOService**) (.cold.2) + 35  
5   com.apple.DriverKit            0x0000000102f1766b IOService::Create_Impl(IOService*, char const*, IOService**) + 91  
6   com.apple.DriverKit            0x0000000102f2668f IOService::Create_Invoke(IORPC, OSMetaClassBase*, int (*)(OSMetaClassBase*, IOService*, char const*, IOService**)) + 135  
7   com.apple.DriverKit            0x0000000102f276d7 IOService::Create(IOService*, char const*, IOService**, int (*)(OSMetaClassBase*, IORPC)) + 267  
8   sc.example.MyUserUSBInterfaceDriver 0x0000000102ee0c89 MyUserUSBInterfaceDriver::NewUserClient_Impl(unsigned int, IOUserClient**) + 313 (MyUserUSBInterfaceDriver.cpp:155)
1

1 Answers

3
votes

As much as the WWDC presentation on DriverKit tried to pretend otherwise, DriverKit's view of the world is very different from the kernel's and you need to be aware of some implementation details, because the abstraction is extremely leaky.

As you've probably already discovered, what looks like an IOService object in your DriverKit driver is actually an IOUserService object in the kernel's (and user space's) view of the I/O registry. The gap is bridged via DriverKit's IPC mechanism.

For creating a new user client, you want an instance of a (kernel) IOUserClient subclass, which is backed by your (dext) IOUserClient subclass. The kernel class for this is actually IOUserUserClient. (Yes, really.) As you've found, the documentation isn't exactly clear on how you go about this. I found it helpful to take a look at what's available in terms of source code - the kernel side of invoking NewUserClient is implemented in the IOUserServer::serviceNewUserClient() function here.

One thing you'll notice straight away is that if the IOServiceDEXTEntitlements property is missing, this won't prevent the code from succeeding:

            prop = userUC->copyProperty(gIOServiceDEXTEntitlementsKey);
            ok = checkEntitlements(entitlements, prop, NULL, NULL);

and in checkEntitlements:

    if (!prop) {
        return true;
    }

This is great news as it means we don't need to worry about it for the minute, and can simply leave it off.

Next, it turns out that the propertiesKey refers to a property on the provider IOUserService kernel object. You can't set those properties from inside the dext's code, so the only way to provide them is from the IOKit matching personality dictionary.

You can name this property however you want, but:

  • Its value must be a dictionary.
  • It must contain the "IOClass" key-value pair, specifying the kernel class to instantiate as a string - in your case, "IOUserUserClient"
  • It must contain the "IOUserClass" key-value pair. This specifies the dext class to instantiate, again as a string. In your case, that looks like MyUserClient.

Putting it together:

  <key>IOKitPersonalities</key>  
  <dict>
    <key>example_device</key>  
    <dict>  
      <key>MyUserClientProperties</key>
      <dict>
        <key>IOUserClass</key>
        <string>MyUserClient</string>
        <key>IOClass</key>
        <string>IOUserUserClient</string>
      </dict>
      <key>CFBundleIdentifier</key>
      <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
      …
    </dict>
  </dict>
  …

Then, from your NewUserClient function, call:

    IOService* client = nullptr;
    kern_return_t ret = this->Create(this, "MyUserClient", &client);

I don't believe SUPERDISPATCH is needed here, as you presumably don't override the Create method in your class, so you super-implementation is inherited anyway.

Then do your error checking, any other initialisation, preparation, etc. you might need, and finally:

    *userClient = client;
    return kIOReturnSuccess;