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

DF-5553's CDC-renumber misfire confirmed on the audio template: usb_make_raw_desc()'s (bLength>=5, type 0x24, subtype in {1,6}) heuristic fires on 9 of the 22 raw tables β€” AS_GENERAL bDelay corrupted 3->4 (iface 1) and 1->3 (iface 2) in every generated descriptor set; 7 interface-0 hits (bcdADC-hi, bUnitID/bSourceID) at zero delta today are silent audio-graph corruption under any nonzero-interface rebase

Summary

The core's shape-only CDC predicate fires on 9/22 audio tables: unlike phone, this template IS selectable in-tree (USB_TEMP_AUDIO) and generates bDelay 3->4 (iface 1) and 1->3 (iface 2) in every descriptor set; seven iface-0 hits (AC header + feature units) patch bcdADC-hi/bUnitID/bSourceID at +0 today - silent audio-graph corruption under any nonzero-interface rebase. Structural harness: sed-extracted the audited file verbatim from guest /usr/src (sha256 == audit tree), two-pass generator replay -> exactly 2 CORRUPTED bytes + 9-entry census, deterministic x2; class-gated core fix drops misfires 9->0 with S1-S8 all passing. In-bounds writes, root-only activation, gadget hardware required - Info, family sibling of DF-5553.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5555 Β· 12 files
FileTypeDescriptionSize
README.md β€” 2.8 KB ↓ raw
VERDICT.md β€” 8.8 KB ↓ raw
table_check.c β€” 24.3 KB view raw
audio_body.inc β€” 8.1 KB ↓ download
build.sh β€” 353 B view raw
run.sh β€” 210 B view raw
build.log β€” 252 B view raw
run.log β€” 8.1 KB view raw
run.2.log β€” 8.1 KB view raw
env.txt β€” 150 B view raw
fix.diff β€” 3.5 KB view raw
verdict.json β€” 7.5 KB view raw

DF-5555 β€” usb_template_audio.c CDC-renumber misfire census (pass 2)

File: sys/bus/u4b/template/usb_template_audio.c (396 LOC) Class: structural harness, same evidence family as DF-5553 (usb_template_phone.c). Deterministic, no guest state required beyond a compiler.

What this proves

The template core's usb_make_raw_desc() (sys/bus/u4b/template/usb_template.c:135-146,162-171) classifies CDC union / call-management descriptors only by (bLength >= 5, type == 0x24, subtype in {6, 1}). USB-Audio CS_INTERFACE descriptors share type 0x24 with those subtypes, so the heuristic fires on this template's tables too. Census (9 hits):

descriptor subtype true meaning iface delta
audio_raw_desc_0 (usb_template_audio.c:96) 0x01 AC header (bcdADC hi byte at [4]) 0 +0
audio_raw_desc_9..14 (lines 141-169) 0x06 feature units (bUnitID[3]/bSourceID[4]) 0 +0
audio_raw_desc_20 (line 205) 0x01 AS_GENERAL (bDelay at [4]) 1 +1: 0x03β†’0x04
audio_raw_desc_30 (line 273) 0x01 AS_GENERAL (bDelay at [4]) 2 +2: 0x01β†’0x03

Two real corruptions of generated descriptors (in-bounds; offset 4 < bLength 7), seven zero-delta misclassifications. No memory unsafety.

Build

cd findings/poc/DF-5555
./build.sh          # or: scp to the guest and run there (see VERDICT.md)

build.sh sed-extracts the audited file's body VERBATIM from the guest's /usr/src (sha256 must equal the audit tree's dd3d65e17a662a75d02ffada2158c72aef47359cf1dd9a07c4f489ea9e26a535) into audio_body.inc and compiles table_check.c (which #includes it) twice: stock, and -DFIX_CLASSGATE (fix.diff's class gate).

Run

./run.sh            # baseline replay + S1-S8 + fix-logic check

Expected output (decisive lines)

  HIT    audio_raw_desc_20 ... in iface 1: CDC-CM patch byte[4] (bDelay) delta=+1  <-- REAL DELTA
  HIT    audio_raw_desc_30 ... in iface 2: CDC-CM patch byte[4] (bDelay) delta=+2  <-- REAL DELTA
  MISFIRE ... byte[4] (bDelay) 0x03 -> 0x04  <-- CORRUPTED
  MISFIRE ... byte[4] (bDelay) 0x01 -> 0x03  <-- CORRUPTED
  -> census: 9 descriptor(s) satisfy the CDC predicate; 2 in nonzero interfaces ...
  ALL STRUCTURAL CHECKS PASSED
  MISFIRE lines under FIX_CLASSGATE: 0 (expected 0)

Exit 0; run.log and run.2.log are byte-identical (deterministic).

