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

Missing upper-bound on attacker-controlled stream.len in fwe_as_input causes kernel heap over-read

Field Value
ID DF-1849
Status new
Severity Medium
CVSS 3.1 CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H
CWE CWE-125 Out-of-bounds Read
File sys/dev/netif/fwe/if_fwe.c
Lines 560-569
Area dev/netif (FireWire Ethernet RX)
Confidence likely
Discovered 2026-07-20
Reported pending
Known CVE none
CVE match variant

Summary

fwe_as_input() trusts the 16-bit fp->mode.stream.len field from a received FireWire isochronous stream packet header to set m->m_pkthdr.len, but only checks the lower bound (>= 16). There is no upper-bound check against the mbuf cluster capacity (MCLBYTES = 2048). An attacker on the FireWire bus can send a stream packet whose header claims data_length up to 65535, causing the network stack to read up to ~63 KB past the 2048-byte mbuf cluster boundary β€” a kernel heap over-read that leaks kernel memory to userspace or panics the kernel.

Root cause

In fwe_as_input() the received packet is extracted from the DMA buffer as:

fp = mtod(sxfer->mbuf, struct fw_pkt *);         /* line 546 */

The packet's stream.len field is a 16-bit unsigned bitfield (COMMON_HDR(len,...) β†’ u_int32_t len:16 at firewire.h:130/141) populated directly by the OHCI controller from the on-wire isochronous packet header β€” fully attacker-controlled, never sanitized between OHCI (fwohci_rbuf_update at fwohci.c:2148-2184 sets chunk->resp from the DMA status and calls fwe_as_input via ir->hand) and this function.

The validation at lines 560-561 only checks the lower bound:

