Premature kfree of persistent per-slot swdesc in txp_rxbuf_reclaim error path causes UAF and double-free
Summary
txp_rxbuf_reclaim at if_txp.c:793-796: MGETHDR/MCLGET failure -> err_sd: kfree(sd). sd is persistent per-slot alloc (txp_alloc_rings:952), only freed in txp_detach:350. rb_sd never NULLed, sc_rxbufprod not advanced. Next reclaim reads dangling rb_sd (UAF read sd_mbuf). txp_rxring_empty (ifconfig down/up) wild-frees sd_mbuf + NULLs. txp_detach double-frees rb_sd. Remote flood to exhaust mbufs or local memory pressure. Sibling of DF-1337 (channel.c UAF pattern). Fix: do NOT kfree sd in error path, NULL sd_mbuf, return.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1461 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | pattern-faithful userspace harness demonstrating the UAF/double-free from txp_rxbuf_reclaim's error path | 6.1 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -o harness harness.c | 144 B | view raw |
| run.sh | run-script | ./harness | 115 B | view raw |
| run.log | run-log | harness output on #0 baseline kernel β BUG CONFIRMED + FIX CONFIRMED | 901 B | view raw |
| fix_run.log | run-log | harness output on #1 patched kernel β identical (harness tests pattern, not kernel code) | 890 B | view raw |
| fix.diff | suggested-fix | git-apply-able fix: remove kfree(sd) from err_sd, NULL sd_mbuf in err_mbuf, keep err_sd label with return | 265 B | view raw |
| fix_build.log | build-log | full nativekernel build output for the single-fix kernel (rc=0, 0 errors) | 5.6 MB | β download |
| env.txt | environment | uname, cc version, network interfaces (no 3Com HW) | 409 B | view raw |
| VERDICT.md | verdict | full narrative: mechanism, reachability, harness evidence, fix, impact | 6.2 KB | β raw |
| README.md | readme | build/run/expected + rationale for harness approach | 1.2 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1461: txp_rxbuf_reclaim UAF / double-free
Finding
DF-1461 (High): txp_rxbuf_reclaim in sys/dev/netif/txp/if_txp.c
frees a persistent per-slot allocation (struct txp_swdesc *sd) in its
error path (lines 793β796), creating a use-after-free and double-free.
Build
cc -O2 -Wall -o harness harness.c
Run
./harness
Expected output
The harness replicates the exact memory-management pattern of
txp_rxbuf_reclaim in userspace (malloc/free) to demonstrate the bug
without requiring the 3Com Typhoon NIC hardware:
--- Testing BUGGY txp_rxbuf_reclaim (if_txp.c:793-796) --- ... => BUG CONFIRMED: dangling pointer + UAF read + double-free --- Testing FIXED txp_rxbuf_reclaim --- ... => FIX CONFIRMED: no double-free, no UAF
Why a harness instead of a live trigger?
The txp driver (3Com 3cR990 "Typhoon", PCI vendor 0x10b7) is compiled
into the X86_64_GENERIC kernel (device txp in the config), but there is
no 3Com hardware on the QEMU/KVM guest. The driver never probes or
attaches, so txp_rxbuf_reclaim is unreachable at runtime. The bug is
confirmed by source-level tracing and demonstrated by a pattern-faithful
harness.
Reproduction
./build.sh && ./run.sh
DF-1461 β VERDICT
Verdict: REPRODUCED (source-level + harness; runtime-unreachable without 3Com HW)
Bug mechanism
txp_rxbuf_reclaim() in sys/dev/netif/txp/if_txp.c:747-797 refills the
receive buffer ring by allocating an mbuf for each empty slot. On
MGETHDR or MCLGET failure, the error path frees the persistent
per-slot struct txp_swdesc *sd β an allocation that lives for the
entire lifetime of the interface and is only meant to be freed in
txp_detach.
The vulnerable code (if_txp.c:761-797)
while (1) {
sd = rbd->rb_sd; // line 762: read persistent ptr
if (sd->sd_mbuf != NULL)
break; // slot already set up
MGETHDR(sd->sd_mbuf, M_NOWAIT, MT_DATA);
if (sd->sd_mbuf == NULL)
goto err_sd; // line 768: MGETHDR failed
MCLGET(sd->sd_mbuf, M_NOWAIT);
if ((sd->sd_mbuf->m_flags & M_EXT) == 0)
goto err_mbuf; // line 772: MCLGET failed
// ... success path ...
}
sc->sc_rxbufprod = i; // line 789: ONLY reached on success
return;
err_mbuf:
m_freem(sd->sd_mbuf);
err_sd:
kfree(sd, M_DEVBUF); // line 796: BUG β frees persistent alloc
}
Three consequences
-
Dangling pointer + UAF read. After the error path,
rbd->rb_sdstill points to the freedsd(never NULLed), andsc->sc_rxbufprodis never advanced (line 789 only runs on the success path). The next call totxp_rxbuf_reclaimβ fromtxp_intr(line 630) ortxp_tick(line 1155) β readssd = rbd->rb_sd(line 762), a dangling pointer, then dereferencessd->sd_mbuf(line 763). Under INVARIANTS, the freed slab chunk is poisoned with0xdeadc0de, so this UAF read panics immediately. Without INVARIANTS, it silently reads whatever now occupies that slab. -
Double-free in
txp_detach. At detach time (line 349-350):c for (i = 0; i < RXBUF_ENTRIES; i++) kfree(sc->sc_rxbufs[i].rb_sd, M_DEVBUF);The slot whoserb_sdwas already freed in the error path is freed a second time β double-free. Under INVARIANTS this panics with a slab assertion (chunk_mark_freeon an already-free chunk). -
Wild free in
txp_rxring_empty. Theifconfig downpath callstxp_rxring_empty(if_txp.c:1076-1090) which freessd->sd_mbuffor each slot. On the UAF slot,sdis a dangling pointer β wild free.
Trigger conditions
The error path fires when MGETHDR or MCLGET fails under memory pressure
(mbuf exhaustion). On a system with 3Com Typhoon hardware, an attacker can
trigger this by flooding the interface to exhaust the mbuf pool, or under
natural memory pressure. The bug is in the GENERIC kernel (device txp),
but the code path is only reachable when a 3cR990 NIC is present.
Reachability on this guest
- txp driver: compiled into X86_64_GENERIC kernel (
device txp). - 3Com hardware: absent.
pciconf -lshows only virtio and Intel 440FX devices. No PCI vendor 0x10b7 device exists. - Result: the driver never probes or attaches; no
txp_softcexists;txp_rxbuf_reclaimis unreachable at runtime.
This is case (d) from the procedure: "genuinely not reachable on this kernel" β the vulnerable code path is dead code at runtime because no matching hardware is present. The bug is a real latent defect confirmed by source tracing and demonstrated by a pattern-faithful harness.
Harness evidence
The harness (harness.c) replicates the exact memory-management pattern of
txp_rxbuf_reclaim using userspace malloc/free:
- Buggy version:
kfree(sd)on MGETHDR failure βrb_sddangling, UAF read on next call, double-free in detach. - Fixed version: no free of
sd; NULLsd_mbuf; next call succeeds normally; detach is clean.
Output (from run.log):
--- Testing BUGGY txp_rxbuf_reclaim (if_txp.c:793-796) --- rb_sd[0] = 0x... (NOT NULLed β dangling pointer) sd = rb_sd[0] = 0x... => UAF READ: sd->sd_mbuf deref = 0x0 detach: freeing rb_sd[0] β already freed in err_sd => DOUBLE-FREE => BUG CONFIRMED: dangling pointer + UAF read + double-free --- Testing FIXED txp_rxbuf_reclaim --- rb_sd[0] = 0x... (still valid β NOT freed) rb_sd[0]->sd_mbuf = 0x0 (NULLed β safe) rc=0, rxbufprod now =1 (advanced) => FIX CONFIRMED: no double-free, no UAF
Fix
fix.diff β two changes to the error path of txp_rxbuf_reclaim:
-
Remove
kfree(sd, M_DEVBUF)at line 796.sdis a persistent per-slot allocation (allocated attxp_alloc_rings:952withM_WAITOK, only freed intxp_detach:350). Freeing it in the error path is the root cause of the UAF/double-free. -
Add
sd->sd_mbuf = NULLafterm_freem(sd->sd_mbuf)in theerr_mbufpath. WhenMCLGETfails, the mbuf header was allocated but the cluster attach failed;m_freemreleases it, and NULLingsd_mbufleaves the slot in a clean state so the next reclaim attempt can retry the allocation.
The err_sd label is kept (the goto err_sd at line 768 still references
it) but its body is now just return;.
Fix validation
- Diff applies:
git apply --checkβ clean (rc=0). - Compiles:
make -j6 nativekernel KERNCONF=X86_64_GENERICβ rc=0, 0 errors. Full build log infix_build.log. - Boots: patched kernel (#1, Fri Jul 17 18:09:16 UTC 2026) boots and
is stable.
kern.versionconfirms the rebuild. - Runtime test:
not_testableβ the vulnerable code path requires 3Com Typhoon hardware not present on this guest. The fix is verified at the source level (nokfree(sd)intxp_rxbuf_reclaim, confirmed viagrep) and structurally (the error path now NULLssd_mbufand returns without freeing the persistent allocation).
Impact
On a system with 3Com 3cR990 hardware, an attacker who can cause mbuf
exhaustion (via network flood or local memory pressure) triggers a
kernel UAF read β double-free. Under INVARIANTS (default GENERIC): panic
(DoS). Without INVARIANTS: potential heap corruption exploitable for
privilege escalation, as the double-free gives a write primitive into
the M_DEVBUF slab.
CWE-416 (Use After Free), CWE-415 (Double Free). CVSS 3.1: AV:A/AC:M/PR:N/UI:N/S:U/C:H/I:H/A:H (7.1 High with hardware).
Fix verification
not_testablecompile+boot validated. Harness before/after.
BEFORE: kfree(sd) in err_sd. AFTER: removed + sd_mbuf=NULL.
Confirmed kernel references
β
Detail
Exploit chain
none -- HW-gated (3Com Typhoon NIC absent). INVARIANTS catches as panic.
Evidence (decisive lines)
β
Verdict
REPRODUCED (harness). txp_rxbuf_reclaim err_sd kfree(sd) on persistent per-slot alloc -> dangling ptr + UAF read + double-free in detach. txp in GENERIC, no 3Com HW.
No comments yet.