Everything else the template-family checklist hunts is verified negative by the same binary: S1 bLength==sizeof on all 22 raw tables, S2 all 9 pointer tables NULL-terminated without holes, S3 string descriptors self-sized, S4 full 0..255 string-index sweep clean, S5 no vendor/HID surface, S6 AC-header interface hardcodes match the numbering walk (bInCollection=2, baInterfaceNr={1,2}, wTotalLength=169 exact), S7 speed tables in-bounds/fail-closed, S8 audio-graph IDs all resolve.

VERDICT.md
↓ download raw

DF-5555 β€” VERDICT

Finding: usb_make_raw_desc()'s CDC renumbering heuristic misclassifies the audio template's USB-Audio CS_INTERFACE descriptors; two AS_GENERAL bDelay bytes are corrupted in the generated config descriptor (iface 1: 0x03β†’0x04, iface 2: 0x01β†’0x03), and seven more descriptors in interface 0 sit on the predicate with zero delta today.

Verdict: reproduced (deterministic structural replay against the guest's own sha-verified source; same evidence class as DF-5553). Impact: none for privilege/memory safety β€” see "Impact ceiling".

1. Root cause (path:line)

  • The core patcher discriminates CDC descriptors ONLY by generic shape: sys/bus/u4b/template/usb_template.c:135-137 (union: raw[0] >= 5 && raw[1] == UDESC_CS_INTERFACE(0x24) && raw[2] == UDESCSUB_CDC_UNION(6) β†’ bytes 3,4 += temp->bInterfaceNumber) and usb_template.c:162-164 (call management: same with subtype UDESCSUB_CDC_CM(1) β†’ byte 4 += bInterfaceNumber). Struct layouts: sys/bus/u4b/usb_cdc.h (usb_cdc_union_descriptor 5 bytes, usb_cdc_cm_descriptor 5 bytes, both __packed).
  • USB Audio 1.0 class-specific descriptors ALSO use type 0x24 with subtypes that collide: AS_GENERAL = 0x01, FEATURE_UNIT = 0x06 (sys/bus/u4b/template/usb_template_audio.c:205-208,273-276,141-169).
  • The interface numbering walk (usb_template.c:307-314 incrementing bInterfaceNumber seeded to 0xFF at usb_template.c:379) assigns: iface 0 = audio_iface_0 (AC, usb_template_audio.c:196), iface 1 = audio_iface_1_alt_0/1 (AS playback, :254/:263), iface 2 = audio_iface_2_alt_0/1 (AS record, :312/:321). Raw descs are scanned at usb_template.c:318-325 with the CURRENT interface number, endpoint raws at usb_template.c:209-215/:332-338.

2. Census (checklist item b) β€” what the harness proved

9 of the 22 raw tables satisfy the predicate:

table line subtype real meaning iface patched bytes delta
audio_raw_desc_0 :96 1 AC header 0 [4] = bcdADC hi +0
audio_raw_desc_9 :141 6 feature unit 0 [3]/[4] = bUnitID/bSourceID +0
audio_raw_desc_10 :146 6 feature unit 0 same +0
audio_raw_desc_11 :151 6 feature unit 0 same +0
audio_raw_desc_12 :156 6 feature unit 0 same +0
audio_raw_desc_13 :161 6 feature unit 0 same +0
audio_raw_desc_14 :166 6 feature unit 0 same +0
audio_raw_desc_20 :205 1 AS_GENERAL 1 [4] = bDelay 0x03β†’0x04 +1
audio_raw_desc_30 :273 1 AS_GENERAL 2 [4] = bDelay 0x01β†’0x03 +2

The CS_ENDPOINT descriptors (audio_raw_desc_22 :216, audio_raw_desc_32 :284, type 0x25) and all type-II/format descriptors (subtype 2) never match β€” verified by scanning endpoint ppRawDesc tables with the same interface number, exactly as the core does (usb_template.c:209-215).

All writes land at offset 4 (or 3+4) with bLength >= 5 checked first, inside the heap config buffer the core just memcpy'd the table into β€” no OOB write is possible from this bug; the const source tables are never written. This is deterministic descriptor-semantics corruption, not memory corruption.

The seven interface-0 hits are the family's "rebase hazard": the bytes the heuristic would patch are bUnitID/bSourceID (feature units) and bcdADC-hi (AC header). In-tree they get +0 because the AudioControl interface is numbered 0, but any future nonzero-interface embedding of these tables (composite gadget, reordered config) silently corrupts the audio graph identity fields instead of a harmless hint byte.

3. Why "reproduced / impact none"

  • The generated-vs-authored byte diff in run.log shows exactly two corrupted bytes, deterministic across runs (run.log == run.2.log).
  • Activation of this template is privileged: USB_SET_TEMPLATE requires caps_priv_check_self(SYSCAP_NODRIVER) (sys/bus/u4b/usb_dev.c:1716-1721); the global usb_template default is set by the hw.usb.usb_template boot tunable / hw.usb.template sysctl (root; sys/bus/u4b/usb_device.c:112-119; guest default observed: 0 = MSC), and generation only runs on a gadget-side device attach (usb_device.c:1727 β†’ usb_temp_setup_by_index, usb_template.c:1359-1361 β€” AUDIO, unlike PHONE, does have a case). The guest has no device-side USB controller, so no runtime EP0 path exists to observe; the structural replay against sha-identical source (dd3d65e1…, guest /usr/src == audit tree) is the honest maximum, per the family standard set by DF-5553.
  • Corruption ceiling: a malformed bDelay hint (and under rebase, bUnitID/bSourceID/bcdADC) in descriptors presented to a USB HOST by a root-configured gadget. No unprivileged trigger, no kernel memory effect, no host-side parser in this tree consumes the bytes (the corruption is visible to whatever host the gadget is plugged into). Hence Info severity, impact=none β€” identical classification to DF-5553.

4. Everything else the family checklist hunts β€” verified negative

Proven by the same binary (run.log, all PASS):

  • (a) Descriptor tables: S1 β€” bLength == sizeof(table) for all 22 raw tables (the memcpy at usb_template.c:131 cannot over-read); S2 β€” all 9 pointer tables (audio_raw_iface_0/1/2_desc, audio_raw_ep_1/2_desc, audio_iface_1/2_ep, audio_interfaces, audio_configs) are NULL-terminated with no interior hole.
  • (c) String tables: S3 β€” all five string descriptors self-sized and even; S4 β€” full 0..255 index sweep of audio_get_string_desc() (usb_template_audio.c:375-396): non-NULL exactly for index 0 (any lang) and 1..4 @ 0x0409, NULL otherwise β€” bounded by string_index < INDEX_AUDIO_MAX (:392), no OOB, designated initializers cover all 5 slots (:378-384), iInterface/iConfiguration indexes all in-range.
  • (d) Vendor/HID + iface hardcodes: S5 β€” usb_template_audio sets no getVendorDesc (:354-366); the core short-circuits to NULL (usb_template.c:1050-1060), so there is no vendor/HID length contract to violate. S6 β€” interface numbers hardcoded inside raw descriptors match the numbering walk: AC header bInCollection=2 == AS interface count, baInterfaceNr={1,2} == walk output (:96-99, confirmed by S6 checks), and wTotalLength 0x00a9 == exact sum (169) of the 16 AC-interface descriptor lengths. S8 β€” every bSourceID/mixer/selector pin in the AC interface resolves to a defined unit/terminal ID.
  • (e) Speed tables: S7 β€” audio_isoc_mps.mps[]/ audio_isoc_interval.bInterval[] indexed only with USB_SPEED_FULL/USB_SPEED_HIGH (< USB_SPEED_MAX, in-bounds; usb_template_audio.c:231-239); other speeds read 0 β†’ the core fails closed with USB_ERR_INVAL (usb_template.c:221-229).
  • No dynamic state, locks, refcounts, or copyin/copyout exist in this file (const tables + one pure callback) β€” the race/refcount/leak classes are structurally N/A; the EP0 length clamp (usb_handle_request.c:744-748, cited in DF-5553) covers the readback side for the whole family.

5. Fix and validation

fix.diff is the family fix authored in DF-5553 and re-validated here at table level: thread the containing interface's bInterfaceClass into usb_make_raw_desc() and gate the CDC union/CM renumbering on UICLASS_CDC/UICLASS_CDC_DATA interfaces. The -DFIX_CLASSGATE binary (identical harness with the gate compiled in) drops the misfires from 9 (two with nonzero bDelay deltas) to 0 while all S1-S8 checks still pass β€” with the gate, the audio template's generated descriptors equal the authored bytes by construction.

A kernel-level A/B build is not_testable for the same reason as DF-5553: observing the generated bytes at runtime requires a device-side USB controller that this guest (and no in-tree default config) provides, plus root to select the template; there is no observable kernel behavior to compare. The logic-level validation is complete and deterministic.

6. Artifacts

  • table_check.c β€” harness (verbatim body include + predicate/numbering replay + S1-S8). sha256 4d7cf91d169e6b5fd2b2bbc04538dfaa94fb88eac4841f882f00a73f2b78680e
  • audio_body.inc β€” 339 lines extracted from the guest's /usr/src/sys/bus/u4b/template/usb_template_audio.c (sha256 of source dd3d65e1… == audit tree)
  • build.log, run.log, run.2.log, env.txt β€” full untrimmed guest output; run.log == run.2.log
  • fix.diff β€” family class-gate fix (core-side)
  • manifest.json, verdict.json

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_testable
baseline reproduced→ patch + rebuild →patched clean

Fix LOGIC validated: table_check_fixed (identical harness with fix.diff's class gate compiled in) drops misfires from 9 (two with nonzero bDelay deltas) to 0 while all S1-S8 checks still pass. A kernel-level A/B build is not honestly testable: observing the generated descriptors at runtime requires a device-side USB controller plus root template selection (no such controller exists on the guest and none is configured in-tree by default), so no observable kernel behavior exists to compare baseline vs patched; with the class gate the generated descriptors equal the authored bytes by construction.

run.log (baseline: 9 HIT census lines, two '<-- REAL DELTA', MISFIRE 'bDelay 0x03 -> 0x04' / '0x01 -> 0x03' with '<-- CORRUPTED', then 'MISFIRE lines under FIX_CLASSGATE: 0 (expected 0)'); fix.diff; VERDICT.md 'Fix and validation'
↓ fix.diffper-fix-DF-5555

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log / run.2.log (decisive: 9 HIT census lines with the two '<-- REAL DELTA' entries, 2 MISFIRE lines with '<-- CORRUPTED' bDelay 0x03->0x04 and 0x01->0x03, 'ALL STRUCTURAL CHECKS PASSED', 'MISFIRE lines under FIX_CLASSGATE: 0'; logs byte-identical across runs); build.log (guest /usr/src source sha256 dd3d65e1... == audited tree; 339 lines extracted verbatim into audio_body.inc, BUILD_OK); VERDICT.md (full path:line narrative incl. census table and byte layouts); fix.diff; table_check.c (harness: verbatim-extracted body + faithful core predicate/numbering-walk replay + S1-S8)

PoC changes

No seed PoC existed (pass 2, no prior finding). Built the evidence from scratch, adapted from the DF-5553 family harness: verbatim #include of the audited file's body (sed from '^enum {' at line 58 to EOF, 339 lines) against a minimal shim of usb.h/usb_cdc.h/usb_template.h constants, plus (a) a byte-exact two-pass generator replay (memcpy + CDC union/CM/IAD patching + numbering walk incl. endpoint ppRawDesc scan order, usb_template.c:209-215,318-338) with a generated-vs-authored diff naming the corrupted field, (b) a full predicate census listing all 9 hits with field names and deltas (the DF-5553 harness printed hits but not the byte diff; the diff plus census together answer 'what bytes get corrupted' exactly), and (c) eight structural check families S1-S8 with audio-specific S5 (no vendor surface), S6 (AC-header bInCollection/baInterfaceNr/wTotalLength consistency with the numbering walk) and S8 (audio-graph ID resolution) replacing the phone's HID-specific checks. One harness iteration bug fixed en route (census printf format/argument mismatch -> segfault; corrected with per-branch printfs).

Verified recommended fix

Thread the containing interface's 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, same family fix as DF-5553).

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; nine of the audio template's 22 raw descriptors are misclassified. Two AS_GENERAL descriptors in non-zero interfaces get real deltas: audio_raw_desc_20 byte[4] (bDelay) 3->4 at interface 1 (usb_template_audio.c:205-208) and audio_raw_desc_30 byte[4] (bDelay) 1->3 at interface 2 (usb_template_audio.c:273-276). Seven interface-0 hits (AC header subtype 1 patching bcdADC-hi; six feature units subtype 6 patching bUnitID/bSourceID) get delta 0 today but are the rebase hazard: any nonzero-interface embedding of these tables turns them into silent audio-graph identity corruption. All writes land in-bounds inside the generated heap config buffer (offsets 3-4 < bLength 7/10; the const source tables are never written), so this is deterministic descriptor corruption, not memory corruption. Impact ceiling is a malformed bDelay hint presented to a USB host by a root-configured gadget (activation is root-only: USB_SET_TEMPLATE needs SYSCAP_NODRIVER, usb_dev.c:1716-1721; the hw.usb.template default is MSC; generation only runs on gadget-side attach, usb_device.c:1727 β€” USB_TEMP_AUDIO does have a by-index case at usb_template.c:1359, unlike PHONE), and the guest has no device-side USB controller, hence impact=none. Everything else the family checklist hunts is verified negative by the same harness (S1-S8: bLength==sizeof for all 22 raw tables, all 9 pointer tables NULL-terminated without holes, all string descriptors self-sized, 256-index callback sweep clean, no vendor/HID surface (getVendorDesc==NULL -> core returns NULL, usb_template.c:1050-1060), AC-header hardcodes bInCollection=2/baInterfaceNr={1,2}/wTotalLength=169 match the numbering walk exactly, speed tables in-bounds and fail-closed on unconfigured speeds).