Crafted image triggers kernel panic via unimplemented directory hash algorithm (ALG2/ALG3)
Summary
hammer_subs.c:953 switch(dip->ino_data.cap_flags & HAMMER_INODE_CAP_DIRHASH_MASK=0x03). ALG0(0x00) ALG1(0x01) implemented. ALG2(0x02) ALG3(0x03) default :1026-1032 hpanic("bad algorithm %p",dip) = kernel panic. cap_flags read verbatim from disk at hammer_inode.c:525 no semantic validation. CRC32 checked but trivially recomputable by attacker. Trigger: crafted HAMMER1 image directory inode cap_flags bits[1:0]=0x02 mount then any filename operation (touch/ls/create/rename). Fix: fall back to ALG0 + kprintf warning instead of panic.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0838 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| image_patcher.c | trigger-source | in-guest tool that flips directory-inode cap_flags to ALG2 on a HAMMER1 v6 image and refreshes the leaf data_crc + B-Tree node CRC | 7.1 KB | view raw |
| repro.sh | repro-script | end-to-end driver: newfs_hammer -> mkdir -> umount -> patch -> mount -> ls (panics on unpatched) | 2.4 KB | view raw |
| build.sh | build-script | cc image_patcher.c -lz | 231 B | view raw |
| run.sh | run-script | alias of repro.sh chain | 1.6 KB | view raw |
| panic.txt | panic-signature | baseline panic in hammer_direntry_namekey via hammer_vop_nresolve on unpatched #0 | 632 B | view raw |
| run.log | run-log | PATCHED #1 kernel: ls/cat succeed, no panic, fallback warning in dmesg | 992 B | view raw |
| fix_build.log | build-log | full nativekernel build of the single-fix kernel (#1), NK_DONE rc=0 | 5.6 MB | β download |
| env.txt | environment | uname, cc version, vfs.hammer.supported_version, hammer utility paths | 799 B | view raw |
| fix.diff | suggested-fix | two-file fix: add HAMMER_INODE_BADDIRHASH flag; replace hpanic in default case with ALG0 fallback + once-per-inode kprintf warning | 1.7 KB | view raw |
| README.md | readme | human-facing build/run/expected docs | 3.1 KB | β raw |
| VERDICT.md | verdict | full mechanism walkthrough, evidence, fix validation | 6.7 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0838 β build & run
Build (the in-guest image-patcher)
cc -O2 -Wall -o image_patcher image_patcher.c -lz
The C source uses only zlib's crc32() (matches HAMMER vβ€6 inode/leaf CRC).
Run as root on the DragonFlyBSD guest.
Reproduce the panic (full chain)
Driver script repro.sh (run as root inside the guest) does everything:
ccthe patchertruncate -s 2G /root/df0838.imgvnconfig vn /root/df0838.imgβ e.g.vn4newfs_hammer -L df0838 -V 6 -f /dev/vn4(forces vol_version=6 so leaf CRC is plain crc32, matching the patcher)mount_hammer+mkdir testdir+ populatetestdir/file1+umount- Run
image_patcheragainst the raw image file: * finds every directory inode (ver=1, mode=0755, obj_type=1) * flipscap_flagsbits[1:0] to0x02(ALG2 β unimplemented) * recomputes the per-leafdata_crc32* recomputes the parent B-Tree node's CRC (4 KiB node, CRC over bytes [4,4096)) vnconfig vn /root/df0838.imgagainmount_hammer /dev/vnN /mnt/df0838ls -la /mnt/df0838/testdir/file1β panics the unpatched kernel
Expected on the UNPATCHED kernel (#0 baseline)
panic: hammer_direntry_namekey: bad algorithm 0xfffff80116ed4640
cpuid = 3
Trace beginning at frame 0xfffff80118b07418
hammer_direntry_namekey() at hammer_direntry_namekey+0x1ae
hammer_vop_nresolve() at hammer_vop_nresolve+0x20f
vop_nresolve() at vop_nresolve+0x53
cache_resolve() at cache_resolve+0x61e
nlookup() at nlookup+0x8e1
Debugger("panic")
Stopped at Debugger+0x7c: movb $0,0xbdaf09(%rip)
db>
The trace points exactly at hammer_subs.c:1031 (hpanic("bad algorithm"))
reached via hammer_vnops.c:1220 (hammer_vop_nresolve).
Expected on the PATCHED kernel (#1, with fix.diff applied)
mount rc=0 -rw-r--r-- 1 root wheel 6 ... /mnt/df0838/testdir/file1 ls rc=0 hello # dmesg: hammer_direntry_namekey: dir ino 0000000000000001 has unknown dirhash alg 2, falling back to ALG0 hammer_direntry_namekey: dir ino 0000000100000615 has unknown dirhash alg 2, falling back to ALG0
No panic, file accessible, fallback to ALG0.
Realism / reachability (Phase 6)
Triggering requires mount_hammer of the attacker's crafted image. On a real
system an attacker:
- Crafts the HAMMER1 image offline (their own machine) β every step in
image_patcher.cworks just as well on a copy ofhammer_disk.hplus zlib. - Ships it to a victim (USB / download / mail attachment).
- Victim mounts the image β either as root, or, with
vfs.usermount=1and ownership of the image+mountpoint, as any unprivileged user. - Any subsequent filename op (
ls /mnt/foo,cat /mnt/x,touch, rename) under the corrupted directory panics the kernel.
This is a kernel DoS via a crafted filesystem image β a classic attacker- supplied-parses-data threat. No escalation chain applies (the panic kills the kernel before any primitive can be derived); impact is panic / DoS.
cap_flags is read verbatim from disk at hammer_inode.c:525 with no
semantic validation of the DIRHASH bits β only the leaf data_crc32 (which
the attacker can recompute) is checked.
DF-0838 β VERDICT
Finding: Crafted HAMMER1 image triggers kernel panic via unimplemented directory-hash algorithm (ALG2/ALG3 bits).
Severity: Low (local DoS via crafted filesystem image β requires the victim to mount an attacker-supplied image).
Verdict: REPRODUCED. Fix validated.
Mechanism
hammer_direntry_namekey(dip, name, len, max_iter) at
sys/vfs/hammer/hammer_subs.c:944 is the directory-name hash function. It
switches on the low 2 bits of dip->ino_data.cap_flags:
case HAMMER_INODE_CAP_DIRHASH_ALG0: ... break; /* 0x00 β crc32 */
case HAMMER_INODE_CAP_DIRHASH_ALG1: ... break; /* 0x01 β domain hash */
case HAMMER_INODE_CAP_DIRHASH_ALG2:
case HAMMER_INODE_CAP_DIRHASH_ALG3:
default:
key = 0;
*max_iterationsp = 1;
hpanic("bad algorithm %p", dip); /* <-- hammer_subs.c:1031 */
break;
ALG0 and ALG1 are the only implemented algorithms; ALG2/ALG3 are reserved and
unimplemented. The default case calls hpanic (an always-panic wrapper).
dip->ino_data is the in-memory copy of the on-disk struct hammer_inode_data
(hammer_disk.h:880). It is loaded verbatim from disk at
hammer_inode.c:525:
ip->ino_data = cursor.data->inode;
with no semantic validation of the cap_flags byte (offset 65 of the struct).
The only on-disk integrity check is the leaf data_crc32
(hammer_crc.h:265-289), which the attacker can freely recompute after
modifying cap_flags. The CRC is crc32 for vol_version β€6, iscsi_crc32
for β₯7.
The directory-hash function is reached from every directory-modifying /
lookup VOP β hammer_vop_nresolve (path lookup, hammer_vnops.c:1220),
hammer_vop_ncreate (hammer_vnops.c:3310), hammer_vop_nlink
(hammer_vnops.c:2000), and hammer_ip_create (hammer_object.c:676) β so
any filename operation under the corrupted directory triggers the panic.
Trigger / PoC
End-to-end reproduction driver is in repro.sh (run as root inside the guest).
The image-corruption tool is image_patcher.c.
- Build a HAMMER1 v6 image (
newfs_hammer -V 6). - Populate it (
mkdir testdir; echo hello > testdir/file1). - Run
image_patcheron the raw image: * Locates every directory inode by signature (ver=1, mode=0o755, obj_type=HAMMER_OBJTYPE_DIRECTORY=1). * Setscap_flagsbits[1:0] to0x02(ALG2) on every directory. * Recomputes the per-leafdata_crc32(over the inode's first 112 bytes =HAMMER_INODE_CRCSIZE). * Recomputes the parent B-Tree node's own CRC (hammer_crc.h:227:crc32(&node->crc+1, sizeof(node)-4)). - Mount the modified image and do any filename operation under it.
The patcher modifies three directory inodes by default (root + an
intermediate dir + testdir), so even a ls /mnt/somefile lookup (which
goes through the root directory's hash) hits the panic β there's no need to
navigate into testdir specifically.
Evidence
Baseline panic (unpatched kernel 6.5-DEVELOPMENT #0, after ls -la
/mnt/df0838/testdir/file1):
panic: hammer_direntry_namekey: bad algorithm 0xfffff80116ed4640
cpuid = 3
Trace beginning at frame 0xfffff80118b07418
hammer_direntry_namekey() at hammer_direntry_namekey+0x1ae 0xffffffff80951f5e
hammer_direntry_namekey() at hammer_direntry_namekey+0x1ae 0xffffffff80951f5e
hammer_vop_nresolve() at hammer_vop_nresolve+0x20f 0xffffffff8095743f
vop_nresolve() at vop_nresolve+0x53 0xffffffff8070c3c3
cache_resolve() at cache_resolve+0x61e 0xffffffff806e06fe
nlookup() at nlookup+0x8e1 0xffffffff806ef621
Debugger("panic")
Stopped at Debugger+0x7c: movb $0,0xbdaf09(%rip)
db>
The stack pins the bug exactly: hammer_direntry_namekey+0x1ae is the
hpanic call site at hammer_subs.c:1031.
Reproduced twice from fresh vm.sh reset with-src snapshots; deterministic.
Impact
- Confidentiality: none.
- Integrity: none (the panic is reached before any state change).
- Availability: kernel panic β full system crash β local DoS.
- Preconditions: the victim must
mount_hammeran attacker-supplied HAMMER1 image. Root can do this directly; an unprivileged user can do this whenvfs.usermount=1and the image+mountpoint are owned by them (a normal configuration for "mount this USB / image" workflows).
No escalation chain applies β the panic fires synchronously inside the syscall path before any corruption primitive can be derived. Impact is DoS only, matching the original Low severity.
Fix
Authored in fix.diff. The change is two-file:
-
sys/vfs/hammer/hammer.hβ add a per-inode flagHAMMER_INODE_BADDIRHASH(0x10000000) to log the warning exactly once per affected directory inode. -
sys/vfs/hammer/hammer_subs.c:1026β in thedefaultcase, instead ofhpanic("bad algorithm %p", dip), fall back to the ALG0 computation (identical code to the ALG0 case) and emit onehdkprintfwarning.
This is the minimal, root-cause fix: an unimplemented value in attacker-controlled on-disk metadata should never be a fatal assertion. The filesystem continues to operate (the ALG0 hash is correct for any directory, so existing entries β which the attacker has not moved β remain findable).
Fix validated on a single-fix kernel
- Unpatched kernel
#0(6.5-DEVELOPMENT #0, Thu Jul 2 06:02:54 UTC 2026): the PoC panics, signature above. - Single-fix kernel
#1(built fromwith-src+fix.diff,6.5-DEVELOPMENT #1, Tue Jul 14 01:05:39 UTC 2026, sha256b726a337β¦855602924):mount_hammersucceeds,ls /mnt/df0838/testdir/file1returns the file,catreturnshello, no panic, dmesg logshammer_direntry_namekey: dir ino β¦ has unknown dirhash alg 2, falling back to ALG0. - Repeated 3Γ to confirm determinism.
The fix supersedes the finding markdown's proposal ("fall back to ALG0 + kprintf warning") by implementing exactly that, plus a once-per-inode log guard to avoid log spam.
Citations
sys/vfs/hammer/hammer_subs.c:944βhammer_direntry_namekeyfunctionsys/vfs/hammer/hammer_subs.c:1026-1032β vulnerabledefaultcasesys/vfs/hammer/hammer_subs.c:1031βhpanic("bad algorithm %p", dip)sys/vfs/hammer/hammer_inode.c:525βcap_flagsloaded verbatim from disksys/vfs/hammer/hammer_disk.h:880-916βstruct hammer_inode_datasys/vfs/hammer/hammer_disk.h:927-931β DIRHASH_{MASK,ALG0..ALG3} constantssys/vfs/hammer/hammer_vnops.c:1220βhammer_vop_nresolvecallersys/vfs/hammer/hammer_crc.h:71βhammer_datacrc= crc32 for vβ€6sys/vfs/hammer/hammer_crc.h:227β B-Tree node CRC computationsys/vfs/hammer/hammer_crc.h:265-289β leafdata_crccomputationsys/vfs/hammer/hammer_btree.h:218-248βstruct hammer_node_ondisk
Fix verification
fixedVALIDATED: baseline panic at hammer_direntry_namekey+0x1ae; patched ls rc=0, cat=hello, dmesg 'falling back to ALG0'.
BEFORE: panic hammer_direntry_namekey bad algorithm. AFTER: ls rc=0, hello, dmesg fallback. x3 runs.
Confirmed kernel references
- sys/vfs/hammer/hammer_subs.c:944
- sys/vfs/hammer/hammer_subs.c:1026
- sys/vfs/hammer/hammer_subs.c:1031
- sys/vfs/hammer/hammer_inode.c:525
- sys/vfs/hammer/hammer_disk.h:880
- sys/vfs/hammer/hammer_disk.h:927
- sys/vfs/hammer/hammer_vnops.c:1220
- sys/vfs/hammer/hammer_crc.h:71
- sys/vfs/hammer/hammer_crc.h:227
- sys/vfs/hammer/hammer_btree.h:218
Detail
Exploit chain
none -- hpanic fires synchronously in VOP path. DoS only. No write.
Evidence (decisive lines)
BASELINE: panic hammer_direntry_namekey+0x1ae. PATCHED: ls rc=0, cat=hello, dmesg 'unknown dirhash alg 2, falling back to ALG0'.
PoC changes
Authored from scratch: image_patcher.c (flips cap_flags to ALG2 + recomputes leaf+BTree CRC), run.sh, fix.diff (fall back to ALG0 + once-per-inode kprintf), VERDICT.md, manifest.json.
Verified recommended fix
Replace hpanic in default case at hammer_subs.c:1031 with ALG0 hash + once-per-inode kprintf warning (HAMMER_INODE_BADDIRHASH guard). Full git-apply-able diff in findings/poc/DF-0838/fix.diff.
Verdict
REPRODUCED. hammer_direntry_namekey switch default hpanic(bad algorithm) for any cap_flags DIRHASH bits != 0 or 1. Crafted HAMMER v6 image with cap_flags ALG2 -> mount + ls panic. Deterministic 2/2.
No comments yet.