if (sxfer->resp != 0 || fp->mode.stream.len <
    ETHER_ALIGN + sizeof(struct ether_header)) {   /* i.e. < 16 */

Lines 567-569 then do:

m->m_data += HDR_LEN + ETHER_ALIGN;               /* += 6 bytes */
m->m_len = m->m_pkthdr.len =
        fp->mode.stream.len - ETHER_ALIGN;

The mbuf cluster is MCLBYTES = 2048 bytes (xferq->psize set at line 337), and m_pkthdr.len was MCLBYTES from allocation (line 351/554). After advancing m_data by 6 bytes, only 2042 bytes of valid data remain. But m_pkthdr.len is set to stream.len - 2, which can be up to 65533 β€” far exceeding the cluster. No check like stream.len > MCLBYTES or stream.len > m->m_pkthdr.len - HDR_LEN exists.

Threat model & preconditions

  • Attacker position: any node on the same IEEE 1394 (FireWire) bus. FireWire isochronous channels have no authentication β€” any bus node can transmit on any channel. Also reachable locally via /dev/fw* (FW_ASYREQ ioctl with FWASREQSTREAM, fwdev.c:553) since devices are 0660 root:operator (fwdev.c:174).
  • Privileges gained or impact:
  • Kernel heap info leak β€” leaked bytes are delivered as IP payload to userspace sockets.
  • Kernel panic (DoS) if the over-read crosses into an unmapped page.
  • Required config or capabilities: device fwe loaded, FireWire controller present, fwe interface up. Default stream_ch=1 (line 84/325).
  • Reachability: attacker sends a crafted isochronous stream packet (tcode=0xa/FWTCODE_STREAM) with data_length > 2048 in the 4-byte isochronous header, plus a valid Ethernet+IP header in the payload so ether_input/ip_input accept and process the frame.

Proof of concept

Two vectors.

Vector A β€” external FireWire bus (no target access needed):

  1. Connect a Linux attacker machine to the target's FireWire bus.
  2. Install libraw1394/libavc1394.
  3. Construct an isochronous stream packet: - First quadlet = (sy<<28)|(tcode<<24)|(chtag<<16)|data_length - tcode = 0xa, chtag = 1 (default fwe channel) - data_length = 0xFFFF (or any value > 2048)
  4. Follow with a valid Ethernet header (dst=broadcast, src=any, type=0x0800) and an IP header with valid checksum and total_length matching the claimed data_length, so ip_input accepts it and delivers "payload" (kernel heap beyond the cluster) to a socket the attacker reads.
  5. Send the packet. The target's fwe_as_input creates an mbuf with m_pkthdr.len up to 65533 but only ~2042 bytes of real data. ether_input β†’ ip_input reads past the cluster.

Vector B β€” local, operator group:

Open /dev/fw0, issue FW_ASYREQ ioctl with req.type=FWASREQSTREAM, pkt.mode.stream.tcode=0xa, pkt.mode.stream.chtag=1, pkt.mode.stream.len=0xFFFF, and a crafted payload β€” same over-read result.

Build & run

cc -o fw_poc fw_poc.c -lraw1394     # Vector A
# OR
cc -o fw_poc fw_poc.c               # Vector B (uses ioctl)

Expected output

# On target:
Fatal trap 12: page fault while in kernel mode
...
ether_input+0x.. / ip_input+0x.. at 0x..
# OR silent heap leak to userspace socket buffer

Impact

Medium-severity kernel heap over-read reachable from any node on the FireWire bus (unauthenticated isochronous channel) or locally via /dev/fw* (operator group). The over-read can disclose kernel memory (KASLR bypass, credential disclosure) or panic the kernel (DoS).

Add an upper-bound check on fp->mode.stream.len before using it to set the mbuf length. The received data must fit within the mbuf cluster after the m_data advancement.

--- a/sys/dev/netif/fwe/if_fwe.c
+++ b/sys/dev/netif/fwe/if_fwe.c
@@ -557,8 +557,10 @@ fwe_as_input(struct fw_xferq *xferq)
        }

        if (sxfer->resp != 0 || fp->mode.stream.len <
-           ETHER_ALIGN + sizeof(struct ether_header)) {
+           ETHER_ALIGN + sizeof(struct ether_header) ||
+           fp->mode.stream.len > m->m_pkthdr.len - HDR_LEN) {
            m_freem(m);
            IFNET_STAT_INC(ifp, ierrors, 1);
            continue;

At this point m->m_pkthdr.len == MCLBYTES (2048) from allocation, so the check rejects any stream.len > 2044. After m_data += 6, the new length (stream.len - 2 ≀ 2042) fits within the remaining cluster space. An equivalent alternative: compare stream.len directly against MCLBYTES: fp->mode.stream.len > MCLBYTES - HDR_LEN.

References

  • OHCI RX buffer update path: fwohci.c:2148-2184.
  • /dev/fw* stream ioctl path: fwdev.c:553.
  • stream.len bitfield: firewire.h:130/141.

Timeline

  • 2026-07-20 Discovered during automated audit.
  • 2026-07-20 Reported to DragonFlyBSD security contact (pending).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1849 Β· 2 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able unified diff; validated as part of combined 41-finding kernel build (rc=0, -Werror clean) 553 B view raw
VERDICT.md verdict source-only confirmation + HW/module gating explanation 1.5 KB ↓ raw
VERDICT.md verdict source-only confirmation + HW/module gating explanation
↓ download raw

DF-1849 Verification

Verdict

SOURCE-CONFIRMED, INCONCLUSIVE-RUNTIME (HW/module gated).

The cited defect exists in the audited source at sys/dev/netif/fwe/if_fwe.c:560-569. Reproduction on the running guest is not possible because the affected code path is gated behind hardware that is not present in the audit QEMU/KVM guest (no AMD/i915 GPU, no LSI MegaRAID, no MMC/SDHCI controller, no FireWire, no ATAPI floppy, etc.) and/or lives in a kernel module that is not loaded on the GENERIC-running guest.

Mechanism (source-only confirmation)

fwe (Ethernet over FireWire) IS in GENERIC but no FireWire HW in guest. Source: fwe_as_input at L546 extracts fp=mtod(...). stream.len is a u16 bitfield populated directly by the OHCI controller from the on-wire isochronous packet header (attacker-controlled). The check at L560 only rejects m_len past m_ext.ext_size.

Cap fp->mode.stream.len against m->m_ext.ext_size before computing m->m_len.

The full git apply-able diff lives in fix.diff in this folder; it was applied as part of a single combined 41-finding kernel build that compiled cleanly (rc=0, -Werror clean) β€” see ../fix_build_summary.txt.

Build validation

  • git apply --check on this fix.diff: OK
  • Combined kernel build (X86_64_GENERIC, INVARIANTS ON) with all 41 findings' fix.diffs applied: rc=0, no warnings, no errors.
  • The patched kernel was not booted/run because the affected code path requires hardware that the audit guest does not have.

Confirmed kernel references

Detail

Exploit chain

none β€” non-corruption classes (info leak / DoS / div0 / logic) or HW/module gated. No memory-corruption primitive reachable from userspace on this guest.

Evidence (decisive lines)

Source-only confirmation. Combined kernel build with all 41 fix.diffs applied: === NK_DONE rc=0 === at Wed Jul 22 18:05:21 UTC 2026 (no errors, no warnings). See findings/poc/fix_build_summary.txt.

PoC changes

Authored findings/poc/DF-1849/fix.diff (minimal targeted guard). VERDICT.md and manifest.json written. fix.diff validated by combined build.

Verified recommended fix

Cap fp->mode.stream.len against m->m_ext.ext_size before computing m->m_len. Full git-apply-able diff in findings/poc/DF-1849/fix.diff; validated as part of combined 41-finding kernel build (rc=0).

Verdict

SOURCE-CONFIRMED, INCONCLUSIVE-RUNTIME. The cited defect exists at sys/dev/netif/fwe/if_fwe.c:560-569. fwe (Ethernet over FireWire) IS in GENERIC but no FireWire HW in guest. fwe_as_input L546 extracts fp=mtod(...). stream.len is a u16 bitfield populated by OHCI controller from on-wire isochronous packet (attacker-controlled). Validation L560-564 only rejects too-small; large stream.len drives m->m_len past m_ext.ext_size (heap OOB write). HW gated.