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

hammer2_igetv panics on unhandled on-media objtype (0,3,8,10,11..255) β€” crafted-image kernel DoS at mount/first access

Field Value
ID DF-2636
Status new
Severity Medium
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H
CWE CWE-754 Improper Check for Unusual or Exceptional Conditions
File sys/vfs/hammer2/hammer2_inode.c
Lines 791-793
Area vfs
Confidence certain
Discovered 2026-08-29
Pass 2 (GLM 5.3 second pass)
Bucket hammer2
Reported pending
Known CVE none
CVE match novel

Summary

hammer2_igetv() switches on ip->meta.type, copied verbatim from the on-media inode by hammer2_pfsalloc (vfsops.c:456) with no validation on the mount path. The switch handles only DIRECTORY/REGFILE/SOFTLINK/CDEV/BDEV/FIFO/SOCKET; every other byte value (0 UNKNOWN, 3, 8, 10 WHITEOUT, 11..255) reaches default: panic("hammer2: unhandled objtype %d") at inode.c:791-793.

Threat model & preconditions

Crafted or bit-flipped (checksum-forged) hammer2 image: unconditional kernel panic at mount or first access to any inode with an unhandled objtype. Mount privilege required β€” same trust boundary as DF-0763, DF-0804, DF-0875, DF-2616..2632.

Proof of concept

VERIFIED on the stock INVARIANTS guest: forge_2636.py flips meta.type at inode-block +0x50 of the PFS root to 0x42; mount -o ro succeeds; the first VFS_ROOT (lstat inside ls -la) panics: panic: hammer2: unhandled objtype 66 with backtrace hammer2_igetv ← hammer2_vfs_root ← nlookup ← kern_stat ← sys_lstat. Fix (kprintf + vp->v_type = VBAD + *errorp = EINVAL instead of panic) validated on a nativekernel rebuild: ls returns Invalid argument, guest stays up. Evidence: findings/poc/DF-2636/.

Impact

Unconditional kernel panic (local DoS) from a crafted image. No memory-corruption primitive; controlled panic.

--- a/sys/vfs/hammer2/hammer2_inode.c
+++ b/sys/vfs/hammer2/hammer2_inode.c
@@
        default:
-           panic("hammer2: unhandled objtype %d",
-                 ip->meta.type);
-           break;
+           kprintf("hammer2: unhandled objtype %d inum %ld\n",
+               ip->meta.type, (long)ip->meta.inum);
+           vp->v_type = VBAD;
+           vx_put(vp);
+           vp = NULL;
+           *errorp = EINVAL;
+           break;
        }
+       if (vp == NULL)
+           break;

Full diff: findings/poc/DF-2636/fix.diff (validated).

Timeline

  • 2026-08-29 Discovered during pass-2 audit of hammer2_inode.c (GLM 5.3); verified reproduced + fix validated same run.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2636 Β· 13 files
FileTypeDescriptionSize
forge_2636.py β€” 2.0 KB view raw
run.sh β€” 536 B view raw
panic.txt β€” 2.9 KB view raw
run.log β€” 2.2 KB view raw
build.log β€” 146 B view raw
env.txt β€” 319 B view raw
fix.diff β€” 662 B view raw
README.md β€” 1.7 KB ↓ raw
VERDICT.md β€” 2.3 KB ↓ raw
verdict.json β€” 3.3 KB view raw
fix_build.log β€” 1.4 KB view raw
fix_run.log β€” 258 B view raw
manifest.json β€” 1.0 KB view raw

DF-2636 β€” panic on unhandled on-media objtype in hammer2_igetv

What

hammer2_igetv() (sys/vfs/hammer2/hammer2_inode.c:744-795) switches on ip->meta.type to initialize the vnode. meta.type is copied verbatim from the on-media inode (hammer2_pfsalloc: iroot->meta = ripdata->meta, hammer2_vfsops.c:456). Any on-media value outside {DIRECTORY,REGFILE,SOFTLINK,CDEV,BDEV,FIFO,SOCKET} β€” e.g. 0 (UNKNOWN), 3, 8, 10 (WHITEOUT), 11..255 β€” hits:

default:
        panic("hammer2: unhandled objtype %d", ip->meta.type);

An attacker who can craft (or bit-flip, given a checksum-forged image) a hammer2 filesystem image gets an unconditional kernel panic at mount or at the first VFS_ROOT (ls/stat) on the mount. Mounting requires root (or vfs.usermount policies), same trust boundary as the rest of the crafted-image family (DF-0763/DF-0804/DF-0875/DF-2616...).

Reproduce (on the DragonFly QEMU guest, as root)

# forge: flip meta.type (inode block +0x50) of the 'testvol' PFS root
# inode to 0x42, set CHECK_NONE on ancestors, recompute volhdr CRC32Cs
python3 forge_2636.py h2base.img craft2636.img     # host side
scp craft2636.img dfbsd:/root/poc/df2636/          # guest side
sh run.sh

run.sh does: vnconfig + mount_hammer2 -o ro /dev/vn1@testvol /mnt/h2636 + ls -la /mnt/h2636.

Expected

Mount succeeds; the first VFS_ROOT (the lstat inside ls) panics:

