Latent negative-length bcopy in fwohci_arcv split-header reassembly can cause kernel panic
Summary
In fwohci_arcv split-header reassembly path rlen recomputed as sizeof(pktbuf)-old_rlen. If old_rlen exceeds sizeof(pktbuf)=16 new rlen goes negative. Code detects with if(rlen<0) kprintf but does NOT skip subsequent bcopy which receives negative int implicitly converted to huge size_t causing massive kernel memory write and panic. Developer comment this must be too long but harmless and why rlen<0 diagnostic show awareness without understanding consequence. Currently unreachable due to buf_offset overwrite guard at line 2871 but latent landmine if control flow changes. [VERIFIED pass2: mechanically proven UNREACHABLE from packet input - battery drove every reachable header straddle, rlen always positive; the landmine is real - injected impossible state makes the very next bcopy hit the stack guard page; hardening guard validated A/B by state injection]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2303 Β· 20 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.2 KB | β raw | |
| VERDICT.md | β | 3.5 KB | β raw | |
| build.log | β | 928 B | view raw | |
| build_fix.sh | β | 511 B | view raw | |
| build_kernel.sh | β | 380 B | view raw | |
| env.txt | β | 1.3 KB | view raw | |
| fix.diff | β | 596 B | view raw | |
| fwohci_harness.c | β | 22.3 KB | view raw | |
| fwohci_harness.h | β | 3.9 KB | view raw | |
| fwpoc.c | β | 7.9 KB | view raw | |
| panic.serial.txt | β | 2.7 KB | view raw | |
| panic.txt | β | 930 B | view raw | |
| prep_guest.sh | β | 1.8 KB | view raw | |
| run.log | β | 1.6 KB | view raw | |
| run.sh | β | 161 B | view raw | |
| run2303b.log | β | 337 B | view raw | |
| run2303b.sh | β | 779 B | view raw | |
| run_fixed.log | β | 3.8 KB | view raw | |
| run_fixed.sh | β | 1.9 KB | view raw | |
| verdict.json | β | 4.7 KB | view raw |
DF-2303 β latent negative-length bcopy in fwohci_arcv split-header pullup
What this is
Verification pack for finding DF-2303 (sys/bus/firewire/fwohci.c:2729-2735).
Low / speculative, latent memory-corruption landmine. Claim: in the
split-header reassembly path, rlen is recomputed as
sizeof(pktbuf) - rlen (fwohci.c:2732); if the previous-buffer fragment
exceeded sizeof(pktbuf)=16 the new rlen goes negative and the diagnostic
at 2733-2734 does NOT skip the following bcopy(db_tr->buf, p, rlen)
(fwohci.c:2735), whose negative int becomes a huge size_t.
Verdict (short)
NOT REPRODUCIBLE FROM PACKET INPUT β and the battery proves it; the landmine itself is REAL and catastrophic when the state is injected:
- (A) Reachability battery (run.log): every header straddle the real parser can produce was driven through the REAL fwohci_arcv split-header path (firewire_debug on, "rlen=%d, offset=%d" printed by the real code at fwohci.c:2721-2723): WREQB (16B hdr): F = 4, 8, 12 -> rlen_old = 4/8/12 WRES (12B hdr): F = 4, 8 -> rlen_old = 4/8 All rlen_new = 16 - rlen_old in [4,12] > 0. No "why rlen < 0". Guest up after all runs.
- (B) Impossible-state injection (run2303b.log, panic.txt): with
buf_offset forced to -(4096-24) (rlen_old = 24 > 16 β a state no
packet stream can produce), the real code printed "why rlen < 0"
and the very next bcopy hit the stack guard page:
panic: vm_fault: fault on stack guardwith memmove in the trace β the negative-length bcopy is a live stack-smash primitive.
Why rlen_old > 16 is unreachable (line-accurate invariant)
- buf_offset becomes negative ONLY at fwohci.c:2762
(
buf_offset = -buf_offset), immediately after fwohci_arcv_swap returned -hlen (hlen > len) for a parse at position X in buffer N (buf_offset = ld - buf = X at that point, set by fwohci.c:2800). - At out: (fwohci.c:2855) if resCount != 0, line 2871 OVERWRITES buf_offset with a positive value, destroying the split state β so the split-header branch (2712ff) is entered only when resCount was 0, i.e. the buffer was full and len at the failing parse was psize - X = rlen_old.
- The parse failed with hlen > len, and hlen = tinfo[tcode].hdr_len <= 16 for every tcode arcv_swap accepts (fwohci.c:2601-2617); therefore rlen_old < hlen <= 16 whenever the state survives. Additionally every AR packet consumes a multiple of 4 bytes (12/16-byte headers + 4-byte trailer + quadlet-rounded payloads) and fwohci_arcv_swap has no STREAM case (tcode 0xa rejected, "Unknown tcode 10"), so F is quadlet-aligned: the battery set {4,8,12} is the complete reachable space for a 4096-byte buffer.
The battery mechanically confirms each element of the invariant ("split header" print, "rlen=F, offset=-X" print, clean completion).
Fix
fix.diff turns the diagnostic into a guard (drop + goto out). A/B validated: the SAME injected state on the fixed kernel prints the drop message and survives instead of panicking.
Reachability honesty
As filed: latent/unreachable today. The finding text itself says "currently unreachable ... latent landmine if control flow changes" β this verification upgrades that claim from speculation to mechanical proof (battery + invariant + demonstrated consequence).
DF-2303 VERDICT β latent negative-length bcopy in fwohci_arcv
- finding: DF-2303, fwohci.c:2729-2735 (split-header pullup, rlen = sizeof(pktbuf) - rlen; unguarded bcopy with negative len)
- status: not_reproduced (from packet input; proven unreachable)
- impact class: corruption (the code defect is a live stack-smash primitive β demonstrated by state injection β but no attacker- reachable input produces the state on this tree)
- confidence: certain (as a negative result)
(A) Battery β the complete reachable split-header space
Rig: REAL fwohci_db_init + REAL fwohci_rx_enable on arrq (psize=4096, ndesc=1, ndb=128; per-tr buffers allocated by the real fwohci_add_rx_buf), received-DMA state fabricated exactly as OHCI AR DMA would leave it (quadlet-aligned WRES(16B)/WREQB(20B) filler chain up to X, split packet-B header at X = psize - F, resCount/status in desc.res), then REAL fwohci_arcv(sc, dbch, -1) with firewire_debug=1.
Observed from the real code (console/dmesg, run.log):
FWOH: ARCV NATURAL tcode=1 hdr=16 F=4 X=4092 -> REAL fwohci_arcv split header rlen=4, offset=-4092 ... F=8: rlen=8, offset=-4088 ... F=12: rlen=12, offset=-4084 WRES F=4: rlen=4, offset=-4092 ... F=8: rlen=8, offset=-4088
rlen_new = 16 - rlen_old in [4,12] β strictly positive; reassembly completed for every case (buf_offset positive after, pdb_tr cleared); no "why rlen < 0"; guest stable after all runs.
Why this is the COMPLETE set: quadlet alignment (packet boundaries are multiples of 4) + headers of 12/16 bytes + hlen>len split criterion + the resCount==0 requirement implied by the 2871 clobber (see README) => F in {4,8,12} for 16-byte headers, {4,8} for 12-byte headers. An earlier battery variant that tried STREAM fillers proved arcv_swap rejects tcode 0xa outright ("Unknown tcode 10", fwohci.c:2614-2618) β STREAM never reaches the split path at all.
(B) Injection β the landmine is real
fwpoc ctl arcv 1 0 0 4072 sets dbch->buf_offset = -4072
(rlen_old = 24 > 16 β impossible per (A)) and calls the real
fwohci_arcv:
FWOH: ARCV INJECT buf_offset=-4072 -> rlen_old=24 (EXCEEDS 16) why rlen < 0 <- fwohci.c:2734 panic: vm_fault: fault on stack guard, addr: 0xfffff80116b51000 memmove() at memmove+0x24f <- fwoctl_ioctl (fwohci_arcv inlined)
(panic.txt) β first bcopy overflows pktbuf[16] on the stack, second bcopy receives (size_t)-8 and writes until the stack guard page: catastrophic kernel memory corruption IF the state is ever reachable.
Bottom line
The filed claim is exactly confirmed: the code is a latent landmine ("developer comment shows awareness without understanding consequence") that is unreachable from packet input on this tree. Recommend the one-line guard anyway (defense in depth; any future change to the 2871 clobber or to busdma resCount semantics re-arms it), plus note that the 2871 overwrite itself silently discards a legitimate split-header state (separate correctness bug, out of scope of this finding).
Fix validation (A/B)
fix.diff: if (rlen < 0) { kprintf drop; pdb_tr=NULL; buf_offset=0;
goto out; }. Fixed kernel + identical injection: drop message
printed, NO panic, guest up (run_fixed.log).
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
fixedPatched kernel: identical impossible-state injection prints fwohci_arcv: oversized header fragment, drop and survives with no panic (run_fixed.log); the natural battery behavior is unchanged (reachable path never takes the guard).
run_fixed.log DF-2303 section: CTL_ARCV mode=1 inj=4072 returns, dmesg shows oversized header fragment drop, uptime continuous
Confirmed kernel references
- sys/bus/firewire/fwohci.c:2699
- sys/bus/firewire/fwohci.c:2712
- sys/bus/firewire/fwohci.c:2729
- sys/bus/firewire/fwohci.c:2732
- sys/bus/firewire/fwohci.c:2733
- sys/bus/firewire/fwohci.c:2735
- sys/bus/firewire/fwohci.c:2756
- sys/bus/firewire/fwohci.c:2762
- sys/bus/firewire/fwohci.c:2871
- sys/bus/firewire/fwohci.c:2578
Detail
Exploit chain
none from packet input (proven); state injection only: rig sets dbch->buf_offset beyond any parser-producible value -> real bcopy(db_tr->buf, p, (size_t)-8) -> stack guard page fault -> panic
Evidence (decisive lines)
run.log: battery F=4/8/12 (tcode WREQB) and F=4/8 (tcode WRES) with the real-code rlen=/offset=/split header console lines, guest up after all; run2303b.log + panic.txt: injected state -> why rlen < 0 -> panic vm_fault fault on stack guard, memmove backtrace; run_fixed.log: same injection on patched kernel prints oversized header fragment, drop and survives
PoC changes
Rig FWOIOC_ARCV: real fwohci_db_init + real fwohci_rx_enable + real fwohci_add_rx_buf allocate the AR channel; received-DMA state fabricated exactly as OHCI AR DMA leaves it (quadlet-aligned WRES(16B)/WREQB(20B) filler chain, split header at X=psize-F, resCount/status in desc.res); natural mode runs the real fwohci_arcv; inject mode forces an impossible buf_offset. First battery attempt used STREAM fillers - disproven: arcv_swap rejects tcode 0xa (Unknown tcode 10) and STREAM never reaches the split path; replaced with quadlet-aligned WRES/WREQB chains.
Verified recommended fix
Turn the rlen<0 diagnostic into a guard: drop the packet and goto out instead of falling into bcopy (fix.diff)
Verdict
The negative-length bcopy at fwohci.c:2735 is NOT reachable from packet input on this tree, and the battery proves it mechanically: every header straddle the real fwohci_arcv stream parser can produce (quadlet-aligned F in {4,8,12} for 16-byte headers, {4,8} for 12-byte headers on a 4096-byte buffer) was driven through the real split-header path with firewire_debug on - the real code printed rlen=4/8/12 with offset=-X for every case, rlen_new = 16-rlen_old stayed in [4,12], no why rlen < 0, reassembly completed, guest stable. Invariant (line-accurate): buf_offset goes negative only at fwohci.c:2762 after arcv_swap returns -hlen; if resCount!=0 the overwrite at fwohci.c:2871 discards the split state first, so the branch is only entered with resCount==0 where rlen_old = psize - X = len at the failed parse, and the parse failed precisely because hlen > len with hlen <= 16; additionally arcv_swap has no STREAM case and AR packet boundaries are quadlet multiples, so F is quadlet-aligned. rlen_old > 16 is therefore impossible. The landmine itself is real: injecting the impossible state (buf_offset=-4072, rlen_old=24) made the real code print why rlen < 0 and the very next bcopy hit the stack guard page - panic: vm_fault: fault on stack guard (memmove in trace) - a live stack-smash primitive if control flow ever changes (panic.txt). Verdict as filed: latent, unreachable today; the hardening guard is recommended and validated.
No comments yet.