udav_attach() failure paths call udav_detach() on a never-attached usb_ether -> deterministic kernel panic at evil-USB plug-in (lockmgr through NULL ue_lock; latent ether_ifdetach-on-unattached ifnet corruption on the second branch)
Summary
Sixth instance of the DF-5334 uether class: both attach failure branches goto the full detach with ue fields assigned only AFTER the failing call - the device-controlled usbd_transfer_setup failure (evil adapter with a udav_devs[] VID:PID missing the bulk endpoints) runs uether_ifdetach on an entirely-zeroed usb_ether -> lockmgr(NULL) Fatal trap 12 VA 0; the uether_ifattach-failure branch ether_ifdetaches a never-attached ifnet (ifindex2ifnet[0]/ifnet-list corruption). KLD-harness-verified: exact post-failure state replay panics at lockmgr_exclusive+0x5d 1/1; detach_usb rerouting + class-wide ue_lock guard fix validated (SURVIVED 2/2, clean loads).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5411 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 4.4 KB | β raw | |
| VERDICT.md | β | 6.6 KB | β raw | |
| udav_attach_poc.c | β | 5.3 KB | view raw | |
| Makefile | β | 149 B | β download | |
| build.log | β | 5.8 KB | view raw | |
| panic.txt | β | 1.0 KB | view raw | |
| run.log | β | 1.9 KB | view raw | |
| fix_run.log | β | 1.9 KB | view raw | |
| fix_uether_build.log | β | 7.3 KB | view raw | |
| fix_udav_build.log | β | 894 B | view raw | |
| fix_build.log | β | 8.2 KB | view raw | |
| env.txt | β | 686 B | view raw | |
| fix.diff | β | 1.7 KB | view raw | |
| manifest.json | β | 1.4 KB | view raw | |
| verdict.json | β | 7.6 KB | view raw |
DF-5411 β if_udav.c attach-failure DF-5334-class panic (goto-detach-on-unattached)
What
udav_attach() (sys/bus/u4b/net/if_udav.c) routes its device-controlled
failure branch through the full udav_detach() on a usb_ether that was
never attached β identical class to DF-5334 (if_cdce), DF-5384 (if_aue),
DF-5395 (if_axge), DF-5397 / DF-5399 (if_urndis / if_mos):
usbd_transfer_setup()fails (missing bulk endpoints on an otherwise matching DM9601-class device) β if_udav.c:265-268goto detach(:267) β if_udav.c:293-294udav_detach(dev).- At that point
ue->ue_sc/ue_dev/ue_udev/ue_methods/ue_lockare ALL still zero β they are assigned only at if_udav.c:280-283, after the failing call (thelockinit(&sc->sc_lock)at :260 initializessc_lock, notue->ue_lock). udav_detach()(if_udav.c:298-309) callsuether_ifdetach(ue)(if_udav.c:305) βsys/bus/u4b/net/usb_ethernet.c:268-305:usb_proc_drain()no-ops on the zeroedue_tq(usb_process.c:401-412);uether_getifp()returns the embedded arpcom ifnet (usb_ethernet.c:134), never NULL β theif (ifp != NULL)guard is dead code; thenUE_LOCK(ue)(usb_ethernet.c:281, macro at :58) =lockmgr(NULL, LK_EXCLUSIVE)β Fatal trap 12, fault VA 0x0.
The second failure branch (uether_ifattach() error, if_udav.c:285-288 β
reachable only on usb_proc_create() ENOMEM, not attacker-timed) leaves
ue_lock valid but a never-if_attach()ed ifnet, and reaches
ether_ifdetach() (usb_ethernet.c:294) β if_detach() on an unattached
ifnet β ifindex2ifnet[0] nulling + TAILQ_REMOVE of a never-inserted ifnet
from the global ifnet list β latent corruption pre-empted in branch 1 by the
NULL-write fault.
Threat model: attacker-supplied USB device (evil adapter / evil dock / VM
USB attach) presenting one of the 6 VID:PID pairs in udav_devs[]
(if_udav.c:205-218; e.g. ShanTou DM9601 0x0fe6:0x8101, Corega FEther
USB-TXC 0x07aa:0x0004, Kontron JP1082) at config index 0 / iface 0
without the expected bulk-IN/OUT endpoints. Not remotely triggerable;
requires physical/VM USB access.
Reproduce (evidence class: KLD harness driving production code)
QEMU has no DM9601 device model. The harness udav_attach_poc.c replays
udav_detach() verbatim (its production calls, in order) on the exact
state udav_attach()'s transfer-setup failure branch produces, using the
production usbd_transfer_unsetup() and uether_ifdetach() (exported
by uether.ko). The only non-production code is the static
udav_attach/udav_detach frame itself (static in if_udav.c, reachable
only via the goto with a physical device present).
# build (in guest, source pack at /root/df5411)
cd /root/df5411 && make -m /usr/src/share/mk
# BASELINE run (stock /boot/kernel/uether.ko auto-loaded by MODULE_DEPEND)
kldload -v ./udav_poc.ko
# expected: 3 marker lines, then
# Fatal trap 12: page fault while in kernel mode
# fault virtual address = 0x0
# current process = ... (kldload)
# Stopped at lockmgr_exclusive+0x5d: orl (%r14),%esi
# guest dead at db> β record: panic.txt
# PATCHED run (fix.diff applied to /usr/src, uether.ko rebuilt + installed)
kldload -v ./udav_poc.ko
# expected: 4th marker "udav_poc: SURVIVED uether_ifdetach()", RC=0,
# 0 Fatal traps, clean kldunload; patched if_udav.ko loads/unloads
# record: run.log
Fix validation
fix.diff (git-apply-able against clean sys/):
(a) uether_ifdetach() early-returns when ue->ue_lock == NULL
(class-wide, same as the DF-5334/5384 validated fix);
(b) udav_attach() routes both pre-attach failures to a detach_usb:
label doing only usbd_transfer_unsetup() + lockuninit() β (b) is
required for the branch-2 ether_ifdetach-on-unattached corruption, which
(a) does not cover, and it also removes the now-dead detach: label.
Both uether.ko and if_udav.ko rebuilt -Werror clean in-guest; patched
uether.ko installed to /boot/kernel; identical harness run SURVIVED.
Files
udav_attach_poc.c+Makefileβ the harnessbuild.logβ harness build (final, -Werror clean)panic.txtβ baseline Fatal trap 12 record (serial console)run.log/fix_run.logβ patched run: SURVIVED, RC=0, 0 trapsfix_uether_build.log,fix_udav_build.log,fix_build.logβ patched module buildsenv.txtβ guest uname, cc, kldstat, patch confirmationfix.diffβ the validated fixVERDICT.md,manifest.json,verdict.json
DF-5411 VERDICT β REPRODUCED (panic), fixed
Finding. udav_attach() (sys/bus/u4b/net/if_udav.c) has the DF-5334
"goto-detach-on-unattached" pattern: the usbd_transfer_setup() failure
branch (if_udav.c:265-268) and the uether_ifattach() failure branch
(if_udav.c:285-289) both jump to detach: (if_udav.c:293-295) and call the
full udav_detach(), which calls uether_ifdetach(&sc->sc_ue)
(if_udav.c:305) on a usb_ether that was never attached.
Why it faults. On the transfer-setup failure path, every ue field that
uether_ifdetach() needs is still zero because they are assigned only at
if_udav.c:280-283 β after the failing call at if_udav.c:263-264:
usb_proc_drain(&ue->ue_tq)(usb_ethernet.c:273) returns immediately (up->up_lock == NULL, usb_process.c:401-412 β its own comment says a zeroed struct makes it a no-op).ifp = uether_getifp(ue)(usb_ethernet.c:276) returns&ue->ue_arpcom.ac_ifβ an embedded member, never NULL β so theif (ifp != NULL)guard at :278 is dead code (same as DF-5334/5384).UE_LOCK(ue)(usb_ethernet.c:281, macro at :58) islockmgr(ue->ue_lock /* NULL */, LK_EXCLUSIVE)β read at VA 0x0 β Fatal trap 12.
The second branch (uether_ifattach error) leaves ue_lock valid but the
ifnet never if_attach()ed; uether_ifdetach() proceeds to
ether_ifdetach() (usb_ethernet.c:294) β if_detach() on if_index 0 β
nulls ifindex2ifnet[0] (lo0) and TAILQ_REMOVEs a never-inserted ifnet
from the global ifnet list (sys/net/if.c) β latent list corruption
pre-empted by the NULL fault in branch 1. That branch needs
usb_proc_create() to fail (ENOMEM), so it is not attacker-timed; the
transfer-setup branch is fully device-controlled: any USB device exposing
one of the 6 udav_devs[] VID:PID pairs (if_udav.c:205-218; e.g. ShanTou
DM9601 0x0fe6:0x8101, Corega FEther USB-TXC 0x07aa:0x0004, Kontron JP1082)
at config 0 / iface 0 while omitting the bulk-IN/OUT endpoints makes
usbd_transfer_setup() fail (USB_ERR_NO_ENDPOINT) at plug-in.
Verification (evidence class: KLD harness driving production code β
QEMU has no DM9601 device model; udav_attach/udav_detach are static and
reachable only with hardware; aue DF-5384 / axge / mos precedents):
- The harness (
udav_attach_poc.c) constructs the exact post-failure state (zeroedstruct udav_softc+lockinit(&sc->sc_lock)per if_udav.c:260 β the only softc initialisation before the failing call) and replaysudav_detach()verbatim: the productionusbd_transfer_unsetup()then the productionuether_ifdetach(). - Baseline (pristine
with-srcguest, stock/boot/kernel/uether.koauto-loaded byMODULE_DEPEND): the three markers print, thenFatal trap 12: page fault while in kernel mode, fault virtual address = 0x0,current process = 1065 (kldload),Stopped at lockmgr_exclusive+0x5d: orl (%r14),%esiβ guest dead atdb>. Record:panic.txt. 1/1 run (deterministic NULL deref; DF-5334/5384 proved the same sink in-situ). - Patched (
fix.diffapplied to guest/usr/src;uether.ko+if_udav.korebuilt-Werrorclean; patcheduether.kointo/boot/kernel, md5-verified against the patched build): identical harness run printsudav_poc: SURVIVED uether_ifdetach()(the guard's early return),KLDLOAD_RC=0,dmesg | grep -c 'Fatal trap'= 0,kldunloadclean, and the patchedif_udav.kodriver module loads/unloads clean (2/2 runs). Records:run.log,fix_uether_build.log,fix_udav_build.log.
Impact. Plug-time kernel NULL dereference (panic/DoS) from a malicious USB device β same severity tier and threat model as DF-5334/5384/5395/5397/ 5399 (Medium, physical/VM USB access). NOT memory corruption with attacker-controlled data: the fault address is fixed VA 0; the only "dereference" is the lockmgr read of the lock word at NULL. No userβroot route. The latent branch-2 second stage (ifindex/ifnet-list corruption) is likewise plug-time corruption at fixed addresses β DoS.
Why this is a separate finding from DF-5334/5384/5395/5397/5399. Those
were filed against if_cdce.c / if_aue.c / if_axge.c / if_urndis.c /
if_mos.c; their persisted fixes were applied in-guest for validation only
and then reverted β the tree still carries the unguarded
uether_ifdetach, and every uether driver's attach-failure path is an
independent instance. This finding covers the if_udav.c instance (root
cause identical, trigger device-controlled the same way).
Fix. fix.diff: (a) uether_ifdetach() early-returns when
ue->ue_lock == NULL (class-wide, matches the DF-5334/5384 validated fix);
(b) udav_attach() routes both pre-attach failures to a detach_usb:
label doing only usbd_transfer_unsetup() + lockuninit() and drops the
now-dead detach: label β (b) is required for the branch-2
ether_ifdetach-on-unattached corruption, which (a) does not cover.
Validated in-guest: baseline panics, patched survives, driver module clean.
Pass-2 negative results (checked and killed).
- RX pseudo-header/m_adj class (DF-5396 in urndis): udav BULK_DT_RD bufsize
is MCLBYTES+3 (if_udav.c:124); actlen β€ 2051, guard
actlen < sizeof(stat)+ETHER_CRC_LEN (:662) ensures payload β₯ 4;
len = min(actlen-3, le16toh(stat.pktlen)) - 4 β€ MCLBYTES-4 = 2044
(:669-670). A device-supplied pktlen < 4 makes len negative β passed
to uether_rxbuf(unsigned) it wraps to β₯ 0xFFFFFFFC and is rejected by
the usb_ethernet.c:589 guard (len > MCLBYTES-ETHER_ALIGN) β no OOB, the
frame is just dropped. Copy lands at m_adj(ETHER_ALIGN) (=2) into a
fresh 2048-byte cluster: 2+2044 = 2046 β€ 2048. No DF-5396 instance.
- TX: m->m_pkthdr.len clamped to MCLBYTES (if_udav.c:594-595),
temp_len β€ MCLBYTES+2 = bufsize (:114, :602-611), copy at offset 2 β
exact fit; usbd_frame_zero range within budget (:617-618).
- MII/register helpers: reg & UDAV_EPAR_EROA_MASK (5-bit) at :813-814/
:853-854; multicast hash h = crc32>>26 β€ 63 into 8-byte table (:521-523).
(The udav_csr_read1() uninitialized-return-on-error at :395-402 is filed
separately as DF-5412, Info β it feeds device register writes only.)
- udav_reset() retry loop (:484-489): device can hold RST for
1000 Γ hz/100 = 10 s under sc_lock β bounded plug-time stall, DoS
ceiling too low to file (same code ships upstream).
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
fixedfix.diff applied in-guest to /usr/src (patch -p1, both files; first variant kept the dead detach: label and tripped -Werror=unused-label, final variant drops it); uether.ko and if_udav.ko rebuilt -Werror clean; patched uether.ko installed into /boot/kernel (md5 verified against patched build); identical harness re-run: 'udav_poc: SURVIVED uether_ifdetach()', KLDLOAD_RC=0, dmesg|grep -c 'Fatal trap' = 0, kldunload clean, and the patched if_udav.ko driver module loads/unloads clean (2/2 runs). Baseline panic record in panic.txt, patched survival in run.log/fix_run.log. fix.diff verified git-apply-able against the clean read-only sys/ tree.
run.log / fix_run.log (SURVIVED marker + KLDLOAD_RC=0 + kldstat + UNLOAD_OK + Fatal-trap count 0 + if_udav.ko LOAD/UNLOAD OK + md5 match of installed uether.ko); fix_uether_build.log + fix_udav_build.log + fix_build.log (-Werror clean); panic.txt (baseline trap on identical input); fix.diff (git apply --check OK on clean tree)
Confirmed kernel references
- sys/bus/u4b/net/if_udav.c:260
- sys/bus/u4b/net/if_udav.c:263-268
- sys/bus/u4b/net/if_udav.c:280-283
- sys/bus/u4b/net/if_udav.c:285-289
- sys/bus/u4b/net/if_udav.c:293-295
- sys/bus/u4b/net/if_udav.c:298-309
- sys/bus/u4b/net/if_udav.c:205-218
- sys/bus/u4b/net/usb_ethernet.c:58
- sys/bus/u4b/net/usb_ethernet.c:134
- sys/bus/u4b/net/usb_ethernet.c:162-186
- sys/bus/u4b/net/usb_ethernet.c:268-305
- sys/bus/u4b/usb_process.c:397-412
Detail
Evidence (decisive lines)
panic.txt (baseline serial: 3 udav_poc markers -> Fatal trap 12, fault virtual address = 0x0, current process 1065 (kldload), Stopped at lockmgr_exclusive+0x5d: orl (%r14),%esi, db> prompt); run.log (patched run: SURVIVED marker, KLDLOAD_RC=0, kldstat, UNLOAD_OK, dmesg Fatal-trap count 0, patched if_udav.ko loads/unloads clean, md5 match /boot/kernel/uether.ko vs patched build); build.log + fix_uether_build.log + fix_udav_build.log (-Werror clean builds); env.txt (guest uname/cc/kldstat + in-guest patch line confirmation); fix.diff (git-apply-able against clean sys/, git apply --check OK)
PoC changes
No seeded PoC existed. Wrote udav_attach_poc.c from scratch (aue DF-5384 harness pattern): KLD harness (MODULE_DEPEND uether) that kmallocs a zeroed struct udav_softc, lockinit()s sc->sc_lock exactly as if_udav.c:260 does (the only softc initialisation before the failing usbd_transfer_setup() at :263), prints the zeroed ue state, then replays udav_detach() verbatim: production usbd_transfer_unsetup(sc->sc_xfer, UDAV_N_TRANSFER) (if_udav.c:304) + production uether_ifdetach(&sc->sc_ue) (if_udav.c:305). First build hit -Werror=cast-qual printing ue_methods with (void*) cast - fixed by printing the const pointer directly. Physical DM9601 emulation is infeasible in QEMU (no device model); static udav_attach/udav_detach cannot be linked against, so the branch-1 state + exact call-sequence replay is the strongest runnable evidence and uses only production code after the goto.
Verified recommended fix
udav_attach(): route both pre-attach failures to a detach_usb label doing only usbd_transfer_unsetup()+lockuninit() (drop the dead detach: label); uether_ifdetach(): early-return when ue->ue_lock==NULL (class-wide, same as DF-5334/5384 fix)
Verdict
Deterministic kernel panic at USB device plug-in: udav_attach()'s usbd_transfer_setup() failure branch (if_udav.c:265-268 'allocating USB transfers failed') goto-jumps into the full udav_detach() (if_udav.c:293-295) while the embedded usb_ether is still entirely zeroed (ue_sc/ue_dev/ue_udev/ue_methods/ue_lock are assigned only at if_udav.c:280-283, AFTER the failing call at :263-264; the lockinit at :260 initialises sc_lock, not ue->ue_lock). uether_ifdetach() (if_udav.c:305) then lockmgr()s through the NULL ue_lock (Fatal trap 12, fault VA 0x0, Stopped at lockmgr_exclusive+0x5d) - the same DF-5334 (if_cdce) class already proved for aue/axge/urndis/mos: usb_proc_drain() no-ops on the zeroed ue_tq (usb_process.c:401-412) and the ifp!=NULL guard at usb_ethernet.c:278 is dead code because uether_getifp() (usb_ethernet.c:134) returns the embedded arpcom ifnet. The trigger is fully device-controlled: any evil USB adapter presenting one of the 6 udav_devs[] VID:PID pairs (if_udav.c:205-218; e.g. ShanTou DM9601 0x0fe6:0x8101, Corega FEther USB-TXC 0x07aa:0x0004, Kontron JP1082) at config 0/iface 0 without the expected bulk-IN/OUT endpoints fails the transfer setup at plug-in. The second failure branch (uether_ifattach error, only on usb_proc_create ENOMEM) additionally runs ether_ifdetach() on a never-if_attach()ed ifnet -> ifindex2ifnet[0] nulling + global ifnet list corruption. Verified with a KLD harness replaying udav_detach() verbatim (its two production calls in order) on the exact branch-1 state, using production usbd_transfer_unsetup()+uether_ifdetach(): baseline on pristine with-src guest with stock uether.ko -> Fatal trap 12 VA 0x0, guest dead in DDB (panic.txt); with fix.diff ((a) uether_ifdetach early-return on NULL ue_lock + (b) udav_attach detach_usb label doing only unsetup+lockuninit, dead detach: label dropped, required for branch 2) both modules rebuilt -Werror clean and the identical harness run SURVIVED with KLDLOAD_RC=0, 0 Fatal traps, clean unload, patched if_udav.ko loads/unloads clean (2/2 runs). Not memory corruption with attacker-controlled data: fixed-address NULL deref at plug time; impact is panic/DoS from physical/VM USB access, same tier as DF-5334 (Medium).
No comments yet.