Hello everyone,
I am migrating a legacy KEXT to a DriverKit (DEXT) architecture. While the DEXT itself is working correctly, I am completely blocked by a code signing issue when trying to establish the UserClient connection from our SwiftUI management app.
Project Goal & Status:
- Our DEXT (com.accusys.Acxxx.driver) activates successfully (systemextensionsctl list confirms [activated enabled]).
- The core functionality is working (diskutil list shows the corresponding disk device node).
The Core Problem: The userclient-access Signing Error
-
To allow the app to connect to the DEXT, the com.apple.developer.driverkit.userclient-access entitlement is required in the app's .entitlements file.
-
However, as soon as this entitlement is added, the build fails.
-
Both automatic and manual signing fail with the same error:
`Provisioning profile ... doesn't match the entitlements file's value for the ... userclient-access entitlement.` -
This build failure prevents the generation of an .app bundle, making it impossible to inspect the final entitlements with codesign.
What We've Confirmed:
- The necessary capabilities (like DriverKit Communicates with Drivers) are visible and enabled for our App ID on the developer portal.
- The issue persists on a clean system state and on the latest macOS Sequoia 15.7.1.
Our Research and Hypothesis:
- We have reviewed the official documentation "Diagnosing issues with entitlements" (TN3125).
- According to the documentation, a "doesn't match" error implies a discrepancy between the entitlements file and the provisioning profile.
- Given that we have tried both automatic and manual profiles (after enabling the capability online), our hypothesis is that the provisioning profile generation process on Apple's backend is not correctly including the approved userclient-access entitlement into the profile file itself. The build fails because Xcode correctly detects this discrepancy.
Our Questions:
Did we misunderstand a step in the process, or is the issue not with the entitlement request at all? Alternatively, are there any other modifications we can make to successfully connect our App to the DEXT and trigger NewUserClient?
Thank you for any guidance.
After a full clean, build, and reboot, the IOServiceGetMatchingService("DriverKitAcxxx") call still fails at runtime, returning IO_OBJECT_NULL.
No, it means your code is wrong and you're looking for the wrong object. From your earlier post, here is the IORegistry entry for your device:
+-o DriverKitAcxxx <class IOUserSCSIParallelInterfaceController, ... registered, matched, active, ...>
| "IOUserClass" = "DriverKitAcxxx"
| "IOUserServerName" = "com.accusys.Acxxx.driver"
...
Pulling that apart in details:
+-o DriverKitAcxxx <class IOUserSCSIParallelInterfaceController
-
"DriverKitAcxxx"-> This is your "Service Name". In a DEX, it's set through IOService.SetName and defaults to the value of your "IOUserClass". You create a match dictionary for it using "IOServiceNameMatching()".
-
"IOUserSCSIParallelInterfaceController"-> This is the name of the class in the kernel. It was configured by your DEX through your "IOClass" property, exactly as it would have been in a KEXT. You create a match dictionary for it using "IOServiceNameMatching()".
Making sure this is entirely clear, the class "DriverKitAcxxx" does NOT exist in the kernel. Instead, there is an instance of "IOUserSCSIParallelInterfaceController" which is the supporting driver for your DEX.
That leads back to here:
After a full clean, build, and reboot, the IOServiceGetMatchingService("DriverKitAcxxx") call still fails at runtime, returning IO_OBJECT_NULL.
This result seems to indicate that the temporary exception entitlement is not sufficient to grant our app visibility of the active IOKit service on macOS.
No, that is NOT what IOServiceGetMatchingService returning IO_OBJECT_NULL has ever meant. What returning NULL actually means is either:
-
An error prevented IOKitLib from looking for your service.
-
The IORegistry was fully searched and your service was not found.
Critically, you CANNOT differentiate between those two errors using IOServiceGetMatchingService. To differentiate between those two cases, you must call "IOServiceGetMatchingServices", in which case you'll either get:
-
An error returned (for #1 above).
-
kIOReturnSuccess but an empty iterator (for #2 above).
I believe if you try this test, you will find that you are in fact getting #2. In any case, the code below demonstrates exactly what I'm describing.
// main.m
// RegistrySearcher
//
// Created by Kevin Elliott on 10/16/25.
//
#import
void KE_PrintIOObject(io_service_t myObj)
{
if(myObj != 0)
{
NSString* className = CFBridgingRelease(IOObjectCopyClass(myObj));
NSLog(@"Class Name = %@", className);
CFMutableDictionaryRef propDictionary = NULL;
if(IORegistryEntryCreateCFProperties(myObj, &propDictionary, CFAllocatorGetDefault(), 0) == kIOReturnSuccess)
{
CFShow(propDictionary);
CFRelease(propDictionary);
}
}
}
void KE_SearchForDict(CFDictionaryRef matchingDictionary)
{
CFShow(matchingDictionary);
io_iterator_t myIterator = 0;
kern_return_t err = IOServiceGetMatchingServices(kIOMainPortDefault, matchingDictionary, &myIterator);
if(err == kIOReturnSuccess)
{
int count = 0;
io_service_t myObj = 0;
while((myObj = IOIteratorNext( myIterator)) != 0)
{
count++;
//KE_PrintIOObject(myObj);
}
NSLog(@"Object Count: %d", count);
}
else
{
NSLog(@"IOServiceGetMatchingServices err: %X", err);
}
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
CFDictionaryRef match = IOServiceNameMatching("DriverKitAcxxx");
KE_SearchForDict(match);
match = IOServiceMatching("DriverKitAcxxx");
KE_SearchForDict(match);
match = IOServiceMatching("IOUserSCSIParallelInterfaceController");
KE_SearchForDict(match);
match = IOServiceMatching("IOPartitionScheme");
KE_SearchForDict(match);
}
return EXIT_SUCCESS;
}
When run on my machine, it returns exactly what I'd expect (since I don't have your hardware):
{
IONameMatch = DriverKitAcxxx;
}
Object Count: 0
{
IOProviderClass = DriverKitAcxxx;
}
Object Count: 0
{
IOProviderClass = IOUserSCSIParallelInterfaceController;
}
Object Count: 0
{
IOProviderClass = IOPartitionScheme;
}
Object Count: 42
Note that the choice of "IOPartitionScheme" was arbitrary, as I simply picked a class which I know will be present on all machines but which will have a relatively limited object count. To run this code on your machine, just create a new Objective-C command line tool project and paste the entire file above into the new main file. No additional configuration is required, as the default command line tool template is sandboxed.
If you run the code on your machine and it fails, send back the error code and I'll see what's going on.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware