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

MNT_RELOAD adopts new group geometry without resizing the cluster-summary arrays β€” heap OOB write, bzero()/free() of OOB-read pointers, and per-reload gd leak

Field Value
ID DF-3047
Status new
Severity High
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:H
CWE CWE-787 (CWE-125/CWE-761 secondary)
File sys/vfs/ext2fs/ext2_vfsops.c
Lines 620, 647-650, 818-842, 947-951, 1057-1059
Area vfs/ext2fs
Confidence certain
Discovered 2026-09-02
Pass 2 (GLM 5.3 second pass)
Bucket memcorrupt
Reported pending
Known CVE none
CVE match novel

Summary

ext2_reload() re-reads the superblock and ext2_compute_sb_data() mutates the live m_ext2fs: e2fs_gcount is re-derived from the new sb and e2fs_gd/e2fs_contigdirs are re-malloc'd (leaking the previous allocations), but e2fs_maxcluster/e2fs_clustersum — sized in ext2_mountfs() for the OLD group count — are never resized. Reload step 3 then loops ics_init, and bzero(sump->cs_sum, contigsumsize+1) with cs_sum read OOB; ext2_unmount() later free()s those OOB pointers. Failed reloads poison the mount identically because gcount is committed before the checks. mount(2) MNT_UPDATE|MNT_RELOAD on a read-only ext2 mount is permitted to root OR the user who performed the original mount — fully unprivileged with vfs.usermount=1 and a user-owned vn device whose backing file is rewritten between mount and reload. VERIFIED: fatal supervisor WRITE at heap-junk address (0x28bf) inside memset — an OOB-read→arbitrary-address 33-byte zero-write plus int32 OOB writes and arbitrary free at unmount; repeated reloads leak gdbcount_allocbsize+gcount4 bytes each. Fix validated in-guest (geometry gate on reload + free-before-realloc): EINVAL instead of panic; unchanged-geometry reload still works; pristine RW mount regression-passes.

Timeline

  • 2026-09-02 Discovered during pass-2 audit of ext2_vfsops.c (GLM 5.3); unpriv OOB write reproduced + fix validated. DF-0803 re-verified, not re-reported.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3047 Β· 11 files
FileTypeDescriptionSize
craft.py β€” 1.8 KB view raw
vntool.c β€” 1.1 KB view raw
reloadtrigger.c β€” 1.2 KB view raw
run.sh β€” 505 B view raw
build.sh β€” 242 B view raw
run.log β€” 1.4 KB view raw
panic.txt β€” 1.0 KB view raw
fix_run.txt β€” 1.4 KB view raw
fix.diff β€” 4.3 KB view raw
env.txt β€” 461 B view raw
verdict.json β€” 3.4 KB view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fixed module: reload with changed geometry returns EINVAL (mount: Invalid argument), guest healthy, unmount clean, no panic; reload with unchanged geometry still succeeds rc=0 with data intact; pristine RW mount regression-checked. Module build completed after adding sys/disk.h include (first build attempt failed on incomplete struct disk).

findings/poc/DF-3047/fix_run.txt; findings/poc/DF-3047/fix.diff
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #0 (same kernel; ext2fs.ko rebuilt with fix.diff hunks 3+4, 2026-09-05)

Confirmed kernel references

Detail

Exploit chain

RO-mount crafted 8-group ext2 on user-owned vn device -> rewrite backing file with valid 16-group ext2 -> mount(2) MNT_UPDATE|MNT_RELOAD (allowed for the mount owner, incl. unprivileged with vfs.usermount=1) -> compute_sb_data sets gcount=16, arrays still sized 8 -> step3: int32 writes past e2fs_maxcluster + bzero(garbage cs_sum,33) -> panic (demonstrated); heap-groomed variant yields controlled-address zeroing + arbitrary free at unmount (characterized, not pursued to uid0)

Evidence (decisive lines)

run.log (full session incl. df/sb before-after), panic.txt (fault virtual address 0x28bf, supervisor write, memset+0x2b), fix_run.txt (fixed: EINVAL, clean unmount), craft.py, reloadtrigger.c

PoC changes

wrote reloadtrigger.c because DFly mount(8) has no -o reload (MNT_RELOAD is syscall-only); guest lacks vnconfig so vntool.c performs VNIOCATTACH; two-run instrumentation (df + raw sb peek) proved the device serves the swapped content before triggering.

Verified recommended fix

gate ext2_reload() on identical superblock geometry and free the prior e2fs_gd/e2fs_contigdirs in ext2_compute_sb_data() before re-allocating

Verdict

ext2_reload()/ext2_compute_sb_data() adopt a new group count and re-allocate e2fs_gd/e2fs_contigdirs (leaking the old ones) but never resize e2fs_maxcluster/e2fs_clustersum; reload step 3 then writes past both arrays and bzero()s cs_sum pointers read OOB, and ext2_unmount() later free()s them. Reproduced: mount(2) MNT_UPDATE|MNT_RELOAD after swapping the backing device content to a larger-geometry image -> fatal supervisor write in memset at heap-junk address 0x28bf.