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

ext2_nodealloccg honors unvalidated inode-bitmap content: reserved inode numbers (below first_ino) and tail bits (>= ipg) are allocatable β€” silent fs aliasing / OOB e2fs_gd[] reads via ino_to_cg

Summary

The INODE_UNINIT regeneration path never marks reserved inodes [1, first_ino) used (and for ipg in 8..15 the stock memset of ipg/8-1==0 bytes leaves on-disk garbage in force), so the scan at :1356-1372 can allocate reserved inode 1 - RUNTIME-CONFIRMED: ls -li shows a regular file created as inode 1 on a mount-legal image. The scan is also bounded by howmany(ipg,NBBY) bytes with no clamp at ipg, so with ipg%8!=0 and cleared tail bits the allocator returns ino beyond cg*ipg+ipg: interior groups alias the next group's inode space (double allocation); the last group sends ino_to_cg(ino) >= gcount into ino_to_fsba's fs->e2fs_gd[] (fs.h:111-113) OOB read steering later I/O. The bgetvp-overlap panic variant was observed only as collateral of the leaked-lock state; standalone run produced silent metadata corruption, no panic - Low. Fix: set reserved+tail bits after regeneration + validate the chosen bit < ipg before setbit/return.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3066 Β· 6 files
FileTypeDescriptionSize
craft3066.py β€” 2.6 KB view raw
trigger3066.sh β€” 232 B view raw
vntool.c β€” 1.1 KB view raw
run.log β€” 694 B view raw
env.txt β€” 470 B view raw
README.md β€” 1.8 KB ↓ raw

DF-3066 β€” ext2_nodealloccg allocates reserved / out-of-group inode numbers from crafted or uninitialized inode bitmaps

Outcome

Mechanism REPRODUCED: on a mount-legal image (ipg=12, INODE_UNINIT with garbage on-disk bitmap byte 0), the first touch created a regular file as inode 1 β€” the reserved bad-blocks inode (ls -li proof in run.log). No standalone kernel panic resulted (unmount clean), so the finding is filed as LOW (silent metadata corruption; escalation chains observed only in combination with other inconsistent state β€” the DF-3062 pack's bgetvp - overlapping buffer follow-up panic, and OOB e2fs_gd[] reads via ino_to_cg when the picked bit lies past ipg in the bitmap tail).

Root causes (code-read; only the first was runtime-confirmed)

  1. The INODE_UNINIT regeneration path never marks reserved inodes [1, EXT2_FIRST_INO(fs)) used, and for ipg in 8..15 the stock memset(b_data, 0, ibytes - 1) (0 bytes) leaves the on-disk garbage byte in force β€” reserved inode 1 is allocatable (observed).
  2. The scan (ext2_alloc.c:1356-1372) is bounded by howmany(ipg, NBBY) bytes; with ipg % 8 != 0 the tail byte's bits >= ipg are honored, so the allocator can return ino > cg*ipg+ipg: for interior groups that aliases the NEXT group's inode space (double allocation); for the last group ino_to_cg(fs, ino) >= gcount indexes fs->e2fs_gd[] out of bounds in ino_to_fsba (fs.h:111-113) β€” OOB kernel-heap read steering later I/O.

Build/run

host: python3 craft3066.py df3066.img; guest: cc vntool; sh trigger3066.sh; then ls -li /mnt/e6 shows the file with inode 1.

Fix direction

After regenerating an uninit inode bitmap, set the reserved bits and the bits >= ipg; clamp/validate the chosen bit index against ipg before returning from ext2_nodealloccg.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

no fix authored (hardening class)

per-fix-DF-3066

Confirmed kernel references

Detail

Evidence (decisive lines)

['run.log (ls -li shows x as inode 1)', 'findings/poc/DF-3062/panic.bgetvp-followup.txt (collateral escalation chain)']

PoC changes

built after the DF-3062 follow-up panic to isolate the reserved-inode mechanism; standalone run did not panic

Verified recommended fix

after regenerating an uninit inode bitmap set bits [1,first_ino) and >= ipg; validate chosen bit < ipg before returning

Verdict

The inode allocator honors unvalidated inode-bitmap content: with EXT2_BG_INODE_UNINIT the regeneration path neither marks reserved inodes used nor clamps the scan at ipg. Runtime-confirmed: a regular file was created as reserved inode 1 (ls -li). The panic escalation (bgetvp overlap in ext2_vget) was observed only as collateral in the DF-3062 fixed-module run; the tail-bit variant (ino beyond ipg -> OOB e2fs_gd[] via ino_to_cg) is proven by code-reading only. Filed Low; no standalone kernel impact demonstrated in this run.