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)
PoC verification
Evidence pack
findings/poc/DF-5555 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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) andusb_template.c:162-164(call management: same with subtypeUDESCSUB_CDC_CM(1)β byte 4+= bInterfaceNumber). Struct layouts:sys/bus/u4b/usb_cdc.h(usb_cdc_union_descriptor5 bytes,usb_cdc_cm_descriptor5 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-314incrementingbInterfaceNumberseeded to 0xFF atusb_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 atusb_template.c:318-325with the CURRENT interface number, endpoint raws atusb_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.logshows exactly two corrupted bytes, deterministic across runs (run.log==run.2.log). - Activation of this template is privileged:
USB_SET_TEMPLATErequirescaps_priv_check_self(SYSCAP_NODRIVER)(sys/bus/u4b/usb_dev.c:1716-1721); the globalusb_templatedefault is set by thehw.usb.usb_templateboot tunable /hw.usb.templatesysctl (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
bDelayhint (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 (thememcpyatusb_template.c:131cannot 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 bystring_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_audiosets nogetVendorDesc(: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 headerbInCollection=2 == AS interface count,baInterfaceNr={1,2} == walk output (:96-99, confirmed by S6 checks), andwTotalLength0x00a9 == exact sum (169) of the 16 AC-interface descriptor lengths. S8 β everybSourceID/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 withUSB_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). sha2564d7cf91d169e6b5fd2b2bbc04538dfaa94fb88eac4841f882f00a73f2b78680eaudio_body.incβ 339 lines extracted from the guest's/usr/src/sys/bus/u4b/template/usb_template_audio.c(sha256 of sourcedd3d65e1β¦== audit tree)build.log,run.log,run.2.log,env.txtβ full untrimmed guest output;run.log == run.2.logfix.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_testableFix 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'
Confirmed kernel references
- sys/bus/u4b/template/usb_template.c:135
- sys/bus/u4b/template/usb_template.c:162
- sys/bus/u4b/template/usb_template.c:209
- sys/bus/u4b/template/usb_template.c:307
- sys/bus/u4b/template/usb_template.c:379
- sys/bus/u4b/template/usb_template.c:1050
- sys/bus/u4b/template/usb_template.c:1359
- sys/bus/u4b/usb_cdc.h:38
- sys/bus/u4b/usb_cdc.h:43
- sys/bus/u4b/usb_dev.c:1716
- sys/bus/u4b/usb_device.c:1727
- sys/bus/u4b/template/usb_template_audio.c:96
- sys/bus/u4b/template/usb_template_audio.c:141
- sys/bus/u4b/template/usb_template_audio.c:205
- sys/bus/u4b/template/usb_template_audio.c:273
- sys/bus/u4b/template/usb_template_audio.c:375
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).
No comments yet.