xe_intr() RX livelock: packet-drop paths 'continue' without releasing the packet to the card β permanent hard hang
Summary
In the receive loop, the three drop paths (zero-length frame, MGETHDR failure, MCLGET failure) 'continue' without writing XE_DO=XE_DO_SKIP_RX_PACKET, which is the sole mechanism that releases a received packet from the card buffer. ESR.FULL_PACKET_RX stays asserted for the unreleased packet, so the while-loop spins forever in hard-interrupt context holding the ifnet serializer - no watchdog can recover, the card IRQ stays asserted: permanent hang requiring power cycle. Cross-validated against Linux's driver for the identical silicon, which issues the skip unconditionally every iteration. Present even for in-bounds lengths (distinct from DF-1410). Trigger: inbound frame while mbuf clusters are exhausted by an unprivileged local user, or a CRC-only frame. Logic-model harness run on the guest: MODE 0 livelocks (200k+ iterations, 0 skip writes), MODE 1 (fix) terminates in 1. Release-before-continue fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4159 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.5 KB | β raw | |
| VERDICT.md | β | 4.9 KB | β raw | |
| livelock_harness.c | β | 4.0 KB | view raw | |
| build.sh | β | 106 B | view raw | |
| run.sh | β | 89 B | view raw | |
| build.log | β | 247 B | view raw | |
| run.log | β | 768 B | view raw | |
| fix.diff | β | 676 B | view raw | |
| manifest.json | β | 826 B | view raw | |
| verdict.json | β | 2.9 KB | view raw |
DF-4159 β RX livelock in xe_intr(): dropped packets are never released
File: sys/dev/netif/xe/if_xe.c (xe(4), Xircom CreditCard/RealPort PCMCIA ethernet)
Severity: Medium Β· CWE-835 Β· hardware-gated (source-certain control flow)
What this pack contains
livelock_harness.cβ userland logic model of the exact RX loop control flow fromif_xe.c:742-844(copied verbatim; drop paths parameterized), with the card register semantics fromif_xereg.hand Linuxxirc2ps_cs.c.build.sh/run.shβ exact commands (built and run on the DF QEMU guest, which has a real toolchain; no Xircom hardware exists there, hence logic model rather than kernel run).build.log/run.logβ full untrimmed output.fix.diffβ one-line-per-path fix.verdict.json/manifest.json.
Build
cc -O2 -Wall -o livelock_harness livelock_harness.c
Run
./run.sh # runs 6 scenarios: as-written vs fixed, per drop path # or individually: ./livelock_harness MODE FAIL_MGET FAIL_MCL LEN_ZERO
Expected output
Scenarios with MODE=0 (DragonFly code as written) and any error path taken (zero-length frame, MGETHDR failure, or MCLGET failure) print:
RESULT: LIVELOCK β loop exceeded 200000 iterations; packet NEVER released (XE_DO skip writes: 0)
The normal path (MODE 0, no failure) and every fixed scenario (MODE 1) print:
RESULT: loop terminated normally after 1 iterations (drops: N, XE_DO skip writes: 1, ...)
Interpretation
In the kernel, xe_intr() runs in hard-interrupt context holding the ifnet
serializer (bus_setup_intr(..., scp->ifp->if_serializer) at if_xe.c:333).
The three continue statements at if_xe.c:758, :772, :779 jump back to
the loop condition (XE_INB(XE_ESR) & XE_ESR_FULL_PACKET_RX) without
writing XE_DO = XE_DO_SKIP_RX_PACKET (if_xe.c:843). The unreleased packet
keeps ESR.FULL_PACKET_RX asserted, so the loop never exits β permanent
interrupt-livelock with the serializer held β system-wide hang (no panic; the
box must be power-cycled). Cross-validated: Linux's xirc2ps_cs.c for the
same silicon issues the skip unconditionally per iteration with the
comment "clear the received/dropped/error packet".
Trigger: any inbound frame on the xe interface while the matching drop
condition holds β most realistically MGETHDR/MCLGET failure under kernel
memory pressure (an unprivileged local user can exhaust mbuf clusters while a
remote peer sends traffic), or a CRC-only frame (RBC==4 β len==0).
DF-4159 VERDICT β RX livelock in xe_intr() drop paths (not released to hardware)
Status: untested (kernel/hardware level) β logic-model harness CONFIRMS the loop divergence; runtime confirmation requires Xircom PCMCIA hardware which does not exist in the QEMU guest.
Root cause (path:line)
sys/dev/netif/xe/if_xe.c, xe_intr():
:742while ((esr = XE_INB(XE_ESR)) & XE_ESR_FULL_PACKET_RX) {:756-759if (len == 0) { IFNET_STAT_INC(ifp, iqdrops, 1); continue; }β no release:769-773MGETHDRfailure βcontinue;β no release:775-781MCLGETfailure βm_freem(mbp); ...; continue;β no release:843XE_OUTW(XE_DO, 0x8000);β the only packet-release point, skipped by all three paths above
XE_DO_SKIP_RX_PACKET (0x8000) is the only mechanism that advances the card's
receive buffer past the current packet (sys/dev/netif/xe/if_xereg.h:247-256).
XE_ESR_FULL_PACKET_RX (ESR bit 0, if_xereg.h:219) is a level indication
of "at least one full packet received [and unreleased]". Consequently every
continue re-enters a loop whose condition is still true: the loop can never
terminate.
Why I believe the register semantics (cross-validation)
-
Linux's driver for the identical silicon,
drivers/net/ethernet/xircom/ xirc2ps_cs.c(still maintained), executes at the end of every loop iteration β good packet, dropped packet (skb-alloc failure), or error packet:/ clear the received/dropped/error packet / PutWord(XIRCREG0_DO, 0x8000); / issue cmd: skip_rx_packet / eth_status = GetByte(XIRCREG_ESR);
The comment names "dropped" explicitly: skipping is mandatory on drop paths.
- Old FreeBSD (10.3, last release shipping this driver) has the identical
continue-without-skip structure β this is a shared, never-upstream-fixed bug in the 1999-era BSD driver, not DragonFly-local; DragonFly inherited and still ships it (X86_64_GENERIC containsdevice xe).
Demonstrated evidence
livelock_harness.c (this pack) models the loop control flow verbatim with a
register file that implements the semantics above. Built and executed on the
audit guest (DragonFly dfbsd 6.5-DEVELOPMENT #0 ... x86_64, gcc 8.3):
- MODE 0 (code as written), normal packet β terminates, 1 skip write.
- MODE 0, len==0 β LIVELOCK (200000-iteration cap hit, 0 skip writes).
- MODE 0, MGETHDR-fail β LIVELOCK.
- MODE 0, MCLGET-fail β LIVELOCK.
- MODE 1 (fix applied), any path β terminates in 1 iteration.
See run.log (full output).
Impact
xe_intr() is registered with scp->ifp->if_serializer (if_xe.c:333-335)
and INTR_MPSAFE; the livelock therefore spins in hard-interrupt context
while holding the ifnet serializer forever:
- every subsequent interface ioctl / ifnet operation on xe0 spins on the serializer;
- the netisr softcall path that would run
if_watchdog/timers also needs the serializer β no recovery path exists in-kernel; - the card keeps its IRQ asserted (RX packet still pending), so even a hypothetical loop exit re-enters immediately;
- net effect: permanent hard hang requiring power cycle β a local+remote DoS
(remote peer sends one frame while an unprivileged local user has drained
mbuf clusters so
MGETHDR/MCLGET(M_NOWAIT)fail; or a crafted CRC-only frame yieldingRBC==4,len==0).
Hardware-gated: triggering requires a Xircom CE2/CE3/CEM/RealPort PCMCIA
adapter (or compatible) β device xe is compiled into the stock
X86_64_GENERIC kernel but only attaches to actual PCMCIA hardware.
Exploit chain
Not a corruption primitive β pure DoS. No escalation path identified (loop writes nothing outside iqdrops counters and the fixed card registers).
Fix validation
fix.diff adds XE_OUTW(XE_DO, 0x8000); before each of the three continue
statements (mirroring Linux's unconditional skip). Logic-model scenarios 5-6
(MODE 1) show the loop then terminates with the packet correctly released.
A full kernel-build validation was not performed: the modified path is
unreachable without Xircom PCMCIA hardware (driver attaches only on such
devices; the guest has none), so a built kernel could never exercise the
changed lines β the harness is the strongest available validation.
Classification notes
- distinct from pass-1 DF-1410 (missing upper bound on
lenβ heap overflow on copy): DF-4159 is about the control flow (packet release) on drop paths, present even for in-bounds lengths. guest_uname: recorded from the guest where the harness ran.
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_testablelogic-model MODE=1 scenarios terminate in 1 iteration with the packet released; kernel-level fix validation impossible without Xircom PCMCIA hardware (driver never attaches in the guest)
run.log scenarios 5-6; fix.diff
Confirmed kernel references
Detail
Exploit chain
local unprivileged user exhausts mbuf clusters (M_NOWAIT MGETHDR/MCLGET fails) while a remote peer sends one frame to the xe interface -> xe_intr spins forever holding if_serializer -> hard hang requiring power cycle. DoS only; no corruption primitive.
Evidence (decisive lines)
run.log (scenarios 2-4 LIVELOCK vs 5-6 fixed), livelock_harness.c (verbatim loop copy), VERDICT.md (path:line trace + Linux cross-validation), fix.diff
PoC changes
n/a (new finding, pass 2); harness authored from the if_xe.c loop verbatim
Verified recommended fix
Write XE_DO=0x8000 (skip packet) before each of the three 'continue' drop paths in xe_intr()'s RX loop.
Verdict
xe_intr() RX drop paths (zero-length frame at if_xe.c:756-759, MGETHDR failure at :769-773, MCLGET failure at :775-781) 'continue' without writing XE_DO=XE_DO_SKIP_PACKET (0x8000, the only packet release, at :843). ESR.FULL_PACKET_RX stays asserted for the unreleased packet so the loop at :742 spins forever in hard-interrupt context holding the ifnet serializer - permanent system hang. Register semantics cross-validated against Linux xirc2ps_cs.c (unconditional per-iteration skip, 'clear the received/dropped/error packet'). Logic-model harness built+run on the audit guest confirms: as-written code livelocks on all three drop paths; one-line fix terminates. Kernel/hardware-level run impossible: no Xircom PCMCIA hardware exists in the QEMU guest.
No comments yet.