USB_TEMP_PHONE gadget template is compiled and advertised but unreachable: missing case in usb_temp_setup_by_index()
Summary
USB_TEMP_PHONE=7 is a valid selection-enum value and usb_template_phone.c is compiled+linked into the shipped usb_template.ko (nm shows the exported descriptor) but the dispatcher has no case for it - index 7 falls to default USB_ERR_INVAL with the misleading 'maybe the module has not been loaded' hint. Guest-verified via KLD: setup_by_index(7) err=3 while direct usb_temp_setup(&usb_template_phone) succeeds FULL/HIGH and sweeps clean - the template is functional, only the dispatch case is missing. Add-the-case (or delete like upstream) fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5353 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| usb_template_fuzz.c | β | 15.1 KB | view raw | |
| Makefile | β | 209 B | β download | |
| build.sh | β | 178 B | view raw | |
| run.sh | β | 160 B | view raw | |
| build.log | β | 5.7 KB | view raw | |
| run.log | β | 2.8 KB | view raw | |
| run.2.log | β | 5.9 KB | view raw | |
| run.initial-expectation-fixes.log | β | 2.9 KB | view raw | |
| VERDICT.md | β | 6.9 KB | β raw | |
| env.txt | β | 195 B | view raw | |
| fix.diff | β | 434 B | view raw | |
| manifest.json | β | 1.2 KB | view raw | |
| verdict.json | β | 4.5 KB | view raw |
VERDICT β DF-5353 (usb_template.c pass 2)
Bottom line: sys/bus/u4b/template/usb_template.c is memory-safe against
the malicious-host threat model β a 187-million-request KLD sweep of the real
descriptor demux (all descriptor types Γ indices Γ langids Γ request types Γ
9 templates Γ 3 speeds) produced zero anomalies, zero panics, and the
two-pass descriptor builder proved byte-exact (independently recomputed sizing
== sum of served wTotalLengths). One Info-level functional defect was
reproduced: the USB_TEMP_PHONE gadget template is compiled into
usb_template.ko and advertised by the usb_ioctl.h enum, but
usb_temp_setup_by_index() has no case for it, so template selection 7 always
fails. One additional Info hardening note (scratch-union aliasing) is filed
without a pack.
What was verified on the guest and how
KLD harness (usb_template_fuzz.c, this directory) driving the shipped
usb_template.ko against a synthetic struct usb_device:
| Claim | Result | Evidence |
|---|---|---|
Dispatch indices bounded (config index vs bNumConfigurations, string id vs sibling tables, langid) |
Holds β 0 anomalies in 187,272,018 crafted requests | run.log, run.2.log anom=0 |
Served lengths consistent with served buffers (len==buf[0], config walk == wTotalLength, device 18B, qualifier 10B, string β€255B even) |
Holds | check_response()/walk_config() in harness; 0 anomalies |
| Two-pass build (size pass == fill pass) | Holds β sum(wTotalLength)+sizeof(usb_temp_data) == independent recomputation for every template/speed | sweep_template() size check; no SIZE MISMATCH lines |
| Invalid template indices rejected | Holds for 9..1023, 0x8000, 0xFF00, 0xFFFF (USB_ERR_INVAL, no state left behind) |
run logs |
USB_TEMP_PHONE (7) unreachable |
Reproduced β setup_by_index(7) β USB_ERR_INVAL (err=3), while direct usb_temp_setup(udev, &usb_template_phone) succeeds at FULL/HIGH and serves 42 valid descriptors (run.2.log phone lines, anom=0) |
run.initial-expectation-fixes.log line 2; run.2.log phone sweep lines |
Guest: DragonFly dfbsd 6.5-DEVELOPMENT #0 ... x86_64, usb compiled into
kernel, usb_template.ko from /boot/kernel. Three full sweep runs
(initial + 2 confirmation), no panic, guest still up; modules unloaded and
/tmp/utfz removed afterwards (guest returned to kldstat baseline).
Why the phone finding is only Info
usb_template_phone is dead code: it cannot be selected
(usb_temp_setup_by_index switch, sys/bus/u4b/template/usb_template.c:1346-1373,
has cases for MSC/CDCE/MTP/MODEM/AUDIO/KBD/MOUSE/SERIALNET but not
PHONE), so an operator setting hw.usb.usb_template=7 gets a silent failure
("maybe the USB template module has not been loaded", usb_device.c:1729).
No memory-safety, information-disclosure, or privilege impact: the error path
is a clean USB_ERR_INVAL. Fix is one line (add the case) or delete the
template (what upstream FreeBSD eventually did).
Negative results with weight (what was hunted and killed)
- OOB via host-controlled
wValue[0]config index βusb_temp_get_config_desc()bounds the index against the generatedbNumConfigurations(usb_template.c:1046-1049) and walks only kernel-writtenwTotalLengthfields; empirically exercised for every index 0..254 Γ every request type (0 anomalies). - String/langid dispatch β
usb_temp_get_string_desc()(usb_template.c:1095-1109) forwards hostwIndex/wValue[0]to the per-template getters; all nine getters bounds-checkstring_index < *_MAXagainst designated arrays and gate langid == 0x0409 (e.g.usb_template_msc.c:170-193); MTP's 0xEE/MS-OS paths return fixed static descriptors. Full index/langid sweep clean. - wTotalLength math / two-pass builder β sizing pass and fill pass walk
identical const template tables;
wTotalLengthis written as the exact chunk size (usb_template.c:398-403); proven equal to an independent recomputation for all 27 template/speed combos. - Registry/unload races β the publish-then-maybe-free window
(
usb_template_ptrset atusb_template.c:1288, freed at:1322on resolve failure) is unreachable by concurrent control requests: gadget ep0 transfers are only started byuhub_exploreβusbd_ctrl_transfer_setup(usb_hub.c:563-565), i.e. afterusb_alloc_device()(which contains the onlyusb_temp_setup_by_index_pcall site,usb_device.c:1727) completes and registers the device.usb_temp_unload()'s 1-secondtsleepgrace (usb_dynamic.c:117-130) is akldunload-time (root-only) hazard, not an attacker path. - Fragmented control transfers β the consumer's
max_len -= off(usb_handle_request.c:746) cannot underflow via template descriptors:control_remis decremented by bytes actually sent (usb_transfer.c:1608-1626), and every fragment serves the same deterministic descriptor, so cumulativeoffβ€ served length. The subdev-handlers that can change demux results mid-transfer use theoffset==0 ? payload : 0pattern (e.g.gadget/g_modem.c), keepingmax_len - off == 0on later fragments. usb_hw_ep_get_needsbounds βep_currbounded byep_end = ues->ep + USB_EP_MAX(usb_template.c:686,726-729);bmInAlloc/bmOutAllocare 2 bytes and every index derives fromUE_ADDR(0x0f)orbest_n < USB_EP_MAX/2(:586,642-651,760-786).- Kernel-side writes from host data β none:
usb_temp_get_desc()serves only READ requests; all SET requests stall to the vendor fallback (usb_template.c:1140-1216); MTP's vendor descriptor is gated onUT_READ_VENDOR_DEVICE+ fixed wIndex values returning static data.
Known-fragile-but-benign (filed as separate Info hardening note)
union usb_device_scratch (usb_device.h:171-179) overlays
struct usb_temp_setup (32 B) on usb_hw_ep_scratch_sub ep[0..1];
usb_hw_ep_resolve() memsets and repopulates the union
(usb_template.c:915-922) inside usb_temp_setup(). Today this is safe
only because uts->err is re-stored from the resolve return value
(:1298,1311) before every read and uts->buf/size are never read after
resolve begins β the harness's clean PASS runs (setup err==0 for all
FULL/HIGH template builds) confirm the current ordering holds.
Cosmetic noise (no finding)
Every template-setup failure path calls usb_temp_unsetup() with
usb_template_ptr == NULL β usbd_free_config_desc: nullpointer console
spew (usb_template.c:1322,1334-1339; usb_request.c:1318-1326). Observed
33Γ during the runs. Log noise only.
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
not_testableInfo-only functional fix; kernel rebuild not warranted. The fix was validated indirectly and empirically: the harness drives usb_temp_setup(udev, &usb_template_phone) directly - exactly what the added case invokes - and the phone template builds cleanly and serves valid descriptors at FULL/HIGH (run.2.log, anom=0), so the one-line case addition is proven behaviorally. fix.diff is git-apply-clean against sys/.
run.2.log: 'FUZZ: template phone speed 2 swept (calls=176868017 nonnull=355 ... anom=0)' and 'FUZZ: template phone speed 3 swept (... nonnull=380 ... anom=0)'; git apply --check findings/poc/DF-5353/fix.diff -> clean.
Confirmed kernel references
- sys/bus/u4b/template/usb_template.c:1341-1376
- sys/bus/u4b/usb_ioctl.h:56-67
- sys/bus/u4b/template/usb_template_phone.c:353
- sys/bus/u4b/template/Makefile:45
- sys/bus/u4b/usb_device.c:1727
Detail
Evidence (decisive lines)
run.initial-expectation-fixes.log line 2 ('setup_by_index(7) err=3'); run.2.log phone sweep lines + final 'FUZZ: PASS calls=187272018 nonnull=380 stalls=187271638 anomalies=0'; VERDICT.md table maps each claim to its decisive lines; fix.diff applies cleanly to sys/ (git apply --check).
PoC changes
No seed PoC existed - harness written from scratch for this pass-2 run. Iterations: (1) added generated opt_usb.h/opt_bus.h/bus_if.h/device_if.h for the out-of-tree KLD build; (2) mirrored usb_template.c's exact include order; (3) fixed variadic kprintf format under -Werror; (4) after run 1, corrected harness expectations: the template enum has NINE values (SERIALNET=8 is valid; my loop had assumed 0..7) and SUPER-speed setup legitimately fails USB_ERR_INVAL because template mps[] tables define FULL/HIGH only (usb_template.c:221-225); (5) added usb_template_phone to the direct-setup table for run 3 to prove the template itself is functional.
Verified recommended fix
Add the missing dispatch case: 'case USB_TEMP_PHONE: err = usb_temp_setup(udev, &usb_template_phone); break;' (or delete the dead phone template as upstream FreeBSD did).
Verdict
Info-level functional defect reproduced on the guest: usb_temp_setup_by_index() (sys/bus/u4b/template/usb_template.c:1346-1373) has no USB_TEMP_PHONE case, so template index 7 (enum value advertised in sys/bus/u4b/usb_ioctl.h:56-67; template compiled into usb_template.ko per template/Makefile and nm) always returns USB_ERR_INVAL - the shipped phone gadget is dead code. Verified empirically: setup_by_index(7) -> err=3 while a direct usb_temp_setup(udev, &usb_template_phone) succeeds at FULL/HIGH speed and serves internally consistent descriptors (run.2.log 'phone' sweeps, anom=0). Simultaneously, the pack is the pass-2 negative-evidence record for the whole file: 187,272,018 crafted host requests through the real descriptor demux (all descriptor types x indices x langids x request types x 9 templates x 3 speeds) yielded ZERO anomalies and no panic; the two-pass builder matched an independent size recomputation for every template/speed. No memory-safety, leak, or privilege impact; fix is a one-line case addition (fix.diff) or deleting the template as upstream FreeBSD did.
No comments yet.