2

I'm building a virtual HID device in Driver Kit.

I was wanting to communicate with the virtual device driver from a daemon, as the daemon is necessary for generating the HID events that would be sent from the driver.

I'm matching on my driver service fine via the daemon, however when I attempt to open the service I get -536870174 which from what I see here means kIOReturnNotPermitted.

From what I've read about this, the device driver user client can only be opened via an application that has been granted the com.apple.developer.driverkit.userclient-access entitlement by Apple.

So, my question:

Is opening the user client of a device driver in Driver Kit with a daemon completely out of the question?

Is my only option here to have a intermediate application with the com.apple.developer.driverkit.userclient-access entitlement, which can act as a broker between the daemon and the driver?

So it would be something like:

Daemon <---xpc connection--> Intermediate App <--- user client ---> Virtual HID device

Edit: To add to Phil's answer below regarding running an app as a daemon, there's some Apple written guidance here.

Chris
  • 7,996
  • 11
  • 66
  • 98

2 Answers2

3

The short of it: there's no need to go to such lengths. Daemons can have entitlements.

The official solution is to make your daemon an application, from the OS's point of view. Essentially, build your daemon as an application bundle, just don't call it .app. Your launchd plist can point at the embedded executable and it will inherit the surrounding package's entitlements.

Unofficially, I've also had success with directly embedding the Info.plist in the daemon's binary, without a surrounding bundle directory. This is commonly done for privileged helper tools for use with SMJobBless() and is described in a bunch of places including here. Essentially, enable the "Create Info.plist Section in Binary" build setting in Xcode, or add -sectcreate __TEXT __info_plist path/to/Info.plist to your linker flags when using another build system. However, I didn't end up shipping this (didn't need it in the end) so I only ever tested it with SIP disabled. So I currently can't say for certain if it will work when signed with an appropriate provisioning profile.

Both methods allow you to give the binary an application bundle ID by specifying it in the Info.plist, which then also enables you to include an entitlements file during code signing. As com.apple.developer.driverkit.userclient-access is not one of the "open" entitlements from the com.apple.security.* namespace, you'll also need a provisioning profile that includes it, or the OS won't accept it with SIP enabled.

pmdj
  • 22,018
  • 3
  • 52
  • 103
  • 2
    great thanks pmdj! and extra thanks for the ongoing help you are providing around here regarding driver kit. – Chris Apr 03 '21 at 08:26
  • 1
    Added note in my question above that you can see Eskimo's "official" guide to running an app as a daemon here - https://developer.apple.com/forums/thread/129596 – Chris Apr 15 '21 at 19:43
  • 1
    @Chris Thanks, I thought I'd seen a better summary than the one I originally linked to, but couldn't find it anymore. I've edited my answer to add that link too. – pmdj Apr 16 '21 at 09:51
1

If you take the path of running an app as a daemon as in the apple resource stated above, the following code is a swift variation of the objective-c that Eskimo has posted.

import Foundation
import Security
import OSLog;

var me:SecCode? = nil;
let kSecCSDefaultFlags:SecCSFlags = SecCSFlags(rawValue: SecCSFlags.RawValue(0))
var err = SecCodeCopySelf(kSecCSDefaultFlags, &me);
assert(err == errSecSuccess)
var infoCF:CFDictionary? = nil;
var staticMe:SecStaticCode? = nil;
err = SecCodeCopyStaticCode(me!, kSecCSDefaultFlags, &staticMe)
assert(err == errSecSuccess);
err = SecCodeCopySigningInformation(staticMe!, kSecCSDefaultFlags, &infoCF);
assert(err == errSecSuccess);

print(infoCF);
os_log("%{public}s", infoCF.debugDescription);
pmdj
  • 22,018
  • 3
  • 52
  • 103
Chris
  • 7,996
  • 11
  • 66
  • 98