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

Zero bufreq.psize passed to FW_STSTREAM/FW_SRSTREAM causes divide-by-zero panic in fwdma_malloc_multiseg

Summary

fwdev_allocbuf forwards user-controlled bufspec straight into fwdma_malloc_multiseg with no validation of psize. If caller has not first issued FW_SSTBUF (so d->bufreq.tx/rx still zero from M_ZERO allocation) or explicitly set psize=0 the rounddown(PAGE_SIZE esize) macro inside fwdma_malloc_multiseg performs integer division by zero panicking kernel. Any operator-group user can crash system with two syscalls (open + FW_STSTREAM). [VERIFIED pass2: reproduced both variants - Fatal trap 18 integer divide fault fwdma_malloc_multiseg+0x139 via real fw_ioctl dispatch; operator-group reach on FireWire-equipped hosts only, fwohci absent from stock config; fix-validated EINVAL]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2293 Β· 20 files
FileTypeDescriptionSize
README.md β€” 2.5 KB ↓ raw
VERDICT.md β€” 3.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
fwharness.c β€” 18.8 KB view raw
fwharness.h β€” 1.4 KB view raw
fwhctl.c β€” 3.7 KB view raw
kernel_buildfix_tail.log β€” 195.3 KB view raw
panic.srstream.txt β€” 1.2 KB view raw
panic.ststream.txt β€” 1.3 KB view raw
poc_2293.c β€” 2.2 KB view raw
prep_guest.sh β€” 1.2 KB view raw
run.sh β€” 90 B view raw
run_2295_fixed_legit.log β€” 573 B view raw
run_2295_fixed_malicious.log β€” 1016 B view raw
run_fixed.log β€” 503 B view raw
verdict.json β€” 2.8 KB view raw

DF-2293 β€” zero bufreq.psize β†’ divide-by-zero panic in fwdma_malloc_multiseg

What this is

