fw_read copies iso stream packet by attacker-controlled stream.len without bounding to psize (DMA buffer over-read)
Summary
In fw_read bulk-iso path uiomove size is fp->mode.stream.len + sizeof(u_int32_t) where stream.len taken directly from received packet header with no upper bound. Packet slot returned by fwdma_v_addr is only ir->psize bytes. Malicious FireWire peer transmitting iso stream packet with stream.len larger than psize-4 causes kernel to copy adjacent DMA-buffer bytes into userspace. For psize=512 and stream.len=1500 over-reads 988 bytes past packet slot. [VERIFIED pass2: reproduced with full attribution - read returns 1504 bytes from 512-byte slot, 62/62 canary windows byte-attributed to adjacent ring slots; clamp fix-validated, legit packet bit-identical]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2295 Β· 22 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.7 KB | β raw | |
| VERDICT.md | β | 4.7 KB | β raw | |
| build.log | β | 211 B | view raw | |
| build.sh | β | 111 B | view raw | |
| build_kernel.sh | β | 396 B | view raw | |
| env.txt | β | 466 B | view raw | |
| fix.diff | β | 1.6 KB | view raw | |
| fix_run.log | β | 688 B | view raw | |
| fwharness.c | β | 18.8 KB | view raw | |
| fwharness.h | β | 1.4 KB | view raw | |
| fwhctl.c | β | 3.7 KB | view raw | |
| leak_attributed.txt | β | 3.0 KB | view raw | |
| leak_fixed_508.txt | β | 1.8 KB | view raw | |
| leak_legit_508.txt | β | 1.8 KB | view raw | |
| leak_sample.txt | β | 5.3 KB | view raw | |
| poc_2295.c | β | 5.0 KB | view raw | |
| prep_guest.sh | β | 1.2 KB | view raw | |
| run.log | β | 1.0 KB | view raw | |
| run.sh | β | 90 B | view raw | |
| run2295.log | β | 1016 B | view raw | |
| run_2295_baseline.log | β | 965 B | view raw | |
| verdict.json | β | 3.2 KB | view raw |
DF-2295 β fw_read iso path over-read (stream.len unbounded vs psize)
Verdict (short)
REPRODUCED with full byte attribution on the stock fwdev.o +
fwdma.o binaries:
- Operator-group user sets up a legal iso receive stream
(FW_SSTBUF rx=4chunk/8packet/512psize, FW_SRSTREAM ch=1) β the DMA
ring is allocated by the real
fwdma_malloc_multiseg. - The harness emulates the malicious FireWire peer: it fills every
slot of the real DMA ring with 16-byte canary records
[magic|slot|offset|xor] and writes a received stream-packet header
with
stream.len = 1500into slot 0 (exactly what OHCI DMA would store; the peer controlslen). - The user
read(2)s1504 bytes. Stock kernel:uiomove(fp, fp->mode.stream.len + 4, uio)(fwdev.c:329-330) copies 1504 bytes out of a 512-byte packet slot β 992 bytes beyond the slot β into userspace, returning 0 (no error). - Every 16-byte window of the beyond-slot region decodes to the exact expected (slot, offset) pair: user[512..1504] == ring slots 1..3 content. Host-side verification of leak_sample.txt: 62/62 windows attributed (leak_attributed.txt).
Fix validated: clamping the copy to ir->psize keeps the legit packet
(len=508) bit-identical (leak_legit_508.txt == leak_fixed_508.txt) and
turns the malicious packet into per-slot truncated copies.
Threat model / reachability honesty
- Requires (a) an open /dev/fwU.S by an operator-group user and (b) a
malicious FireWire peer transmitting an iso stream packet whose
header
lenexceeds psize-4 (physical/bus-attached attacker or compromised device). The header quadlet is DMA'd by hardware into the packet slot and is never validated against the slot size. - Local-only exploitation without a peer is NOT possible: the user cannot place a forged header into the ring (hardware writes it).
- Disclosure target: bytes following the packet slot inside the bus_dmamem ring segment(s), and β for large len (16-bit field, up to 65535) β up to ~64KB past the slot, i.e. past the whole ring allocation into adjacent kernel KVA. The attributed 992-byte over-read (ring slots 1-3) is proven; the beyond-ring variant was not separately demonstrated (kernel already rebuilt with the fix for A/B; same primitive).
Files
- poc_2295.c β trigger (setup, read, attribution, leak_sample.txt)
- fwhctl.c β root-side peer emulation (inject canary ring + packet)
- leak_sample.txt β the 1504-byte read dump (baseline, malicious len)
- leak_attributed.txt β host-side byte-attribution proof (62/62)
- leak_legit_508.txt / leak_fixed_508.txt β A/B bit-identity
- run_2295_baseline.log, run_2295_fixed_malicious.log, run_2295_fixed_legit.log
- fix.diff β combined fix (this finding: clamp ulen to ir->psize)
DF-2295 VERDICT β REPRODUCED (kernel heap over-read to userspace, leak)
Chain (line-accurate, stock fwdev.o + fwdma.o)
fw_read (fwdev.c:260):
321: fp = (struct fw_pkt *)fwdma_v_addr(ir->buf,
322: ir->stproc->poffset + ir->queued); /* slot base */
325: if(fp->mode.stream.len == 0){ err = EIO; ... } /* len from
RECEIVED
packet hdr */
329: err = uiomove((caddr_t)fp,
330: fp->mode.stream.len + sizeof(u_int32_t), uio); /* UNBOUNDED
vs psize */
fp->mode.stream.len is the 16-bit length field of the stream packet
header that the FireWire peer transmits (OHCI DMA writes header+payload
into the psize-sized slot). No check against ir->psize exists
anywhere on the path. uiomove copies len+4 bytes from the slot to
userspace.
Observed (baseline, stock binaries)
Ring: 4 chunks x 8 packets x 512-byte slots (32 slots), allocated by
the real fwdev_allocbuf β fwdma_malloc_multiseg. All 32 slots
pre-filled (by the harness emulating the peer) with 16-byte canary
records: {0xF00DF00D, tag=0x50000000|slot, byte_offset, 0xDEADC0DE^tag}.
Packet header injected at slot 0: 05dc01a0 (len=1500, tcode=0xa).
DF-2295: read() returned 1504 (errno=0) DF-2295: returned packet header = 05dc01a0 (len=1500 tcode=a sy/chtag=a0) DF-2295: copied 1504 bytes; slot capacity 512 -> 992 bytes beyond the packet slot DF-2295: user[512..] == DMA ring slot 1 (tag=0x50000001) offset 0 -- beyond slot 0 DF-2295: user[528..] == DMA ring slot 1 (tag=0x50000001) offset 16 -- beyond slot 0 ...
Host-side verification of leak_sample.txt (this pack): all 62 sixteen- byte windows in [512,1504) decode to the exact expected (slot, offset): window at user offset X carries the record of ring offset X-512, i.e. slot (X-512)/512+1 at offset (X-512)%512. Every one of the 992 beyond-slot bytes is attributed to known adjacent ring-slot memory β a complete, byte-exact kernel memory disclosure to userspace. (The in-guest PoC summary line "attributed 248/4 bytes" under-counts by reporting matched 4-byte words; the host verifier and leak_sample.txt are authoritative.)
Note: read() returns SUCCESS (rc=1504) β the over-read is not even flagged as an error.
Threat model / reachability (honest)
- Preconditions: operator-group user with an open /dev/fwU.S (0660 root:operator, stock clone perms) that has set up an iso receive stream, AND a FireWire peer transmitting a crafted packet with stream.len > psize-4. The peer is the source of the length field; the local user is the recipient of the over-read bytes.
- Disclosure range: with psize=512 and len up to 65535, a single uiomove can read up to 65539 bytes starting at a 512-byte slot β i.e. beyond the entire fwdma multiseg allocation into adjacent kernel virtual memory. The 992-byte attributed over-read (adjacent slots 1-3) is proven; the beyond-ring variant uses the identical primitive and was not separately run (the guest kernel had already been rebuilt with the fix for A/B validation).
- Privesc assessment: READ-ONLY disclosure (adjacent DMA ring content and, for extreme len, adjacent kernel KVA). No write primitive, no direct escalation. On FireWire-equipped hosts this is a kernel info-leak that can assist exploit chains; requires peer cooperation. Closed as: leak, local impact contingent on a malicious peer.
Fix validation (A/B, fix.diff clamp hunk)
Fixed fw_read:
ulen = fp->mode.stream.len + sizeof(u_int32_t);
if (ulen > ir->psize)
ulen = ir->psize;
err = uiomove((caddr_t)fp, ulen, uio);
- Malicious packet (len=1500, psize=512): every copy is clamped to the slot; read returns per-slot chunks (512 bytes each) β no single copy ever crosses a slot boundary (run_2295_fixed_malicious.log: the beyond-slot bytes now come from separate, clamped per-slot copies of subsequent slots β bounded by construction).
- Legit packet (len=508 = psize-4): 512 bytes returned, hexdump
bit-identical to the baseline kernel output
(leak_legit_508.txt vs leak_fixed_508.txt β
diffempty, BIT_IDENTICAL_508).
Artifacts
leak_sample.txt, leak_attributed.txt (host verifier output), run_2295_baseline.log, run_2295_fixed_.log, leak__508.txt, poc_2295.c, fwhctl.c, fwharness.c (rig), env.txt.
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
fixedConfirmed kernel references
Detail
Exploit chain
operator-group user sets up iso receive (SSTBUF/SRSTREAM); malicious peer injects stream packet with len>psize-4; user read() receives adjacent kernel DMA-heap bytes (proof: full byte attribution). Read-only disclosure; no write primitive; no direct privesc - flagged as leak with peer-cooperation precondition.
Evidence (decisive lines)
leak_sample.txt (1504-byte read dump), leak_attributed.txt (62/62 windows attributed host-side), run_2295_baseline.log, leak_legit_508.txt vs leak_fixed_508.txt (bit-identical diff), run_2295_fixed_malicious.log
PoC changes
Rewrote as a coordinated reader/injector: opuser opens/reads, root-side fwhctl emulates the OHCI receive DMA (canary ring + crafted header through the real fwdma_v_addr slot). Fixed a stdout-buffering race and an attribution counter bug (bytes vs words) in the pack copy.
Verified recommended fix
Clamp the uiomove length to ir->psize (ulen = min(stream.len+4, ir->psize)).
Verdict
fw_read copies fp->mode.stream.len+4 bytes from a psize-sized DMA packet slot with no bound (fwdev.c:329-330). With psize=512 and a peer-supplied stream.len=1500, read() returned 1504 bytes successfully, 992 bytes beyond the slot, and every beyond-slot byte was attributed to known adjacent ring-slot content (62/62 canary windows decode to the exact slot+offset). Requires an operator-group opener plus a malicious FireWire peer supplying the oversized header; disclosure covers the whole DMA ring and, for extreme len, memory past the ring allocation (same primitive; beyond-ring variant not separately run). Fix validated: clamping the copy to ir->psize keeps a legit packet (len 508) bit-identical and bounds every copy to one slot.
No comments yet.