Skip to content

Fix exFAT-on-GPT USB drives (and a UMS device-list use-after-free)#19

Open
crux161 wants to merge 5 commits into
averne:masterfrom
crux161:fix/exfat-gpt-usb
Open

Fix exFAT-on-GPT USB drives (and a UMS device-list use-after-free)#19
crux161 wants to merge 5 commits into
averne:masterfrom
crux161:fix/exfat-gpt-usb

Conversation

@crux161
Copy link
Copy Markdown
Contributor

@crux161 crux161 commented May 29, 2026

Summary

exFAT-formatted, GPT-partitioned USB drives would not stay mounted in SwitchWave: a drive could be detected but its volume never appeared (or appeared and vanished) in the media list, and the Settings pane could crash while a USB drive was connected.

Two independent root causes, fixed here:

1. GPT VBR probe issued reads at garbage LBAs (the headline bug)

exFAT volumes on GPT already mount fine via libusbhsfs's upstream Microsoft Basic Data Partition path. The problem was the relaxed-gpt-vbr-probe patch this project carries for libusbhsfs: it probed the VBR of any GPT entry with a non-empty type GUID. Some USB drives leave uninitialized/garbage entries in the GPT partition array (type GUID 0xF4-filled, lba_start = 0xF4F4F4F4F4F4F4F4), so the probe issued reads at absurd LBAs, which triggered an Invalid CSW BOT mass-storage reset (~9 s stall) and knocked flaky drives off the bus — the volume registered and then immediately disappeared.

Captured with a LIBUSBHSFS_DEBUG build (sd:/libusbhsfs.log):

usbHsFsMountParseGuidPartitionTableEntry -> Successfully registered FAT volume at LBA 0x800
usbHsFsMountParseGuidPartitionTableEntry -> Found unrecognized GPT partition type at LBA 0xF4F4F4F4F4F4F4F4. Probing VBR
usbHsFsScsiReceiveCommandStatusWrapper  -> Invalid CSW detected. Performing BOT mass storage reset.
usbHsFsManagerRemoveDisconnectedDriveContexts -> Removing drive context with ID ...

Fix: bounds-check entry_lba/lba_end against the logical unit's block_count before probing, so garbage entries are skipped while genuine non-Microsoft-GUID volumes are still detected. After the fix the same drive registers the FAT/exFAT volume and stays mounted, with no probe/reset/removal sequence. Playback from the exFAT key confirmed working.

The patch wiring is added to the Docker build, plus a LIBUSBHSFS_DEBUG switch (links -lusbhsfsd) to make sd:/libusbhsfs.log available for future diagnosis.

2. Use-after-free race in the UMS device list

libusbhsfs invokes the populate callback from its background hotplug thread, which rewrote UmsController::devices while the render thread read it (Settings USB table), freeing the device name/mount_name strings mid-copy — a data abort when opening/interacting with the Settings pane while a USB drive was present (same class as #18).

Fix: guard the device list with a mutex (populate_devices/unmount_device/set_devices_changed_callback take the lock, get_devices() returns a copy under it, finalize() snapshots before unmounting; callbacks run outside the lock). The Settings USB table is hardened to match: copy the list for rendering, route user-controlled strings through "%s" (device names come from USB descriptors and may contain %), and guard front() on a possibly-empty filesystem list.

Testing

  • Built via ./build-docker.sh (and LIBUSBHSFS_DEBUG=1 ./build-docker.sh).
  • exFAT/GPT USB drive ("USB DISK 3.0", ~62 GB) on firmware 21.1.0 / Atmosphère 1.11.1: volume now appears, stays mounted, and plays back.
  • sd:/libusbhsfs.log shows a clean registration with no 0xF4F4… probe / Invalid CSW / drive removal.

Notes

The bounds-check is fundamentally a libusbhsfs improvement; I'm also preparing an upstream PR to DarkMatterCore/libusbhsfs so the relaxed-but-bounded GPT probe can live there and this project can eventually drop the patch.

🤖 Generated with Claude Code

crux161 and others added 2 commits May 29, 2026 15:38
exFAT volumes on GPT-partitioned USB drives already mount through
libusbhsfs's upstream Microsoft Basic Data Partition path, but some
drives leave uninitialized/garbage entries in the GPT partition array
(type GUID 0xF4-filled, lba_start = 0xF4F4F4F4F4F4F4F4). The
relaxed-gpt-vbr-probe patch probed any non-empty type GUID, so it
issued reads at those absurd LBAs, triggering an "Invalid CSW" BOT
mass-storage reset (~9s stall) that knocked flaky drives off the bus:
the volume would register and then immediately disappear from the UI.

Bounds-check entry_lba/lba_end against the logical unit's block_count
before probing, so garbage entries are skipped while genuine
non-Microsoft-GUID volumes are still detected.

Also wire the patch into the Docker build and add a LIBUSBHSFS_DEBUG
switch (links -lusbhsfsd) for capturing sd:/libusbhsfs.log when
diagnosing mount issues.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
libusbhsfs invokes the populate callback from its background hotplug
thread, which rewrote UmsController::devices while the render thread
read it (settings USB table), freeing the device name/mount_name
strings mid-copy: a data abort when opening or interacting with the
settings pane while a USB drive was present.

Guard the device list with a mutex: populate_devices, unmount_device
and set_devices_changed_callback take the lock, get_devices() returns
a copy under it, and finalize() snapshots before unmounting. Callbacks
are invoked outside the lock to avoid re-entrancy.

Harden the settings USB table accordingly: copy the device list for
rendering, pass user-controlled strings through "%s" (the device name
comes from USB descriptors and may contain %), and guard front() on a
possibly-empty filesystem list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
crux161 and others added 3 commits May 29, 2026 16:31
Match the revised DarkMatterCore/libusbhsfs#32 after maintainer review:
move the LBA bounds check to the top of the entry parser (guarding every
branch), probe the EXT superblock as well as the VBR for unrecognized
type GUIDs, and reference the Windows Recovery Environment partition as
a concrete example of a standard filesystem behind a non-standard GUID.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reflow the two added comment blocks so each line is its own /* ... */,
matching the surrounding code style, per review on the upstream PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reword the comment on the catch-all branch to state that it only handles
entries with a non-empty, unrecognised type GUID (all-zero/empty entries
are skipped via the g_emptyPartitionGuid check), per upstream review.
@averne
Copy link
Copy Markdown
Owner

averne commented May 29, 2026

The problem was the relaxed-gpt-vbr-probe patch this project carries for libusbhsfs

?

The project carries no such patch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants