Which IOUserClient entitlements are really required?

Hello @all

I'm develop a DriverKit driver extension and without entitlement checks by OS everything runs fine. But if the entitlements check is enabled in the NVRAM then I get an error due connecting my IOUserClient instance. Which entitlements are really and exactly required for my driver?

My driver contains:

  1. one IOUserClient instance
  2. and multiple IOUserSerial instances

The bundle identifier of the driver ist:

org.eof.tools.VSPDriver

The bundle identifier of the client app

org.eof.tools.VSPInstall

My entire source code is available on GitHub if any one want to dive deep in :)

kernel[0:5107] () [VSPDriver]: NewUserClient called.
kernel[0:5107] () [VSPDriver]: CreateUserClient: create VSP user client from Info.plist.
kernel[0:5107] () [VSPUserClient]: init called.
kernel[0:5107] () [VSPUserClient]: init finished.
kernel[0:5107] () [VSPDriver]: CreateUserClient: check VSPUserClient type.
kernel[0:5107] () [VSPDriver]: CreateUserClient: success.
kernel[0:5107] () [VSPDriver]: NewUserClient finished.
kernel[0:5107] () [VSPUserClient]: Start: called.
kernel[0:5107] () [VSPUserClient]: User client successfully started.
kernel[0:389f] DK: VSPUserClient-0x100001127:UC failed userclient-access check, needed bundle ID org.eof.tools.VSPDriver
kernel[0:389f] DK: VSPUserClient-0x100001127:UC entitlements check failed
kernel[0:5107] () [VSPUserClient]: Stop called.
kernel[0:5107] () [VSPUserClient]: User client successfully removed.
kernel[0:5107] () [VSPUserClient]: free called.

Here my drivers entitlement file:

<?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>com.apple.developer.driverkit</key>
	<true/>
	<key>com.apple.developer.driverkit.allow-third-party-userclients</key>
	<true/>
	<key>com.apple.developer.driverkit.family.serial</key>
	<true/>
</dict>
</plist>

Here my drivers Info.plist file

<?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>NSHumanReadableCopyright</key>
	<string>Copyright © 2025 by EoF Software Labs</string>
	<key>OSBundleUsageDescription</key>
	<string>Provide virtual serial port</string>
	<key>com.apple.developer.driverkit</key>
	<true/>
	<key>com.apple.developer.driverkit.allow-any-userclient-access</key>
	<true/>
	<key>com.apple.developer.driverkit.communicates-with-drivers</key>
	<true/>
	<key>com.apple.developer.system-extension.redistributable</key>
	<true/>
	<key>OSBundleLibraries</key>
	<dict>
		<key>com.apple.iokit.IOSerialFamily</key>
		<string>1.0</string>
	</dict>
	<key>IOKitPersonalities</key>
	<dict>
		<key>VSPDriver</key>
		<dict>
            <key>CFBundleIdentifier</key>
            <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
			<key>CFBundleIdentifierKernel</key>
			<string>com.apple.kpi.iokit</string>
			<key>IOMatchCategory</key>
			<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
			<key>IOProviderClass</key>
			<string>IOUserResources</string>
			<key>IOResourceMatch</key>
			<string>IOKit</string>
			<key>IOProbeScore</key>
			<integer>0</integer>
			<key>IOClass</key>
			<string>IOUserService</string>
			<key>IOUserClass</key>
			<string>VSPDriver</string>
			<key>IOUserServerName</key>
			<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
			<key>UserClientProperties</key>
			<dict>
				<key>IOClass</key>
				<string>IOUserUserClient</string>
				<key>IOUserClass</key>
				<string>VSPUserClient</string>
			</dict>
			<key>SerialPortProperties</key>
			<dict>
				<key>CFBundleIdentifierKernel</key>
				<string>com.apple.driver.driverkit.serial</string>
				<key>IOProviderClass</key>
				<string>IOSerialStreamSync</string>
				<key>IOClass</key>
				<string>IOUserSerial</string>
				<key>IOUserClass</key>
				<string>VSPSerialPort</string>
				<key>HiddenPort</key>
				<false/>
				<key>IOTTYBaseName</key>
				<string>vsp</string>
				<key>IOTTYSuffix</key>
				<string>0</string>
			</dict>
		</dict>
	</dict>
</dict>
</plist>

Here the entitlements of the client app

<?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>com.apple.developer.driverkit</key>
    <true/>
    <key>com.apple.developer.driverkit.allow-third-party-userclients</key>
    <true/>
	<key>com.apple.developer.driverkit.communicates-with-drivers</key>
    <true/>
	<key>com.apple.developer.shared-with-you</key>
	<true/>
	<key>com.apple.developer.system-extension.install</key>
	<true/>
	<key>com.apple.security.application-groups</key>
	<array>
		<string>$(TeamIdentifierPrefix).org.eof.apps</string>
	</array>
</dict>
</plist>

Here the Info.plist of the client app:

<?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>LSMinimumSystemVersion</key>
	<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
	<key>com.apple.developer.driverkit</key>
	<true/>
	<key>com.apple.developer.driverkit.install</key>
	<true/>
	<key>com.apple.developer.system-extension.install</key>
	<true/>
	<key>com.apple.developer.system-extension.uninstall</key>
	<true/>
	<key>com.apple.developer.driverkit.userclient-access</key>
	<array>
		<string>VSPDriver</string>
	</array>
	<key>com.apple.private.driverkit.driver-access</key>
	<array>
		<string>VSPDriver</string>
	</array>
	<key>com.apple.security.temporary-exception.iokit-user-client-class</key>
	<array>
		<string>IOUserUserClient</string>
	</array>
</dict>
</plist>
Accepted Answer

I'm develop a DriverKit driver extension and without entitlement checks by OS everything runs fine. But if the entitlements check is enabled in the NVRAM then I get an error due connecting my IOUserClient instance. Which entitlements are really and exactly required for my driver?

So, there are two different sides to this question. Starting with the DEXT:

  • DEXT have TOTAL control over who connects to them. The entitlement's here control who is allowed to "ask" to connect but the DEXT itself can accept or reject whatever it wants. Everything below ONLY controls that initial connection process, as the DEXT can always say "nah" and reject any connection it wants.

  • By default, DEXTs are only allowed to connect to processes signed by the same team as the DEXT. No entitlement is required for this, so if "your" DEXT will only talk to "your" app, then no entitlement is required.

  • On macOS, this can be expanded by using "com.apple.developer.driverkit.allow-any-userclient-access", which allows "any" process to attempt to connect.

  • On iPadOS, this can be expanded by using "com.apple.developer.driverkit.allow-third-party-userclients"

Now, moving to the connecting process:

  • By default, process are not allowed to connect to DEXTs, so without an entitlement the connection process will fail. I believe the enforcement mechanism is through the sandbox, so unsandboxed process maybe able to connect without the entitlement, but that's an edge case I'm not going to try and address.

  • On macOS, the "com.apple.developer.driverkit.userclient-access" entitlement defines exactly which DEXT a process can connect to. The value of that entitlement is an array of strings, where each entry in the array is the bundle ID of the DEXT that process can connect to.

  • On iPadOS, the "com.apple.developer.driverkit.communicates-with-drivers" entitlement allows an app to connect to user clients. No specific DEXT is specified on iPadOS.

Applying that specifically to macOS:

  1. Any app that want to talk to a specific DEXT needs to request "com.apple.developer.driverkit.userclient-access" with the bundle ID(s) of the DEXT they want to interact with.

  2. DEXT need no additional entitlement unless they want to accept connections from apps outside of their team.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

One small update to this. The impact of "com.apple.developer.driverkit.allow-any-userclient-access" is broader than I'd originally realized, as it actually removes the requirement that the connecting process include "com.apple.developer.driverkit.userclient-access". Note that "com.apple.developer.driverkit.allow-any-userclient-access" requires approval from Apple, just like "com.apple.developer.driverkit.userclient-access".

However, the dynamics of using the broader entitlement can still be complicated in a sandboxed apps, as there is also a sandbox prohibition that prevents "arbitrary" connection into IOKit. So the sandboxed app may still need some kind of entitlement to communicate with the DEXT.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Kevin Elliot said

By default, DEXTs are only allowed to connect to processes signed by the same team as the DEXT. No entitlement is required for this

I have not found this to be the case, at least not for communication between my CMIO Extension and my dext. The Extension must be sandboxed, and UserClient Access is required to escape the sandbox. UserClient Access is managed, so it isn't available to me yet for my dext's bundle ID. For development, I use allow-any-userclient-access in the dext, but still have to use com.apple.security.temporary-exception.iokit-user-client-class in the extension to escape its mandatory sandbox.

So, first off, if you'd like to see the DEXT entitlement check, the code is here in IOUserServer::serviceNewUserClient. I think I've generally described the check correctly, but it's also not the most straightforward code we've ever written.

By default, DEXTs are only allowed to connect to processes signed by the same team as the DEXT. No entitlement is required for this.

This could have been stated better, as it's really only relevant on iPadOS. Making the rules for iPadOS more explicit:

The DEXT then needs:

  1. If the DEXT only allows connections from its own team, no additional entitlement.

  2. If the DEXT wants to accept connections from other teams, it needs "com.apple.developer.driverkit.allow-third-party-userclients”.

I have not found this to be the case, at least not for communication between my CMIO Extension and my dext.

As I talked about above, there are two "sides" to this equation. This entitlement here:

For development, I use allow-any-userclient-access in the dext

..."opens up" your DEXT, allowing other processes to connect to it without any additional entitlement verification. That's actually why we included it as a dev-only entitlement— it allows you to develop a user client without needing special approval from us.

Switching to the user space side, the key issue here is this:

the extension to escape its mandatory sandbox.

The issue here is specifically your CMIO extension. If you look for "IOUserUserClient" in the file "/System/Library/Sandbox/Profiles/application.sb" (the base application sandbox profile), you'll find this line:

(allow iokit-open-user-client (iokit-user-client-class "IOUserUserClient"))

IOUserUserClient is the kernel class used to support DEXT user clients, so, by default, the app/user space side of the connection should work. You can try this for yourself (I actually just did) with "Communicating between a DriverKit extension and a client app.” I'm not sure why the sandbox is off in that sample, but if you enable it yourself, everything works fine.

In any case, the problem for your CMIO extension is that cmioextension.sb DOESN'T have the same allow, which then prevents your extension from connecting to your DEXT.

In any case, shifting back to here:

still have to use com.apple.security.temporary-exception.iokit-user-client-class

Above I said:

"By default, processes are not allowed to connect to DEXTs, so without an entitlement the connection process will fail."

...but, having looked at things more closely, that's not actually what's going on. The default is actually that "apps" CAN connect to DEXTs, with the kernel itself then accepting/denying the connection based on the entitlement configuration.

However, I think this also means that your final CMIO extension is going to end up being blocked by the same issue you're having now, so your shipping extension would need both com.apple.developer.driverkit.userclient-access AND com.apple.security.temporary-exception.iokit-user-client-class. Can you file a bug on this and then post the bug number back here? My intuition is that CMIO should adopt the same IOUserUserClient allow that application.sb uses, but it's possible there's something else going on here.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Which IOUserClient entitlements are really required?
 
 
Q