DF-3023 / verdict.json
{ "finding_id": "DF-3023", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Unprivileged pwrite() with a negative offset on the default tmpfs /tmp panics INVARIANTS kernels: sys_extpwrite() passes the offset unvalidated, tmpfs_write() (unlike tmpfs_read:559) never rejects uio_offset<0, and the unsigned size_t uio_resid makes the EFBIG/RLIMIT/growth checks wrap mod 2^64, driving tmpfs_reg_resize(vp, -16384) into KKASSERT(newsize>=0) at tmpfs_subr.c:990. Reproduced twice from clean boots; guest drops to db>. Production (non-INVARIANTS) kernels fail cleanly with ENOSPC via the tm_pages_max guard - no memory corruption, no escalation path (resize target is kernel-derived; buffer cache returns EFAULT for the silent-write variants). Fix (uio_offset<0 -> EINVAL in tmpfs_write) validated on a rebuilt kernel: panic gone, EINVAL returned, no regression.", "exploit_chain": "", "evidence": [ "panic.txt โ serial-console panic signature (tmpfs_reg_resize newsize>=0, stack via kern_pwritev->vn_write->vop_write->tmpfs_write)", "run.log โ decisive run: probe EFAULT + panic trigger (second clean-boot reproduction; first in boot_panic_raw.txt)", "run.patched.log โ patched kernel: both variants return EINVAL=22, guest stays up", "fix_build.log โ nativekernel build/install/boot record + regression check", "trigger.c โ minimal unpriv reproducer" ], "kernel_refs": [ "sys/vfs/tmpfs/tmpfs_vnops.c:666", "sys/vfs/tmpfs/tmpfs_vnops.c:713", "sys/vfs/tmpfs/tmpfs_vnops.c:726", "sys/vfs/tmpfs/tmpfs_vnops.c:760", "sys/vfs/tmpfs/tmpfs_vnops.c:559", "sys/vfs/tmpfs/tmpfs_subr.c:990", "sys/kern/sys_generic.c:360", "sys/kern/vfs_vnops.c:766", "sys/sys/_uio.h:59" ], "poc_changes": "Seed concept (positive 2^63-offset wrap) was refuted live: size_t uio_resid is unsigned so offset+resid cannot wrap for positive offsets (guest returned EFBIG). Re-derived the negative-offset variant: entry-sum wraps to exactly 0 for offset=-N/resid=N, and iteration-1 sum goes negative for offsets below -16384; empirically bisected the trigger matrix (EFAULT/EFBIG/panic) to pwrite(32752 @ -32752).", "attempts": 4, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline); #1 Sat Sep 5 11:23:49 UTC 2026 (patched)", "runtime_sec": 5400, "guest_dirty": 0, "build_cmd": "cc -O2 -o poc/df3023 poc/df3023.c (fix kernel: cd /usr/src && patch -p0 < fix.diff && make -j6 nativekernel && make installkernel && reboot)", "run_cmd": "as unprivileged user on tmpfs /tmp: ./df3023 (expect: probe -> EFAULT; panic trigger -> kernel panic at tmpfs_subr.c:990, guest down at db>)", "code_hash": "21418db7435eed319f1970b07c9300d957920fae59108b5c88f137946bd5b66f", "notes": "16K-aligned single-block negative writes pass all limits but the buffer cache rejects loffset<0 with EFAULT - no silent unaccounted-page primitive. Production kernels: clean ENOSPC (wrong errno only). Impact confined to INVARIANTS/DEBUG builds (dev machines, this audit fleet's standard kernel).", "recommended_fix": "tmpfs_write: reject uio_offset < 0 with EINVAL, mirroring tmpfs_read (fix.diff validated in-guest); belt-and-suspenders: sys_extpwrite could also validate offset sign.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Sat Sep 5 11:23:49 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Single-fix kernel (uio_offset<0 -> EINVAL at tmpfs_vnops.c:702) built via make nativekernel and booted. Exact PoC rerun: both negative-offset pwrites return EINVAL, no panic, guest up (run.patched.log). Normal write/pwrite/ftruncate regression OK. Baseline panicked twice before the fix.", "fix_evidence": "fix_build.log, run.patched.log, env.txt" } |