@@ -366,25 +366,55 @@ The original description follows.
366366All are cheap to fix with ` volatile ` . Practical impact is low (stale reads, not corruption), but
367367` isConnected() ` returning ` true ` long after unplug is user-visible.
368368
369- ---
370-
371- ## Open findings
369+ ## 9. ` messagePort ` (remote) creation is unchecked (macOS) — ** FIXED**
370+
371+ > ** Status:** Fixed on 2026-07-06. ` startAsyncIOThread ` now null-checks all * three* CF creations
372+ > that can return NULL — ` CFMessagePortCreateLocal ` , ` CFMessagePortCreateRunLoopSource ` , and
373+ > ` CFMessagePortCreateRemote ` (a NULL anywhere in the chain causes the same downstream damage,
374+ > e.g. when bootstrap-port registration fails in a sandboxed process) — and throws a ` UsbException `
375+ > naming the failed call. The remote port is assigned to the ` messagePort ` field only after the
376+ > check, so ` removeEventSource ` can never send on a NULL port.
377+ >
378+ > The naive throw would have traded the leak for a hang: the singleton would be stuck in state
379+ > ` STARTING ` and every later ` addEventSource ` call would block forever in ` waitForRunLoopReady ` .
380+ > The catch block therefore releases the partially created ports (` CFRelease ` ; releasing the last
381+ > reference also invalidates the local port and deregisters its name, so a retry can recreate it)
382+ > and resets the state to ` NOT_STARTED ` , so a later ` open() ` retries cleanly. The exception
383+ > surfaces from ` MacosUsbDevice.open() ` before anything is registered with the run loop.
384+ >
385+ > Verified with the full hardware test suite on macOS (59 tests, all passing); the failure paths
386+ > themselves require injected CF failures and are verified by inspection.
372387
373- ## 9. ` messagePort ` (remote) creation is unchecked (macOS)
388+ The original description follows.
374389
375390` MacosAsyncTask.java:135 ` : ` CFMessagePortCreateRemote ` can return NULL (e.g. a
376391race where the local port's run-loop source isn't serviced yet).
377392` removeEventSource ` would then send on a NULL port and the event source would
378393never be removed — a slow leak of run-loop sources. Worth asserting non-null at
379394creation.
380395
381- ## 10. Magic-number reinterpret in ` loadDescription ` (macOS)
396+ ## 10. Magic-number reinterpret in ` loadDescription ` (macOS) — ** FIXED**
397+
398+ > ** Status:** Fixed on 2026-07-06. The pointer returned by ` GetConfigurationDescriptorPtr ` is now
399+ > first reinterpreted to exactly ` ConfigurationDescriptor.LAYOUT.byteSize() ` (9 bytes) — the
400+ > minimum needed to read the header — then, after a sanity check that ` wTotalLength ` is at least
401+ > the header size (throwing a ` UsbException ` naming the bogus value otherwise), reinterpreted to
402+ > exactly ` totalLength ` . No magic number remains, and the view never claims memory beyond the
403+ > kernel buffer, which IOKit itself sized from the same ` wTotalLength ` field when caching the
404+ > descriptor. Verified with the full hardware test suite on macOS (59 tests, all passing) —
405+ > ` loadDescription ` runs during enumeration of every connected device.
406+
407+ The original description follows.
382408
383409` MacosUsbDevice.java:194 ` : ` dereference(descPtrHolder).reinterpret(999999) `
384410before re-slicing to ` totalLength() ` . It's bounded immediately afterward so it's
385411safe in practice, but the arbitrary size is fragile if a malformed descriptor
386412ever reports a ` totalLength ` beyond the real buffer.
387413
414+ ---
415+
416+ ## Open findings
417+
388418## 11. ` open() ` holds the device monitor across up to 4×90 ms sleeps (macOS)
389419
390420` MacosUsbDevice.java:103-135 ` is ` synchronized ` and sleeps while retrying
@@ -464,8 +494,7 @@ timeout rather than an infinite block).
464494
465495## Recommended priority
466496
467- Findings 1–8 are fixed (see above). Next up: the macOS message-port null check of
468- finding 9. Findings 10–12 are low-priority robustness notes.
497+ Findings 1–10 are fixed (see above). Findings 11–12 are low-priority robustness notes.
469498
470499The Linux fixes for findings 1–3 still need a hardware test run on a Linux machine, and the
471500finding 7 fix a hardware regression run on Windows.
0 commit comments