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

FAT32 active-FAT number pm_curfat (bpbExtFlags & FATNUM, 0..15) never validated against pm_FATs β€” crafted BPB relocates every FAT read AND write outside the FAT region (fatblock adds curfat*FATsecs), kernel-mediated disk corruption on a RW mount of a crafted image

Summary

vfsops.c:409-410 sets pm_curfat = bpbExtFlags & 0xf whenever the FATMIRROR bit is clear, with no check that curfat < pm_FATs (bpbFATs may be 1). fatblock() (msdosfs_fat.c:87) then computes bn += pm_fatblk + pm_curfat * pm_FATsecs for ALL FAT I/O: with FATs=1 and curfat=15, every FAT entry read is taken from (and, on a RW mount, every FAT update written to) fatblk + 15*FATsecs - the root-directory/data area or past the volume entirely. The mount succeeds (the census clamps maxcluster against the claimed FAT capacity regardless of where the FAT bytes come from), so fatentry/fatchain/updatefats write cluster-chain updates through the wrong device blocks: silent cross-region corruption of the mounted medium. FreeBSD shares this validation gap; impact limited to the device the mounter chose to trust. Crafted-image RW mount: kernel writes FAT updates to attacker-chosen offsets outside the volume's FAT - data destruction (extendable to another mounted fs's area when the vn image is shared). No kernel memory corruption. Phase V skipped (Low, disk-only). Fix: reject pm_curfat >= pm_FATs (and FATs==0). [VERIFIED pass2: reproduced - FATs 1 extflags 0x8F mounts RW and writes NEW.TXT FAT chain inside CANARY.BIN data clusters with real FAT untouched; fix A/B mount rejected]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3132 Β· 12 files
FileTypeDescriptionSize
craft_curfat.py β€” 4.9 KB view raw
run_guest.sh β€” 1.5 KB view raw
run.log β€” 1.2 KB view raw
README.md β€” 717 B ↓ raw
VERDICT.md β€” 2.4 KB ↓ raw
build.sh β€” 198 B view raw
run.sh β€” 216 B view raw
manifest.json β€” 824 B view raw
verdict.json β€” 2.3 KB view raw
fix.diff β€” 1.0 KB view raw
env.txt β€” 272 B view raw
compile_validation.txt β€” 1.8 KB view raw

DF-3132 -- verification pack

Finding: pm_curfat never validated against pm_FATs; FAT I/O displaced cross-region

Status: reproduced / impact corruption / confidence certain

What was run

host: python3 craft_curfat.py out.img (FATs=1 extflags=0x8F; landing zone seeded with a FAT copy)

How to reproduce

guest: vnconfig -c -S 64m /dev/vn0 img && mount_msdos /dev/vn0 /mnt && echo hi > /mnt/NEW.TXT && umount, then hexdump sectors 32 and 152

Expected

real FAT unchanged; NEW.TXT chain (entry 7 EOC) written at sector 152 inside CANARY.BIN clusters

See VERDICT.md for the full narrative and verdict.json for the persisted machine verdict. Artifacts: craft_curfat.py, run_guest.sh, run.log.

VERDICT.md
↓ download raw

DF-3132 verification

Kernel: DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Bottom line

A FAT32 BPB with bpbFATs=1 and bpbExtFlags=0x8F (mirror bit set, active FAT 15) mounts successfully: msdosfs_vfsops.c:409-410 sets pm_curfat=15 without ever checking it against pm_FATs, and every subsequent FAT read AND write is displaced 15*FATsecs sectors past the real FAT by fatblock (msdosfs_fat.c:87). After creating NEW.TXT on the RW mount and unmounting, the image shows the real FAT sector 32 unchanged (entry 7 still free) while the EOC chain write for cluster 7 landed at sector 152 inside CANARY.BIN data clusters - kernel-mediated writes outside the FAT region, silently corrupting file data. The landing zone had been pre-seeded with a copy of the real FAT so the displaced reads see a workable filesystem, proving the kernel operates consistently against the wrong blocks.

Kernel references

Exploit chain

(none: availability/correctness finding; no privilege chain)

Fix

reject pm_curfat >= pm_FATs and pm_FATs == 0 at mount; combined fixed kernel: the crafted volume is refused with bad active FAT and no FAT byte is written anywhere

See fix.diff in this directory (git-apply-able against the read-only sys/ tree; applied only inside the disposable verification guest).

Reproduction

build: host: python3 craft_curfat.py out.img (FATs=1 extflags=0x8F; landing zone seeded with a FAT copy)

run: guest: vnconfig -c -S 64m /dev/vn0 img && mount_msdos /dev/vn0 /mnt && echo hi > /mnt/NEW.TXT && umount, then hexdump sectors 32 and 152

expected: real FAT unchanged; NEW.TXT chain (entry 7 EOC) written at sector 152 inside CANARY.BIN clusters

Notes on the PoC (what changed vs the finding sketch)

initial canary filled the landing zone with 0xAA, which made the displaced FAT census read all clusters as busy (ENOSPC before any write); the zone now carries a copy of the real FAT so the displaced reads work and the misplaced WRITE is observable

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

not_testable
↓ fix.diffper-fix-DF-3132

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log hexdumps: sector 32 (real FAT) entry 7 = 0 after umount vs sector 152 (displaced) entry 7 = 0x0FFFFFFF EOC; mount line shows the crafted volume accepted RW

PoC changes

initial canary filled the landing zone with 0xAA, which made the displaced FAT census read all clusters as busy (ENOSPC before any write); the zone now carries a copy of the real FAT so the displaced reads work and the misplaced WRITE is observable

Verified recommended fix

reject pm_curfat >= pm_FATs and pm_FATs == 0 at mount; combined fixed kernel: the crafted volume is refused with bad active FAT and no FAT byte is written anywhere

Verdict

A FAT32 BPB with bpbFATs=1 and bpbExtFlags=0x8F (mirror bit set, active FAT 15) mounts successfully: msdosfs_vfsops.c:409-410 sets pm_curfat=15 without ever checking it against pm_FATs, and every subsequent FAT read AND write is displaced 15*FATsecs sectors past the real FAT by fatblock (msdosfs_fat.c:87). After creating NEW.TXT on the RW mount and unmounting, the image shows the real FAT sector 32 unchanged (entry 7 still free) while the EOC chain write for cluster 7 landed at sector 152 inside CANARY.BIN data clusters - kernel-mediated writes outside the FAT region, silently corrupting file data. The landing zone had been pre-seeded with a copy of the real FAT so the displaced reads see a workable filesystem, proving the kernel operates consistently against the wrong blocks.