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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3254 Β· 13 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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

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_testable
↓ fix.diffper-fix-DF-3254

Confirmed 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.