AccessoryAccess in a sandboxed helper: IOUSBHostInterface fails without a temporary-exception entitlement. Is there a supported way?

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:

  1. The main app registers an AAUSBAccessoryListener with AAUSBAccessoryManager and shows the consent UI. Nothing changed here from the Virtualization.framework path.
  2. 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.
  3. 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.
  4. 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

  1. 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?

  2. 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?

  3. 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.

  4. 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?

  5. 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.

Update: we found a configuration that works without the temporary-exception entitlement. We would still like to understand whether it is the intended approach, and why the modern IOUSBHost interface API is not usable here.

WHAT WORKS NOW

We kept AccessoryAccess and IOUSBHostDevice for the device itself, and switched only the interfaces and pipes to the IOUSBLib plug-in API:

  • The helper still rebuilds the AAUSBAccessory from its XPC representation and calls -openWithServiceQueue:completionHandler:. It uses the returned IOUSBHostDevice for control requests (-sendDeviceRequest:...) and for -configureWithValue:matchInterfaces:NO error:.
  • For each IOUSBHostInterface child of the device it now calls IOCreatePlugInInterfaceForService with kIOUSBInterfaceUserClientTypeID, queries kIOUSBInterfaceInterfaceID942, and calls USBInterfaceOpen. Async completions come through CreateInterfaceAsyncEventSource on a dedicated run loop thread.
  • Bulk and interrupt transfers use ReadPipeAsync and WritePipeAsync. Stall recovery uses ClearPipeStallBothEnds and alternate settings use SetAlternateInterface.

The main app's entitlements are now only the standard ones, plus com.apple.developer.accessory-access.usb and com.apple.security.device.usb. The helper inherits the app's sandbox. With this, a USB 3 flash drive mounts in the guest and a USB 2.5GbE Ethernet adapter (Realtek 0x0BDA:0x8156) gets a DHCP address and passes traffic.

We confirmed the cause twice:

  1. With the same entitlements (com.apple.security.device.usb, no temporary exception), the IOUSBHostInterface version of our helper still fails with "Failed to create IOUSBHostInterface" (kIOReturnInternalError, 0xe00002c9) for every interface. We retested this today on the same devices.

  2. A test tool running under a sandbox profile that allows only the two user-client classes application.sb grants for com.apple.security.device.usb:

    (deny iokit-open-user-client)
    (allow iokit-open-user-client
           (iokit-user-client-class "IOUSBDeviceUserClientV2"
                                    "IOUSBInterfaceUserClientV3"))
    

    On an interface of a device AccessoryAccess currently holds, -[IOUSBHostInterface initWithIOService:...] fails with the same error, while IOUSBLib's USBInterfaceOpen succeeds and reports the interface's pipes. Without the sandbox, both APIs open the interface.

So the difference is entirely the App Sandbox rule for AppleUSBHostFrameworkInterfaceClient.

FOLLOW-UP QUESTIONS

  1. Why does com.apple.security.device.usb allow IOUSBLib's interface user client but not AppleUSBHostFrameworkInterfaceClient, the client -[IOUSBHostInterface initWithIOService:...] creates? IOUSBHost is the API the documentation recommends for new code, and the one AccessoryAccess returns for the device. Is the missing sandbox rule intentional, or an oversight we should report through Feedback Assistant?

  2. Is there a supported way for a sandboxed app, or a helper inheriting its sandbox, to obtain an IOUSBHostInterface for an accessory it opened through AccessoryAccess, without a temporary-exception entitlement? For example, a planned API on AAUSBAccessory or IOUSBHostDevice that returns its interfaces.

  3. Is it supported long term to mix the IOUSBHostDevice returned by AccessoryAccess with IOUSBLib interface objects for the same device? Should we expect it to keep working, given that IOUSBLib is the older API?

  4. If IOUSBHostInterface does gain a sandbox rule later, we would prefer to move back to it. Is there anything we should watch for, such as a new entitlement name or a release in which the rule is added?

Thank you. We are happy to provide the test tool, the sandbox profile, and IORegistry dumps.

Sorry it did not work out in the later also. It did not run the latest binary. In both cases we need to add the temporary exception to get it work.

Is there a way i can get it work wihout temporary exceptiom

I created testflight version and ran it on developement machine(m5 macbook air) with the legacy approach and it worked. But when i test it on fresh m4 macbook air it is failing to connect the usb devices. Just for the update and current status of the bug.

AccessoryAccess in a sandboxed helper: IOUSBHostInterface fails without a temporary-exception entitlement. Is there a supported way?
 
 
Q