β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5505

ucycom_attach pins the per-bus USB explore thread ~5 s per insertion via the HID report-descriptor fetch: an evil Earthmate NAKing (or slowly answering) GET_DESCRIPTOR(REPORT) stalls bus-wide device enumeration for the full USB_DEFAULT_TIMEOUT (family minimum: exactly one request)

Summary

The attach's first bus transaction is the synchronous descriptor fetch at the 5000ms default on the per-bus explore thread: an evil adapter answering at 4.9s makes the request SUCCEED while still pinning all bus enumeration (harness: 4798/4801ms succeeding, 4903/4910ms silent). Same mechanism as DF-5359/5488/5407; ucycom is the smallest member. Core-level 1000ms bound in usbd_req_get_report_descriptor benefits ums/ukbd/wsp/uhid/uaudio identically.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5505 Β· 14 files
FileTypeDescriptionSize
ucycom_attachstall_harness.c β€” 9.7 KB view raw
Makefile β€” 114 B ↓ download
extract.sh β€” 917 B view raw
build.sh β€” 205 B view raw
run.sh β€” 186 B view raw
build.log β€” 7.1 KB view raw
extract.log β€” 206 B view raw
run.log β€” 1.2 KB view raw
run.2.log β€” 840 B view raw
run.patchedkernel.log β€” 893 B view raw
fix_kernel.log β€” 349.3 KB ↓ download
fix.diff β€” 905 B view raw
env.txt β€” 360 B view raw
VERDICT.md β€” 4.8 KB ↓ raw
VERDICT.md
↓ download raw

DF-5505 VERDICT β€” ucycom_attach explore-thread stall (reproduced, mechanism-level)

Finding

ucycom_attach() (ucycom.c:202-285) runs in newbus attach context on the per-bus USB explore (hub event) thread when an Earthmate β€” the ONLY VID:PID in ucycom_devs[] (ucycom.c:177-179), VID 0x1163 DeLorme β€” is inserted. Its very first bus transaction is the synchronous HID report-descriptor fetch

error = usbd_req_get_hid_desc(uaa->device, NULL, &urd_ptr, &urd_len,
    M_USBDEV, UCYCOM_IFACE_INDEX);          /* ucycom.c:231-233 */

usbd_req_get_hid_desc (usb_hid.c:801-843) allocates a device-declared-length buffer and calls usbd_req_get_report_descriptor (usb_hid.c:834-835), whose terminal call is usbd_do_request(udev, lock, &req, d) (usb_request.c:1892) β€” the macro form of usbd_do_request_flags(…, USB_DEFAULT_TIMEOUT) with USB_DEFAULT_TIMEOUT = 5000 ms (usbdi.h:87, 511-512). An evil Earthmate that NAKs the control data stage pins the explore thread β€” and therefore bus-wide device enumeration for every device on that bus β€” for the FULL 5 s timeout per insertion; a device answering at just-under-timeout makes the request SUCCEED and pins the thread anyway. This is the DF-5359 (uipaq, 12.8 s)/DF-5407 (uplcom, 70 s)/DF-5488 (uvisor, 5–10 s) mechanism; ucycom is the family minimum: exactly ONE request, so the per-insertion cost is ~5 s.

The remainder of the attach path (three hid_report_size() calls + validation, ucycom.c:241-254) is CPU-only and measured negligible (the harness runs it verbatim against the real kernel hid_report_size, usb_hid.c:558).

Reproduction (KLD-harness-with-production-code, DF-5488 class)

The harness runs VERBATIM, sed-extracted at build time from the guest's md5-gated /usr/src (ucycom.c c7b432ce…, usb_hid.c 3329879b…):

  • usbd_req_get_hid_desc β€” usb_hid.c:801-843 (the core function ucycom calls);
  • the ucycom.c:229-254 attach EP0 block (fetch + error path + report-size validation) with locals named exactly as ucycom_attach declares them, running against the REAL kernel hid_report_size() with a synthetic 21-byte HID report descriptor (ilen=27, olen=27, flen=5 β€” passes the verbatim validation).

Interposed models only at the hardware boundary: usbd_req_get_report_descriptor (the EP0 sink whose real timeout is the finding), usbd_get_iface / usbd_get_config_descriptor / hid_get_descriptor_from_usb (fake 21-byte HID interface), device_printf (kprintf stand-in). The model sleeps min(evil_delay, core_cap) in the "WXFER" wait and answers or times out exactly as the real usb_request.c timeout loop would.

Measured (run.log, run.2.log β€” fresh guest)

  • Phase A (evil NAKs 4.9 s then answers β€” request SUCCEEDS, no error visible): explore-thread occupancy 4798 / 4801 ms, attach CONTINUES with validated report sizes i=27 o=27 f=5.
  • Phase B (evil silent): 4903 / 4910 ms (the 5000 ms USB_DEFAULT_TIMEOUT at hz=100 tick granularity), attach ABORTs through the verbatim error path (ucycom.c:235-240 β†’ 279-284).
  • Phase C (fix modeled: core cap 1000 ms): 990–1000 ms, fail-fast abort.

Fix validation

  • fix.diff bounds the report-descriptor fetch at usb_request.c:1892 (usbd_do_request_flags(…, 1000) instead of the 5 s default); git apply --check clean on the audited repo.
  • Compile-level: patched usb_request.c (md5 4755628a…) spliced into the guest tree, make nativekernel KERNCONF=X86_64_GENERIC NO_CLEAN=1 RC=0 in 146 s (fix_kernel.log), make installkernel + reboot into the patched kernel + both harnesses re-run unchanged (run.patchedkernel.log) β€” no regression.
  • Behavioral proof at mechanism level: phase C 4.9–5.0 s β†’ ~1.0 s.
  • The fix benefits the other usbd_req_get_hid_desc consumers equally (ums.c:444/616, ukbd.c:1033/1260, wsp.c:728, uhid.c:768, uaudio.c:6101) and bounds only an enumeration-time descriptor read, which any functional device serves in milliseconds.
  • Scope caveat: compile + mechanism level; no physical crafted Earthmate can be attached to this guest.

Bring-up note

The first DF-5505 harness revision panicked the guest (panic: lockmgr: LK_RELEASE: no lock held): the evil-device model called lksleep() on a lock it did not hold. The DF-5504 model never hit this because the taskqueue thread legitimately holds sc->sc_lock across callbacks. The model was fixed to acquire/release around the sleep exactly as the production requester does; the guest was reset (vm.sh reset with-src) and all reported numbers are from the fresh guest.

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched reproduced

