struct tlabel leaked on every split-transaction timeout (firewire_xfer_timeout unlinks without kfree)
Summary
The timeout STAILQ_REMOVE_HEADs the tlabel without kfree and fw_tl_free only frees entries still in the list - every timed-out split transaction leaks one 16-byte M_FW allocation (operator user + non-answering node = ~1MB/day sustained, no ceiling). Guest-measured via harness malloc-type counters: 5000/5000 leaked (80,000 bytes); fix validated delta 0/0. kfree-after-remove fix (carried in the DF-5321 diff).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5322 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df5322.c | β | 16.7 KB | view raw | |
| Makefile | β | 1.3 KB | β download | |
| build.sh | β | 161 B | view raw | |
| run.sh | β | 172 B | view raw | |
| run.log | β | 1.4 KB | view raw | |
| fix.diff | β | 6.8 KB | view raw | |
| fix_build.log | β | 6.5 KB | view raw | |
| fix_run.log | β | 662 B | view raw | |
| env.txt | β | 417 B | view raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| README.md | β | 2.1 KB | β raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 3.1 KB | view raw |
DF-5322 β struct tlabel leaked on every split-transaction timeout
File: sys/bus/firewire/firewire.c:348 (firewire_xfer_timeout())
Severity: Low Β· Bucket: kernleak Β· Confidence: certain
Status: reproduced (5000 objects / 80000 bytes leaked over 5000
timeouts) on the QEMU guest via the production code path; fix validated
(delta 0).
Root cause
firewire_xfer_timeout() unlinks an expired tlabel with
STAILQ_REMOVE_HEAD(&fc->tlabels[i], link) (firewire.c:348) but never
frees it. The only kfree of a struct tlabel in the file is in
fw_tl_free() (firewire.c:900), which frees an entry only when it still
finds it in the list. After the timeout removed it:
- kernel-internal xfers:
fw_xfer_done() -> fw_asy_callback_free -> fw_xfer_free -> fw_xfer_unload -> fw_tl_free()finds nothing; /dev/fwFW_ASYREQxfers: the waiter'sfw_xfer_free_buf()-> samefw_tl_free()β finds nothing.
So every split-transaction timeout leaks exactly one
sizeof(struct tlabel) (= 16 bytes on x86_64) allocation from M_FW.
Trigger
Any local user permitted to use /dev/fwN.M can create these at will:
FW_ASYREQ a read to a node that never answers (or pull the cable) β
each transaction times out after 200 ms and leaks 16 bytes
(~1 MB/day at the natural retry rate; unbounded in aggregate with
kernel-internal fwmem traffic added on busy buses).
What to run here (guest, root)
./build.sh # df5322.ko: compiles the PRODUCTION firewire.c in
sh run.sh # mode 0: 5000 timeouts through the production
# firewire_xfer_timeout() + fw_xfer_free() paths
Baseline output (run.log):
DF5322: 5000 split-transaction timeouts DF5322: M_FW inuse delta = 5000 objects, memuse delta = 80000 bytes DF5322: LEAK REPRODUCED: ~1 tlabel leaked per timeout
Fixed (fix.diff applied to /usr/src, module rebuilt, fix_run.log):
DF5322: M_FW inuse delta = 0 objects, memuse delta = 0 bytes
fix.diff is shared with DF-5321 (the timeout now kfrees the tlabel it
unlinks, plus the tlabel-lock/CAS rework); see ../DF-5321/VERDICT.md
for the full fix-validation narrative.
DF-5322 β verdict
Status: reproduced (measured kernel memory leak, production path) Impact: leak (unbounded, slow kernel memory exhaustion) Confidence: certain
Guest: DragonFly dfbsd 6.5-DEVELOPMENT #0 X86_64_GENERIC (6 vCPU, no
FireWire hardware). Evidence class: KLD harness (DF-5230/DF-5302
pattern) compiling the unmodified production firewire.c into the
module; mode 0 drives exactly the production sequence of a
split-transaction timeout:
fw_xfer_alloc() /* the FW_ASYREQ/fwmem xfer */
fw_get_tlabel() /* inserts the tlabel (M_FW) */
firewire_xfer_timeout() /* 10 Hz watchdog: unlinks the
tlabel at firewire.c:348
WITHOUT kfree, completes xfer */
fw_xfer_free() /* owner-side free: fw_tl_free()
scans the list, finds nothing,
never kfrees the tlabel */
The harness measures the renamed-but-identical M_FW malloc type
in-kernel (ks_use[cpu].inuse/memuse, summed over all CPUs) across
5000 iterations (zones prewarmed; no other M_FW activity).
Result (baseline, unpatched production source)
DF5322: 5000 split-transaction timeouts
DF5322: M_FW inuse delta = 5000 objects, memuse delta = 80000 bytes
(each leaked struct tlabel = 16 bytes)
DF5322: LEAK REPRODUCED: ~1 tlabel leaked per timeout
Exactly one leaked 16-byte struct tlabel per timed-out transaction β
matching the static analysis: the only kfree site is fw_tl_free()
(firewire.c:900) and it requires list membership, which
firewire_xfer_timeout() destroyed at firewire.c:348.
An independent confirmation is the panic in the sibling DF-5321 runs: the watchdog dereferenced tlabels freed by other paths, proving these objects' lifetime is not tracked anywhere after the timeout unlinks them.
Exploitability
Memory-exhaustion DoS only (16 bytes per timed-out transaction,
attacker-paced, M_FW unbounded). No information disclosure, no
corruption from this defect alone.
Fix validation
fix.diff (shared with DF-5321) adds kfree(tl, M_FW) for the
timeout-unlinked tlabel inside the new fc->tlabel_lock critical
region. Applied to the guest /usr/src, module rebuilt, identical
trigger:
DF5322: 5000 split-transaction timeouts DF5322: M_FW inuse delta = 0 objects, memuse delta = 0 bytes
fix_status: fixed.
Bottom line
Certain, production-path, unbounded-but-slow kernel memory leak, reproduced and measured on the audit guest, fixed by a one-line kfree (carried inside the DF-5321 serialization fix).
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
fixedBaseline: 5000 objects / 80000 bytes leaked over 5000 timeouts via the production path. Patched (fix.diff on /usr/src, module rebuilt, identical trigger): delta 0 objects / 0 bytes.
fix_run.log, fix_build.log, fix.diff
Confirmed kernel references
Detail
Exploit chain
open /dev/fwN.M (operator group) -> FW_ASYREQ to a non-answering node (or kernel fwmem traffic on a busy bus) -> each 200 ms split timeout leaks one M_FW tlabel -> unbounded slow kernel memory exhaustion.
Evidence (decisive lines)
run.log (baseline: 'M_FW inuse delta = 5000 objects, memuse delta = 80000 bytes ... LEAK REPRODUCED'), fix_run.log ('delta = 0 objects, memuse delta = 0 bytes'), VERDICT.md
PoC changes
Harness is the DF-5321 module (df5322.c byte-identical to df5321.c; KMOD renamed df5322, sysctl names unchanged 'df5321_*'); mode 0 measures the renamed M_FW malloc type in-kernel (ks_use[cpu].inuse/memuse summed) around the production timeout+free sequence with prewarmed zones.
Verified recommended fix
kfree(tl, M_FW) after STAILQ_REMOVE_HEAD in firewire_xfer_timeout() (carried inside the DF-5321 fix.diff serialization rework).
Verdict
Reproduced and measured on the QEMU guest with the production firewire.c compiled into a KLD harness: every split-transaction timeout leaks exactly one 16-byte struct tlabel because firewire_xfer_timeout() (firewire.c:348) unlinks the tlabel without kfree and the only free site, fw_tl_free() (firewire.c:900), requires list membership. 5000 timeouts -> M_FW inuse delta 5000 objects / 80000 bytes (baseline); 0/0 with fix.diff applied to /usr/src and the module rebuilt. Unbounded attacker-paced kernel memory exhaustion (DoS); no disclosure or corruption from this defect alone.
No comments yet.