IOServiceAuthorize and USB device redirection on Mac Catalyst

Dear Apple Developer Support,

We develop a Mac Catalyst app that has the com.apple.vm.device-access entitlement. We are now able to redirect USB devices to a remote virtual machine using the SPICE remote desktop protocol.

We have run into a case where the only working solution we have found depends on an API that the SDK marks as unavailable for Mac Catalyst. We would like to understand the supported approach before submitting a build for review.

The app uses libusb's Darwin backend to capture a USB device before redirecting it. The capture sequence is the standard one:

  1. IOServiceAuthorize(service, kIOServiceInteractionAllowed)
  2. USBDeviceReEnumerate(device, kUSBReEnumerateCaptureDeviceMask)
  3. USBDeviceOpenSeize, then USBInterfaceOpen on the captured device

A) Behaviour without step 1

IOKitLib.h annotates IOServiceAuthorize with __API_UNAVAILABLE(ios, macCatalyst), so our first implementation skipped it on Catalyst and went straight to the capture re-enumeration. In that configuration:

  • The entitlement is present and readable via SecTaskCopyValueForEntitlement.
  • USBDeviceReEnumerate with kUSBReEnumerateCaptureDeviceMask returns kIOReturnSuccess.
  • The device does re-enumerate — it disappears and reappears on the host, and its volumes are unmounted.
  • The kernel drivers then re-attach, and the following USBInterfaceOpen fails with kIOReturnExclusiveAccess ("another process has device opened for exclusive access").

The capture reports success but does not actually take the device away from the system's drivers. This is reproducible with a USB mass-storage device (a flash drive with a mounted volume).

B) Behaviour with step 1

If IOServiceAuthorize is called before the re-enumeration, capture works correctly and USB redirection functions as expected.

To call it from a Catalyst target we had to work around the availability annotation by binding the symbol directly:

extern kern_return_t my_service_authorize(io_service_t service, uint32_t options) asm("_IOServiceAuthorize");

This compiles, links and runs. We note that IOKit.tbd in the macOS SDK exports _IOServiceAuthorize for the arm64-maccatalyst and x86_64-maccatalyst targets, which is why it links.

Our questions are as follows:

  1. With com.apple.vm.device-access granted, what is the supported way to authorize USB device capture from a Mac Catalyst app?
  2. If IOServiceAuthorize is the intended mechanism, is calling it from Catalyst as shown above acceptable for App Store review, given the __API_UNAVAILABLE(macCatalyst) annotation on its declaration?
  3. If it is not acceptable, is there a Catalyst-available equivalent? Or is the entitlement expected to make USBDeviceReEnumerate(kUSBReEnumerateCaptureDeviceMask) sufficient on its own? If the latter is the intent, the behaviour described above suggests it is not currently sufficient, and we would be glad to file a bug report with a reproducible case.

We would rather not ship an API usage that could be read as circumventing a platform availability annotation, which is why we are asking before submitting.

Thank you, Morpheusly Inc. development team

The app uses libusb's Darwin backend to capture a USB device before redirecting it.

As a quick aside, I believe libusb is using the old "IOUSBLib.h" API, which is one of our old CFPlugin-based user clients. It was replaced with the IOUSBHost framework, which is a FAR more pleasant API. The newer API won't change the issue at hand, but it's still what I would recommend using.

  1. With com.apple.vm.device-access granted, what is the supported way to authorize USB device capture from a Mac Catalyst app?
  2. If IOServiceAuthorize is the intended mechanism, is calling it from Catalyst as shown above acceptable for App Store review, given the __API_UNAVAILABLE(macCatalyst) annotation on its declaration?

Please file a bug on this and then post the bug number back here once it's filed. There isn't a Mac Catalyst API for this, but there should be, most likely by just exposing IOServiceAuthorize to macCatalyst.

In terms of what you should do today, I don't really have a great answer. Strictly speaking, I believe it's technically possible to embed a macOS app/executable inside a Mac Catalyst app bundle, but Xcode will fight that very hard, probably to the point you'd need to sign using codesign. However, having done all that work, it's very possible the App Store would reject it as malformed, even if the system itself would run it. More to the point, you're basically replacing one problem with another, which isn't really progress, and, even worse, mixing environments is more likely to cause problems than the manual reference.

For the moment, please get a bug filed on this, and I'll talk to the IOKit team and see what their thoughts are.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Hi Kevin,

Thanks for your thoughts on this. With the addition of an extern declaration like the following:

#define my_service_authorize IOServiceAuthorize

extern kern_return_t my_service_authorize(io_service_t service, uint32_t options) asm("_IOServiceAuthorize");

and then calling darwin_service_authorize() in place of IOServiceAuthorize() in libusb, the app builds and works without issue.

This strongly suggests that simply exposing IOServiceAuthorize to Mac Catalyst will do the job.

We looked for how to file a bug with the IOKit team, but unfortunately we were not able to find a good lead on how to accomplish the task. Could you please point us in the right direction?

Thanks again! Morpheusly Inc development team

This strongly suggests that simply exposing IOServiceAuthorize to Mac Catalyst will do the job.

Yeah, I don't think there's any technical reason IOServiceAuthorize can't be in Catalyst, it just "isn't". FYI, going over the entire API set to determine what should/should not be in Catalyst was a very big job done very quickly, and that led to some... odd choices. Notably, for awhile NSTask was NOT in Catalyst, while posix_spawn was.

We looked for how to file a bug with the IOKit team, but unfortunately we were not able to find a good lead on how to accomplish the task. Could you please point us in the right direction?

I thought I included that link...

Anyway, there's a guide here which covers "everything". I'm not sure what the component choices are these days, but just pick something that seems reasonable. Once you post the number back here, I can sort out any issues.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware