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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4159 Β· 10 files
FileTypeDescriptionSize
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 from if_xe.c:742-844 (copied verbatim; drop paths parameterized), with the card register semantics from if_xereg.h and Linux xirc2ps_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).

VERDICT.md
↓ download raw

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():

  • :742 while ((esr = XE_INB(XE_ESR)) & XE_ESR_FULL_PACKET_RX) {
  • :756-759 if (len == 0) { IFNET_STAT_INC(ifp, iqdrops, 1); continue; } ← no release
  • :769-773 MGETHDR failure β†’ continue; ← no release
  • :775-781 MCLGET failure β†’ m_freem(mbp); ...; continue; ← no release
  • :843 XE_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)

  1. 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.

  1. 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 contains device 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 yielding RBC==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_testable
baseline no→ patch + rebuild →patched clean

logic-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
↓ fix.diffper-fix-DF-4159

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.