Hello,
We are building USB passthrough for virtual machines on macOS 27 and would appreciate guidance from the AccessoryAccess / IOUSBHost / App Sandbox teams on the supported way to open a USB accessory's interfaces from a sandboxed helper process.
WHAT WE ARE BUILDING
Our app is a sandboxed macOS app that runs virtual machines. For guests that use Virtualization.framework, USB passthrough already works through VZUSBPassthroughDevice, using the AAUSBAccessory we get from AccessoryAccess.
For a second kind of guest we use our own virtual machine monitor, built on Hypervisor.framework. Each VM runs in its own helper process, a nested app bundle in Contents/Helpers that the main app launches with Process. The helper's entitlements are:
com.apple.security.app-sandbox = true
com.apple.security.inherit = true
com.apple.security.hypervisor = true
com.apple.vm.networking = true
The main app's entitlements include:
com.apple.security.app-sandbox = true
com.apple.developer.accessory-access.usb = true
com.apple.security.device.usb = true
(plus hypervisor, networking, files, application groups)
The helper emulates an xHCI controller for the guest and forwards the guest's USB transfers to the real device. We use the same AccessoryAccess hand-off the documentation describes for worker processes:
- The main app registers an AAUSBAccessoryListener with AAUSBAccessoryManager and shows the consent UI. Nothing changed here from the Virtualization.framework path.
- When the user attaches a device to a VM, the main app calls -[AAUSBAccessory createXPCRepresentation]. We send the bytes of that xpc_data object to the helper over our existing control channel. We use the bytes rather than an XPC connection because the representation contains only data, no Mach ports or file descriptors.
- The helper rebuilds the accessory with -[AAUSBAccessory initWithXPCRepresentation:] and calls -openWithServiceQueue:completionHandler:. This succeeds and returns an IOUSBHostDevice. In the IORegistry the device then has an AppleUSBHostFrameworkDeviceClient created by the helper.
- When the guest sends SET_CONFIGURATION, the helper calls -[IOUSBHostDevice configureWithValue:matchInterfaces:NO error:], which succeeds. It then finds the device's IOUSBHostInterface children in the IORegistry and opens each one with -[IOUSBHostInterface initWithIOService:options:queue:error:interestHandler:]. Bulk and interrupt transfers then go through IOUSBHostPipe (copyPipeWithAddress:, enqueueIORequestWithData:...).
THE PROBLEM
Steps 1-3 work, and so do control requests on the default pipe. The guest enumerates the device and loads its driver. Step 4 fails: every interface fails to open with
Failed to create IOUSBHostInterface. (kIOReturnInternalError, 0xe00002c9)
With no interfaces open there are no pipes, so the guest's driver fails as soon as it moves data. A USB flash drive reported "This device cannot start (Code 10)", and a USB Ethernet adapter reported "Network cable unplugged".
What we found:
-
An unsandboxed command-line tool, run while the helper held the device through AccessoryAccess, opened the same IOUSBHostInterface with no error. The IORegistry then showed an AppleUSBHostFrameworkInterfaceClient under the interface, created by that tool. So the kernel and the accessory state allow the open; the App Sandbox blocks it.
-
/System/Library/Sandbox/Profiles/application.sb contains:
(when (entitlement "com.apple.security.device.usb") (allow iokit-open-user-client (iokit-user-client-class "IOUSBDeviceUserClientV2" "IOUSBInterfaceUserClientV3")))com.apple.security.device.usb (which the app has) therefore covers only the legacy IOUSBLib user clients, not AppleUSBHostFrameworkInterfaceClient, the class IOUSBHostInterface uses. We found no rule for AppleUSBHostFrameworkDeviceClient either. We assume AccessoryAccess authorizes the device client when the accessory is opened, but nothing does the same for the accessory's interfaces.
-
Adding com.apple.security.device.usb to the helper itself doesn't work: the helper is killed at launch (exit status 5). We understand an inherited-sandbox helper may carry only a limited set of entitlements.
HOW WE WORKED AROUND IT
We added this entitlement to the main app. The helper inherits the app's sandbox, so it gets the exception too:
<key>com.apple.security.temporary-exception.iokit-user-client-class</key>
<array>
<string>AppleUSBHostFrameworkInterfaceClient</string>
</array>
With it, the helper opens the interfaces and passthrough works: the flash drive mounts in the guest, and the Ethernet adapter gets a DHCP address and passes traffic.
We would rather not ship a temporary-exception entitlement, especially since we also distribute through the Mac App Store.
QUESTIONS
-
Is opening an accessory's interfaces with IOUSBHostInterface the intended way to do I/O on a device obtained through AccessoryAccess? If so, what is the supported way to authorize AppleUSBHostFrameworkInterfaceClient for a sandboxed process, or a helper inheriting its sandbox, without a temporary exception?
-
Does AccessoryAccess grant access to an accessory's interfaces as well as the IOUSBHostDevice? For example, should a listener registered with interface matching criteria (AAUSBAccessoryMatchingCriteria initWithDeviceMatchingDictionary: interfaceMatchingDictionaries:...) be able to open those interfaces? Or is there an API we missed that returns IOUSBHostInterface or IOUSBHostPipe objects for an opened accessory?
-
Is passing the accessory to a helper as the bytes of -createXPCRepresentation, instead of through an XPC connection, supported? The documentation describes the representation as something sent "via XPC". Our helpers are plain child processes, not XPC services.
-
Should we use the legacy IOUSBLib interface API instead (IOUSBInterfaceInterface through IOCreatePlugInInterfaceForService, client class IOUSBInterfaceUserClientV3, which com.apple.security.device.usb allows)? Is that supported for a device AccessoryAccess has opened for exclusive access, and is it acceptable to mix IOUSBHostDevice from AccessoryAccess with IOUSBLib interface objects for the same device?
-
Would the temporary-exception entitlement above be accepted in App Store review for this case, or should we file a Feedback Assistant request for a dedicated entitlement or an API change?
Environment:
- macOS 27 (Golden Gate), Apple silicon
- Xcode with the macOS 27 SDK
- App and helper are signed with the same team; the helper is a nested app bundle in Contents/Helpers using com.apple.security.inherit
- Tested devices: a USB 3 flash drive (mass storage, Bulk-Only Transport) and a USB 2.5GbE Ethernet adapter (Realtek, vendor 0x0BDA, product 0x8156)
We can provide a minimal sample project that reproduces the IOUSBHostInterface failure, plus IORegistry dumps.
Thank you for your time.