Verification pack for finding DF-2293 (sys/bus/firewire/fwdev.c). Medium / local DoS. Claim: FW_STSTREAM/FW_SRSTREAM issued before any FW_SSTBUF reaches fwdev_allocbuf() with bufreq.tx/rx.psize == 0 (the fw_drv1 is kmalloc'd M_ZERO in fw_open, fwdev.c:179), which passes esize=0 into fwdma_malloc_multiseg() (fwdma.c:145); the macro rounddown(PAGE_SIZE, esize) (sys/sys/param.h:400, ((x)/(y))*(y)) divides by zero and panics the kernel.

Verdict (short)

REPRODUCED on the stock fwdev.o/fwdma.o binaries, twice:

  • FW_SRSTREAM variant: Fatal trap 18: integer divide fault β†’ fwdma_malloc_multiseg+0x139 ← fwdev_allocbuf+0x6a ← fw_ioctl+0x94e (panic.srstream.txt)
  • FW_STSTREAM variant: same trap, fw_ioctl+0x982 (the FW_STSTREAM call site, fwdev.c:458) (panic.ststream.txt)

Trigger is an operator-group user (device is 0660 root:operator, fwdev.c:854-856 semantics), open + one ioctl. Fix validated: with the psize/nchunk/npacket validation the same ioctls return EINVAL and the guest stays up (run_fixed.log).

Reachability honesty

  • Stock X86_64_GENERIC compiles the whole firewire stack (device firewire, sys/config/X86_64_GENERIC:319) but NOT fwohci (no PCI OHCI controller driver), so on real hardware-less systems no /dev/fw* node is ever created β€” the attack needs FireWire hardware with the stock fwohci kld or a built-in controller.
  • On this QEMU guest there is no FireWire hardware, so the verification uses the established verbatim-interposition harness (fwharness.c): the stock kernel objects fwdev.o, fwdma.o, firewire.o run unmodified; the harness fabricates the controller-side externalities (firewire_comm, xferqs, tcode table copied verbatim from fwohci.c:85, softc injected into the real firewire_devclass) and creates /dev/fw0.0 against the real firewire_ops with stock clone permissions. The panicking code is byte-identical to stock.

Reproduce

See build.sh / run.sh. Guest-side prep: prep_guest.sh + build_kernel.sh (kernel-compiled harness; runtime kldload GPFs on this guest).

Files

  • poc_2293.c β€” trigger (open + FW_SRSTREAM or FW_STSTREAM)
  • panic.srstream.txt, panic.ststream.txt β€” serial-console panic captures
  • run_fixed.log β€” fixed-kernel A/B (EINVAL, no panic, guest alive)
  • fix.diff β€” combined 4-finding fix (this finding: hunk 1)
  • fwharness.c/fwharness.h/fwhctl.c β€” verification harness
  • env.txt, build.log, kernel build logs
VERDICT.md
↓ download raw

DF-2293 VERDICT β€” REPRODUCED (panic, local DoS, operator-group user)

Chain (line-accurate, stock binaries)

  1. fw_open (fwdev.c:157-182) allocates struct fw_drv1 with M_WAITOK | M_ZERO (fwdev.c:179) β†’ d->bufreq.tx/rx all zero.
  2. User issues FW_STSTREAM/FW_SRSTREAM without a prior FW_SSTBUF (there is no psize validation anywhere on this path).
  3. fw_ioctl FW_STSTREAM (fwdev.c:447-467) / FW_SRSTREAM (fwdev.c:475-496) selects a free fc->it[]/fc->ir[] xferq and calls fwdev_allocbuf(fc, q, &d->bufreq.tx/rx) (fwdev.c:458 / :486).
  4. fwdev_allocbuf (fwdev.c:94-137) does b->psize = roundup2(0, 4) == 0 (fwdev.c:107) and calls fwdma_malloc_multiseg(fc, 4, 0, 0, BUS_DMA_WAITOK) (fwdev.c:108-109).
  5. fwdma_malloc_multiseg (fwdma.c:145): esize(0) ≀ PAGE_SIZE branch β†’ ssize = rounddown(PAGE_SIZE, esize) (fwdma.c:159); rounddown(x,y) = ((x)/(y))*(y) (sys/sys/param.h:400) β†’ integer division by zero.
  6. amd64 kernel takes trap 18 (#DE) β†’ panic: integer divide fault.

Observed (baseline harness kernel, stock fwdev.o/fwdma.o)

FW_SRSTREAM run (uid=1002 opuser, groups include operator gid 5):

Fatal trap 18: integer divide fault while in kernel mode
panic: integer divide fault
fwdma_malloc_multiseg() at fwdma_malloc_multiseg+0x139
fwdev_allocbuf() at fwdev_allocbuf+0x6a
fw_ioctl() at fw_ioctl+0x94e        <-- FW_SRSTREAM case (fwdev.c:486)

FW_STSTREAM run:

Fatal trap 18: integer divide fault while in kernel mode
fwdma_malloc_multiseg() at fwdma_malloc_multiseg+0x139
fwdev_allocbuf() at fwdev_allocbuf+0x6a
fw_ioctl() at fw_ioctl+0x982        <-- FW_STSTREAM case (fwdev.c:458)

Two syscalls (open + ioctl) as an operator-group user; no FireWire bus activity required. The distinct fw_ioctl offsets (+0x94e vs +0x982) prove both cited entry paths were exercised.

Threat model / reachability (honest)

  • Device nodes: /dev/fwU.S are created by fwdev_clone (fwdev.c:822-860) with UID_ROOT/GID_OPERATOR/0660 β€” an operator-group member can open them. On stock X86_64_GENERIC the firewire stack is compiled in but fwohci is not, so nodes only exist on machines with a FireWire OHCI controller (fwohci kldload or custom kernel). Impact: local kernel panic (DoS) for operator-group users on FireWire-equipped systems.
  • Verification used the verbatim-interposition harness (no hardware): stock fwdev.o/fwdma.o/firewire.o binaries, fabricated controller-side externalities, /dev/fw0.0 with stock clone perms. The panic occurs in unmodified stock code.

Fix validation (A/B on the same harness)

fix.diff hunk 1 adds, in fwdev_allocbuf right after the EBUSY check:

    if (b->psize <= 0 || b->nchunk <= 0 || b->npacket <= 0)
        return(EINVAL);

Patched kernel (DragonFly 6.5-DEVELOPMENT #8 .../FWH): * poc_2293 sr β†’ ioctl rc=-1 errno=22, no panic * poc_2293 st β†’ ioctl rc=-1 errno=22, no panic * guest alive; legit path (FW_SSTBUF {4,8,512} + FW_SRSTREAM, exercised by the DF-2295 rig on the same kernel) still allocates the DMA ring normally. Baseline (unpatched): panic, both variants (panics above).

Privesc assessment

None β€” pure control-flow abort (divide trap), no memory corruption. Classified impact: dos (kernel panic, reboot).

Artifacts

panic.srstream.txt, panic.ststream.txt, run_fixed.log, poc_2293.c, fwharness.c (rig), env.txt, build.log, kernel_buildfix_tail.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

fixed
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #8: Sun Oct 18 07:38:56 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/FWH x86_64

Confirmed kernel references

Detail

Exploit chain

open /dev/fw0.0 (0660 root:operator) + one FW_SRSTREAM or FW_STSTREAM ioctl with zeroed bufreq -> kernel panic (DoS). No escalation: divide trap only.

Evidence (decisive lines)

panic.srstream.txt and panic.ststream.txt (trap 18 backtraces at the two fw_ioctl call sites), run_fixed.log (EINVAL on patched kernel, guest alive), VERDICT.md

PoC changes

Seed concept kept; trigger built self-contained (embedded ioctl structs with _Static_assert layout checks); ran as operator-group user opuser on the fabricated /dev/fw0.0 with stock clone permissions.

Verified recommended fix

Validate psize/nchunk/npacket > 0 in fwdev_allocbuf before any allocation.

Verdict

Zero bufreq.psize (no FW_SSTBUF before FW_SRSTREAM/FW_STSTREAM) reaches fwdma_malloc_multiseg with esize=0 and panics on rounddown(PAGE_SIZE, 0) integer division by zero. Reproduced twice as an operator-group user through the stock fwdev.o and fwdma.o binaries: Fatal trap 18 with backtraces fwdma_malloc_multiseg+0x139 <- fwdev_allocbuf+0x6a <- fw_ioctl+0x94e (SRSTREAM) and +0x982 (STSTREAM). Two syscalls, no bus activity. Fix validated: bufspec validation returns EINVAL, no panic, legit allocation path unchanged.