USB: claim mass-storage devices from a privileged helper (proposal) - #7842
Open
giovariot wants to merge 1 commit into
Open
USB: claim mass-storage devices from a privileged helper (proposal)#7842giovariot wants to merge 1 commit into
giovariot wants to merge 1 commit into
Conversation
USB redirection currently cannot take over a mass-storage device on macOS:
`libusb_claim_interface()` returns LIBUSB_ERROR_ACCESS for any device already
bound to a kernel driver, so the disk shows up in the guest without its data
path, or not at all.
This adds a small privileged LaunchDaemon (`UTMUSBHelper`) that does the
claim, talks to the app over XPC, and hands the traffic to libusbredirhost.
QEMU keeps running as a normal user process and only sees a socket, so
nothing about TCC, storage locations or the group container changes for it.
Measured with a controlled A/B comparison (same device, same unmount, same
binary, only the uid changes): as a normal user libusb refuses with "USB
device capture requires either an entitlement (com.apple.vm.device-access)
or root privilege"; as root it succeeds.
Two invariants the code enforces, both found the hard way:
* the volumes of the device are unmounted BEFORE the claim, never in
parallel and never after — a device claimed while macOS still has it
mounted means two owners for the same disk;
* on release the device is reset to force re-enumeration, even when the
claim failed — without it the device stays on the bus with no block
device, `diskutil` hangs, and the only way out for the user is pulling
the cable.
Not merge-ready as it stands: see the pull request description.
giovariot
marked this pull request as ready for review
August 31, 2026 22:23
Contributor
|
Please follow https://github.com/utmapp/UTM/blob/main/CONTRIBUTING.md specifically you must use |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft / RFC. This is a design proposal with a working reference implementation, not a merge-ready patch. I am opening it because the problem it solves comes up regularly and the diagnosis took a while to pin down — even if you decide not to take this approach, the findings may save someone else the same days.
The problem
On macOS, USB redirection cannot take over a mass-storage device.
libusb_claim_interface()returnsLIBUSB_ERROR_ACCESSfor any device already bound to a kernel driver, so the disk either does not appear in the guest or appears without a working data path.Measured with a controlled A/B comparison — same device, same unmount, same binary, only the uid changes:
USB device capture requires either an entitlement (com.apple.vm.device-access) or root privilegeWhat this adds
A small privileged LaunchDaemon,
UTMUSBHelper, registered throughSMAppService(macOS 13+). It performs the claim, speaks to the app over XPC, and pumps the traffic throughlibusbredirhost. QEMU keeps running as an ordinary user process and only ever sees a socket — nothing changes for it regarding TCC, storage locations or the group container.Two invariants the code enforces, both found the hard way:
diskutilhangs, and the only way out for the user is unplugging the cable.The honest caveat
Root is not the whole story: TCC is. Claiming a USB interface on a storage device goes through the
iokit-open-service IOUSBHostInterfacesandbox gate, which consults TCC. For a root process TCC also asks the system domain, and that domain can only answer if it can attribute the request to a responsible process that is a user-session app with Full Disk Access. A LaunchDaemon has no such responsible process, whatever its uid: the question becomesRemovableVolumes, TCC tries to forward it to a user agent it cannot reach (bootstrap look-up: No such process) and denies — silently.So this daemon works as designed only with the
com.apple.vm.device-accessentitlement, which is restricted. With a signing identity that has it, this is the clean shape: registration once, lifetime managed by launchd, no per-launch authorization prompt.Without that entitlement there is a workaround — having the app itself spawn the privileged worker via Authorization Services, so the worker inherits a responsible process in the user session and TCC does show the prompt. That variant is what I currently ship downstream; it is uglier (an authorization prompt, a root process spawned by the app) and I did not propose it here.
State of the patch
UTMUSBHelpertarget and a copy phase intoContents/Library/LaunchDaemons/are still needed