⬢ DragonFlyBSD Kernel Audit
DF-5553 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5553",
  "status": "reproduced",
  "reproduced": true,
  "impact": "none",
  "confidence": "certain",
  "verdict": "Structural (pass-2 family class, guest-run table replay): usb_make_raw_desc()s CDC renumbering heuristic (usb_template.c:135-146,162-171) discriminates CDC union/call-management descriptors only by (bLength>=5, type==0x24, subtype==6/1), which collides with USB-Audio CS_INTERFACE subtypes; five of the phone templates descriptors are misclassified. Three (AC header subtype 1, two feature units subtype 6, all in interface 0) get delta 0; two AS_GENERAL descriptors in non-zero interfaces get real deltas: phone_raw_desc_20 byte[4] (bDelay) 1->2 at interface 1 (usb_template_phone.c:162-170) and phone_raw_desc_30 byte[4] 1->3 at interface 2 (usb_template_phone.c:229-237). All writes land in-bounds inside the generated heap config buffer (the const source tables are never written; offsets 3-4 < bLength 7/10/11), so this is deterministic descriptor corruption, not memory corruption. Impact ceiling is a malformed bDelay hint presented to a USB host, and the template cannot even be activated in-tree (no USB_TEMP_PHONE case in usb_temp_setup_by_index, DF-5353; selection is root-only via USB_SET_TEMPLATE), hence impact=none. Everything else the family checklist hunts is verified negative by the same harness (S1-S7: bLength==sizeof for all 14 raw tables, all 11 pointer tables NULL-terminated without holes, all string descriptors self-sized, 256-index callback sweep clean, vendor-desc length contract exact at 39==wDescriptorLength, HID interface hardcode==3, speed tables in-bounds/fail-closed).",
  "exploit_chain": "",
  "evidence": "run.log / run.2.log (decisive: 5 MISFIRE lines with the two <-- CORRUPTED nonzero deltas, ALL STRUCTURAL CHECKS PASSED, MISFIRE lines under FIX_CLASSGATE: 0); build.log (guest /usr/src source sha256 6dac1ced... == audited tree; 359 lines extracted verbatim into phone_body.inc, BUILD_OK); VERDICT.md (full path:line narrative incl. byte layouts); fix.diff; table_check.c (harness: verbatim-extracted body + faithful core predicate replay)",
  "kernel_refs": [
    "sys/bus/u4b/template/usb_template.c:135",
    "sys/bus/u4b/template/usb_template.c:162",
    "sys/bus/u4b/template/usb_template.c:307",
    "sys/bus/u4b/template/usb_template.c:379",
    "sys/bus/u4b/template/usb_template_phone.c:107",
    "sys/bus/u4b/template/usb_template_phone.c:132",
    "sys/bus/u4b/template/usb_template_phone.c:162",
    "sys/bus/u4b/template/usb_template_phone.c:229",
    "sys/bus/u4b/template/usb_template_phone.c:287",
    "sys/bus/u4b/template/usb_template_phone.c:379",
    "sys/bus/u4b/template/usb_template_phone.c:397",
    "sys/bus/u4b/template/usb_template_phone.c:412",
    "sys/bus/u4b/usb_handle_request.c:744"
  ],
  "poc_changes": "No seed PoC existed (pass 2, no prior finding). Built the evidence from scratch: a harness that #includes the audited files body extracted verbatim at build time (sed from ^enum { to EOF) against a minimal shim of usb.h/usb_template.h/usb_cdc.h constants, replicating usb_make_raw_desc()s classification predicate and usb_make_interface_desc()s numbering walk; plus seven structural check families (S1-S7) covering the whole pass-2 template-family checklist, and a -DFIX_CLASSGATE variant validating fix.diffs logic (misfires 5 -> 0). Two build-environment fixes were needed: DragonFly uses sha256 not sha256sum, and the guests /tmp/df5553 needed mode 1777 after root created it.",
  "attempts": 3,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "runtime_sec": 240,
  "guest_dirty": 0,
  "build_cmd": "in-guest: cd /tmp/df5553 && ./build.sh  (sed-extract /usr/src/.../usb_template_phone.c lines 58-416 -> phone_body.inc; cc -O2 -Wall -o table_check table_check.c; cc -O2 -Wall -DFIX_CLASSGATE -o table_check_fixed table_check.c)",
  "run_cmd": "in-guest: cd /tmp/df5553 && ./run.sh  (baseline replay + S1-S7 + fixed-logic check; run twice: run.log, run.2.log)",
  "code_hash": "f5bb377ed750601c6a6693549f6314613a088e4759d4e8f5000291263bcc5bdd",
  "notes": "Evidence class per family standard: gadget-mode activation of this template is impossible in-tree (DF-5353: no USB_TEMP_PHONE case in usb_temp_setup_by_index, usb_template.c:1346-1373), so a guest KLD calling usb_temp_setup(udev,&usb_template_phone) would be the only runtime route and nothing in-tree exercises it; the structural replay against the guests own /usr/src (sha-proven identical) is the honest maximum. Misfire detail worth triage: the same predicate also fires on usb_template_audio.cs tables (same 0x24 subtype shapes) - any nonzero-interface rebase or composite embedding of these descriptor sets turns the 0-delta misfires into silent bUnitID/bSourceID/bcdADC corruption; fix.diff (class-gated CDC renumbering) closes the whole family. Also verified negative this pass: no sysctl-mutable template strings exist in this trees core (FreeBSDs later usb_template_vendor/product sysctls are absent), and usb_handle_request.c:744-748 clamps every template-supplied length to the EP0 wLength remainder.",
  "recommended_fix": "Thread the containing interfaces bInterfaceClass into usb_make_raw_desc() and gate the CDC union/call-management renumbering on UICLASS_CDC/UICLASS_CDC_DATA interfaces so audio CS_INTERFACE descriptors are emitted byte-exact (fix.diff).",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": true,
  "fix_patched_reproduced": false,
  "fix_verdict": "Fix LOGIC validated: table_check_fixed (identical harness with fix.diffs class gate compiled in) drops misfires from 5 (two with nonzero bDelay deltas) to 0 while all S1-S7 checks still pass. A kernel-level A/B build is not honestly testable: the phone template cannot be activated on any in-tree kernel (no USB_TEMP_PHONE case - DF-5353; USB_SET_TEMPLATE is caps-privileged), so no observable kernel behavior exists to compare baseline vs patched; with the class gate the generated descriptors equal the authored bytes by construction.",
  "fix_evidence": "run.log (baseline: 5 MISFIRE, two <-- CORRUPTED += 1 / += 2, then MISFIRE lines under FIX_CLASSGATE: 0 (expected 0)); fix.diff; VERDICT.md Fix and validation"
}