fix.diff is git-apply-able against the audited repo (git apply --check passes; the guest's own patch(1) balked at the well-formed hunk so the host-built patched file -- md5 4755628a... -- was spliced byte-identically). Compile-level: the patched usb_request.c built into a full X86_64_GENERIC kernel on the guest (make nativekernel NO_CLEAN=1, RC=0, 146 s; fix_kernel.log), was installed with make installkernel, booted, and both harnesses re-ran unchanged on the patched kernel (run.patchedkernel.log) -- no regression. Behavioral proof at mechanism level: harness phase C (core cap 1000 ms) measures 990-1000 ms fail-fast vs 4.8-5.0 s baseline. Scope caveat: mechanism + compile level per the family evidence class; a physical crafted Earthmate cannot be attached to this guest.

fix.diff (sha256 c662e587eb8e1e579c6d36cd6090896c04da94c5f5b2b01fb3816fa3c4cf5197); fix_kernel.log (nativekernel RC=0, 145.79 real); run.patchedkernel.log (patched-kernel re-run); run.log/run.2.log 'UA5505[C]: attach ABORT ... 994/990/1000 ms' vs 'UA5505[A]: ... 4798/4801 ms' and 'UA5505[B]: ... 4903/4910 ms'; VERDICT.md 'Fix validation' section
↓ fix.diffper-fix-DF-5505

Confirmed kernel references

Detail

Exploit chain

attacker physically inserts an evil USB adapter presenting the DeLorme Earthmate VID:PID; uhub hands it to ucycom_attach on the per-bus USB explore thread; the device NAKs (or slowly answers) the GET_DESCRIPTOR(REPORT) control data stage; the synchronous 5 s-timeout request pins the explore thread ~4.8-5.0 s per insertion, stalling enumeration of every other device on that bus; repeatable at will by re-insertion

Evidence (decisive lines)

run.log/run.2.log lines 'UA5505[A]: attach CONTINUES ... after 4798 ms of explore-thread occupancy' (request SUCCEEDED), 'UA5505[B]: attach ABORT ... 4903 ms' (silent: full USB_DEFAULT_TIMEOUT), 'UA5505[C]: ... 994/990/1000 ms' (fix modeled); extract.log shows the verbatim sed-extraction of usb_hid.c:801-843 and ucycom.c:229-254 from the md5-gated guest tree; fix_kernel.log shows the patched usb_request.c building into a full kernel RC=0; VERDICT.md narrates the call chain with line cites and the bring-up panic note

PoC changes

No seed PoC existed; authored from scratch on the DF-5488 verbatim-extraction pattern (usb_hid.c:801-843 and ucycom.c:229-254 sed-extracted md5-gated from the guest's own /usr/src; usbd_req_get_report_descriptor/usbd_get_iface/usbd_get_config_descriptor/hid_get_descriptor_from_usb/device_printf interposed as models). Build fixups: add sys/malloc.h (nested-extern on kmalloc/kfree), usb_serial.h + usb_process.h + sys/condvar.h for the embedded struct types. First revision panicked the guest (model lksleeped on a lock it did not hold -> 'panic: lockmgr: LK_RELEASE: no lock held'); fixed to acquire/release around the WXFER sleep as the production requester does, guest reset with-src, and all reported numbers are from the fresh guest.

Verified recommended fix

Bound the enumeration-time report-descriptor fetch: usbd_do_request_flags(..., 1000) at usb_request.c:1892 instead of the 5 s USB_DEFAULT_TIMEOUT; full diff in fix.diff

Verdict

Mechanism-level reproduction with production code verbatim (DF-5488 evidence class): ucycom_attach() (ucycom.c:202-285) runs on the per-bus USB explore (hub event) thread at insertion of an evil DeLorme Earthmate (the only VID:PID in ucycom_devs[], ucycom.c:177-179) and its first bus transaction is the synchronous HID report-descriptor fetch at ucycom.c:231-233 -> usbd_req_get_hid_desc (usb_hid.c:801-843, run VERBATIM by the harness) -> usbd_req_get_report_descriptor, whose terminal usbd_do_request (usb_request.c:1892) carries the default USB_DEFAULT_TIMEOUT = 5000 ms (usbdi.h:87,511-512). The harness ran the VERBATIM core function plus the VERBATIM ucycom.c:229-254 attach block (against the real kernel hid_report_size, usb_hid.c:558) with only the EP0 sink modeled: an evil device that NAKs 4.9 s and then answers -- so the request SUCCEEDS and no error is visible anywhere -- pinned the simulated explore thread 4798/4801 ms per insertion with attach CONTINUING into the verbatim report-size validation (i=27 o=27 f=5); a fully silent evil device pinned it 4903/4910 ms (the full 5 s core timeout) before the verbatim error path aborts. This pins bus-wide device enumeration for every device on that bus (DF-5359/DF-5407/DF-5488 mechanism); ucycom is the family minimum with exactly one request (~5 s per insertion, vs uipaq 12.8 s Low and uplcom 70 s Medium). fix.diff bounds the fetch at usb_request.c:1892 (usbd_do_request_flags(...,1000)): git-apply-clean on the audited repo, compiled into a full kernel on the guest (make nativekernel NO_CLEAN=1, RC=0, 146 s, fix_kernel.log), installed and rebooted into with both harnesses re-run unchanged (run.patchedkernel.log), and modeled in harness phase C at 990-1000 ms fail-fast. The fix equally bounds the same fetch for ums/ukbd/wsp/uhid/uaudio, and only affects an enumeration-time descriptor read any functional device serves in milliseconds. No Cypress device model exists on the guest, hence the harness evidence class; availability-only impact, Low per family calibration.