hammer_ioc_dedup validates the forged data_offset zone AFTER hammer_btree_extract_data has already dereferenced it β privileged INVARIANTS-kernel panic (zone-0 KKASSERT) and attacker-steered in-volume wild read on production kernels (DF-3187 sink via the gated dedup entry point)
Summary
hammer_dedup.c calls hammer_btree_extract_data on both cursors (:66,:82) before checking hammer_is_zone_data on the extracted leaves' data_offset (:92-93). On a crafted v5+ HAMMER image, a leaf with a forged zone-0 (or non-data-zone) data_offset is dereferenced first: _hammer_bread hits KKASSERT(HAMMER_ZONE(buf_offset)!=0) at hammer_ondisk.c:1123 -> panic on INVARIANTS kernels; on production kernels zone-0 falls through the raw-buffer path into an attacker-steered wild read anywhere the volume decodes to, feeding the CRC test and bcmp (1-bit CMP_FAILURE oracle; no copyout unlike DF-3187), and any surviving zone-8/9 offset with mismatched layer2 zone then panics the blockmap KKASSERTs or corrupts freemap accounting on the attacker's own image. The check exists but is ordered after the sink; the INVALID_ZONE flag path at :94 proves the authors knew extract can yield non-data zones. Gated by caps_priv_check(SYSCAP_NOVFS_IOCTL) - privileged sibling of DF-3187, covered by that finding's extract-chokepoint validation, but the ordering flaw lives here. Not verified (Low, privileged-gated; panic primitive same as DF-3187's live-demoed KKASSERT). Fix: extract record, validate zones, then extract data. [VERIFIED pass2: reproduced - DEDUP with non-zero zone-0 data_offset panics in _hammer_bread KKASSERT via the 66 ordering; refinement raw 0 caught by btree 730 guard; fix A/B clean INVALID_ZONE return]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3254 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| patch_doff.c | β | 3.9 KB | view raw | |
| dedup_trigger.c | β | 1.9 KB | view raw | |
| panic.txt | β | 924 B | view raw | |
| README.md | β | 866 B | β raw | |
| VERDICT.md | β | 2.8 KB | β raw | |
| build.sh | β | 314 B | view raw | |
| run.sh | β | 174 B | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| verdict.json | β | 3.0 KB | view raw | |
| fix.diff | β | 1.4 KB | view raw | |
| env.txt | β | 272 B | view raw | |
| ab_fixed.log | β | 340 B | view raw | |
| compile_validation.txt | β | 1.8 KB | view raw |
DF-3254 -- verification pack
Finding: hammer_ioc_dedup validates the data zone AFTER extract has dereferenced it
Status: reproduced / impact panic / confidence certain
What was run
guest: cc -O -o patch_doff patch_doff.c; cc -O -I/usr/src/sys/vfs/hammer -o dedup_trigger dedup_trigger.c; image: newfs_hammer + a data file + synctid, offline-patch a DATA leaf data_offset to 0x100000 with CRC fix
How to reproduce
guest: mount_hammer /dev/vn0 /mnt/h1 && ./dedup_trigger /mnt/h1 --copy h1.img <element-offset>
Expected
panic: assertion HAMMER_ZONE(buf_offset) != 0 failed in _hammer_bread (see panic.txt); with data_offset exactly 0 the ioctl instead returns INVALID_ZONE harmlessly
See VERDICT.md for the full narrative and verdict.json for the persisted machine verdict. Artifacts: patch_doff.c, dedup_trigger.c, panic.txt, run.log.
DF-3254 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
HAMMERIOC_DEDUP with a leaf whose data_offset was forged to a NON-ZERO zone-0 value (0x100000) panics the INVARIANTS kernel exactly through the claimed ordering: hammer_ioc_dedup.c:66 calls hammer_btree_extract_data BEFORE the hammer_is_zone_data validation at :92-93, and _hammer_bread hits KKASSERT(HAMMER_ZONE(buf_offset) != 0) at hammer_ondisk.c:1123 - live trace: panic assertion failed in _hammer_bread, backtrace hammer_bread_ext -> hammer_btree_extract -> hammer_ioc_dedup -> hammer_ioctl -> hammer_vop_ioctl. Verification refines the original claim: a raw data_offset of exactly 0 is caught by the early guard at hammer_btree.c:730-731 (extract returns 0 with NULL data and the INVALID_ZONE flag fires cleanly, as observed in the first attempt); the zone validation is only ordered after a dereference for non-zero zone-0 (and forged non-data-zone) offsets. Privileged ioctl on a crafted RW-mounted image; availability only.
Kernel references
- sys/vfs/hammer/hammer_dedup.c:66
- sys/vfs/hammer/hammer_dedup.c:82
- sys/vfs/hammer/hammer_dedup.c:92-93
- sys/vfs/hammer/hammer_ondisk.c:1123
- sys/vfs/hammer/hammer_btree.c:730-731
Exploit chain
(none: availability/correctness finding; no privilege chain)
Fix
extract leaf and zone-check BEFORE extract_data for both cursors; combined fixed kernel: the same trigger returns cleanly with the INVALID_ZONE flag and no panic
See fix.diff in this directory (git-apply-able against the read-only sys/ tree; applied only inside the disposable verification guest).
Reproduction
build: guest: cc -O -o patch_doff patch_doff.c; cc -O -I/usr/src/sys/vfs/hammer -o dedup_trigger dedup_trigger.c; image: newfs_hammer + a data file + synctid, offline-patch a DATA leaf data_offset to 0x100000 with CRC fix
run: guest: mount_hammer /dev/vn0 /mnt/h1 && ./dedup_trigger /mnt/h1 --copy h1.img
expected: panic: assertion HAMMER_ZONE(buf_offset) != 0 failed in _hammer_bread (see panic.txt); with data_offset exactly 0 the ioctl instead returns INVALID_ZONE harmlessly
Notes on the PoC (what changed vs the finding sketch)
first attempt used data_offset 0 (caught by the btree.c:730 guard, INVALID_ZONE flag, no panic); second used non-zero zone-0 0x100000 which passes the guard into the KKASSERT; also the DATA records live in zones 10/11 on this newfs, not 8/9, so the patcher filter was widened
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_testableConfirmed kernel references
Detail
Evidence (decisive lines)
panic.txt (assertion + full backtrace), run.log (patch output showing the DATA leaf and its zone-10 offset being rewritten to 0x100000), patch_doff.c (leaf data_offset rewriter with node CRC recompute), dedup_trigger.c
PoC changes
first attempt used data_offset 0 (caught by the btree.c:730 guard, INVALID_ZONE flag, no panic); second used non-zero zone-0 0x100000 which passes the guard into the KKASSERT; also the DATA records live in zones 10/11 on this newfs, not 8/9, so the patcher filter was widened
Verified recommended fix
extract leaf and zone-check BEFORE extract_data for both cursors; combined fixed kernel: the same trigger returns cleanly with the INVALID_ZONE flag and no panic
Verdict
HAMMERIOC_DEDUP with a leaf whose data_offset was forged to a NON-ZERO zone-0 value (0x100000) panics the INVARIANTS kernel exactly through the claimed ordering: hammer_ioc_dedup.c:66 calls hammer_btree_extract_data BEFORE the hammer_is_zone_data validation at :92-93, and _hammer_bread hits KKASSERT(HAMMER_ZONE(buf_offset) != 0) at hammer_ondisk.c:1123 - live trace: panic assertion failed in _hammer_bread, backtrace hammer_bread_ext -> hammer_btree_extract -> hammer_ioc_dedup -> hammer_ioctl -> hammer_vop_ioctl. Verification refines the original claim: a raw data_offset of exactly 0 is caught by the early guard at hammer_btree.c:730-731 (extract returns 0 with NULL data and the INVALID_ZONE flag fires cleanly, as observed in the first attempt); the zone validation is only ordered after a dereference for non-zero zone-0 (and forged non-data-zone) offsets. Privileged ioctl on a crafted RW-mounted image; availability only.
No comments yet.