panic: hammer2: unhandled objtype 66
hammer2_igetv() at hammer2_igetv+0x42a
hammer2_vfs_root() at hammer2_vfs_root+0xdc
nlookup() at nlookup+0xb9c
kern_stat() ... sys_lstat()

Observed verbatim on the stock INVARIANTS kernel β€” see panic.txt / run.log.

Fix

Return an error (and mark the vnode VBAD) instead of panicking; see fix.diff.

VERDICT.md
↓ download raw

DF-2636 β€” VERDICT

Status: reproduced (impact: panic, confidence: certain).

Narrative

The PoC forger (cribbing the proven DF-2616/DF-2624 technique) walks volhdr -> sroot blockset -> PFS inodes, finds the inode named testvol (inode block at image offset 0x1400800 on our base image), flips meta.type (inode block offset +0x50 per hammer2_disk.h hammer2_inode_meta_t) from 1 (DIRECTORY) to 0x42, sets CHECK_NONE on the poison bref and the sroot bref, zeroes mirror_tid on the poison bref so recovery does not recurse, and recomputes the volume-header CRC32Cs.

On the guest (stock INVARIANTS kernel, DragonFly 6.5-DEVELOPMENT #0):

  1. mount_hammer2 -o ro /dev/vn1@testvol /mnt/h2636 succeeded (console: hammer2_mount: devstr="/dev/vn1@testvol" ... rdonly=1, HAMMER2: INITIATE SPANs). No type validation exists on the mount path.
  2. The first VFS_ROOT β€” triggered by the lstat inside ls -la β€” panics exactly at the cited site:
panic: hammer2: unhandled objtype 66
hammer2_igetv() at hammer2_igetv+0x42a
hammer2_vfs_root() at hammer2_vfs_root+0xdc
nlookup() at nlookup+0xb9c
kern_stat() at kern_stat+0x1d
sys_lstat() at sys_lstat+0x57

The ssh session died at the panic (guest froze in ddb) β€” panic.txt and run.log contain the serial-console capture.

Why this is the cited bug

hammer2_igetv() at sys/vfs/hammer2/hammer2_inode.c:791-793: default: panic("hammer2: unhandled objtype %d", ip->meta.type); ip->meta was populated from media in hammer2_pfsalloc (hammer2_vfsops.c:456, iroot->meta = ripdata->meta) with no validation of type. Valid-but-unhandled on-media values include 0, 3, 8, 10 (WHITEOUT), and 11..255.

Exploit ceiling

Unauthenticated kernel panic (DoS) from a crafted filesystem image at mount/first-access; requires the ability to mount a hammer2 image (root). No memory-corruption primitive β€” the switch is a straight dispatch on an unvalidated byte, the default branch is a controlled panic.

Fix validation

fix.diff changes the default branch to log, mark the vp VBAD, vx_put it, and return EINVAL; if (vp == NULL) break; guards the tail of the loop. Built with make nativekernel in the guest, installed, rebooted: the same forged image then mounts and ls returns an error instead of panicking (see fix_run.log, fix_build.log). Baseline panic confirmed on the stock kernel in the same session.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff (default case -> kprintf + VBAD + vx_put + *errorp=EINVAL, with 'if (vp == NULL) break;' guard) applied to the guest /usr/src, kernel rebuilt with 'make nativekernel' + installkernel (fix_build.log), rebooted into kernel #1. Re-running the exact PoC: mount succeeds, 'ls -la /mnt/h2636' returns 'Invalid argument' instead of panicking, guest stays up (fix_run.log). Baseline panic confirmed on stock kernel #0 in the same session.

fix_build.log (install completed), fix_run.log (MOUNT_RC=0, ls: Invalid argument, no panic, vm up)
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Sat Aug 29 12:37:23 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Evidence (decisive lines)

panic.txt lines 46-53 (panic + backtrace), run.log (mount lines then trap), forge_2636.py (byte flip at inode block +0x50 per hammer2_disk.h), fix_run.log (fixed kernel: error return, no panic)

PoC changes

Cribbed the DF-2616/DF-2624 image-forger harness (h2common.py volhdr->sroot->PFS walk, CHECK_NONE ancestors, volhdr CRC32C recompute) instead of writing a forger from scratch; targeted the mounted 'testvol' PFS root inode at image offset 0x1400800, field offset +0x50 (meta.type).

Verified recommended fix

Replace the panic with a VBAD vnode + EINVAL error return in hammer2_igetv's default case (see fix.diff).

Verdict

Reproduced on the stock INVARIANTS guest kernel (DragonFly 6.5-DEVELOPMENT #0). A forged hammer2 image with the PFS-root inode's meta.type set to 0x42 (valid values 0,3,8,10,11..255 also work; only 1,2,4,5,6,7,9 are handled) mounts cleanly; the first VFS_ROOT -- the lstat inside ls -la -- panics exactly at hammer2_inode.c:791-793: 'panic: hammer2: unhandled objtype 66', backtrace hammer2_igetv <- hammer2_vfs_root <- nlookup <- kern_stat <- sys_lstat. ip->meta is copied verbatim from media by hammer2_pfsalloc (vfsops.c:456) with no type validation anywhere on the mount path. Controlled panic (DoS) from a crafted image; requires mount privilege. Fixed kernel returns EINVAL instead of panicking (fix validated).