Zone-end wrap check uses exact equality while unvalidated layer2->append_off / blockmap next_offset / hint can push next_offset past the zone boundary β allocations returned outside the requested zone on production kernels
Summary
hammer_blockmap_alloc/reserve terminate their offset walk only on exact equality 'next_offset == HAMMER_ZONE_ENCODE(zone+1,0)' (:162/:466), but the walk advances by unvalidated disk-derived values: 'next_offset += layer2->append_off - offset' (:256/:538, append_off a raw uint32 from media - up to ~4GB per iteration, multiple iterations accumulate) and 'next_offset += resv->append_off - offset' (:312/:573). A crafted layer2 append_off (forgeable CRC) - or a crafted vol0_blockmap[].next_offset / caller hint (the volume header CRC is never verified, DF-3042) - moves next_offset past the zone end without ever equaling it, carrying into the zone nibble (zone 8->9 etc.); the entry-side guards KKASSERT(HAMMER_ZONE_DECODE(blockmap->next_offset) == zone) (:137/:449) are INVARIANTS-only. Result: alloc returns offsets in a foreign zone (file data placed into META/BTREE big-blocks), breaking the zone-separation invariant the rest of HAMMER relies on; INVARIANTS kernels panic at one of the many KKASSERTs, production kernels silently store cross-zone data_offsets. All I/O still funnels through the buffer cache with the 52-bit phys mask - impact confined to the attacker's own crafted fs (zone confusion, metadata/data cross-placement), no kernel memory unsafety. Code-trace only (Low). Fix: production zone checks at entry + re-check after each advance + layer2->append_off <= HAMMER_BIGBLOCK_SIZE. [VERIFIED pass2: reproduced - crafted next_offset foreign zone panics at the exact cited 449 guard via first 512KB write; fix A/B same trigger completes with diagnostic no panic]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3077 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 328 B | β raw | |
| VERDICT.md | β | 1.7 KB | β raw | |
| craft_3077.c | β | 2.2 KB | view raw | |
| env.txt | β | 248 B | view raw | |
| fix.diff | β | 1.6 KB | view raw | |
| manifest.json | β | 679 B | view raw | |
| panic.txt | β | 14.2 KB | view raw | |
| phase_hammer3077.sh | β | 794 B | view raw | |
| run.log | β | 393 B | view raw | |
| verdict.json | β | 2.0 KB | view raw |
DF-3077 - Zone-end wrap check uses exact equality; crafted next_offset escapes the zone nibble
Severity Low | verdict: reproduced | impact: panic
Reproduce
Sources in this directory; build with cc -O2 -o <bin> <src> -pthread on the guest.
See VERDICT.md for the full narrative and run.log for the decisive output.
DF-3077 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
Crafted-image reproduction using the DF-3040 forger technique: vol0_blockmap[zone 10 large-data].next_offset patched to HAMMER_ZONE_ENCODE(11, 0) (a foreign zone nibble; volume-header CRC is never verified per DF-3042). Read-only mount succeeds; the first large-data allocation (512KB dd) trips the cited entry guard exactly: panic assertion "HAMMER_ZONE_DECODE(blockmap->next_offset) == zone" failed in hammer_blockmap_reserve at hammer_blockmap.c:449, via hammer_ip_add_bulk/hammer_vop_strategy. On production (non-INVARIANTS) kernels the same crafted state would allocate cross-zone silently, as filed. Preconditions: root mount of crafted image + a >=16K write; local DoS / fs-internal zone confusion only.
Kernel references
- sys/vfs/hammer/hammer_blockmap.c:137
- sys/vfs/hammer/hammer_blockmap.c:449
- sys/vfs/hammer/hammer_blockmap.c:162
- sys/vfs/hammer/hammer_blockmap.c:466
Fix
See fix.diff.
Fix validation (A/B on rebuilt kernel #0 Sun Oct 25 06:10:12 UTC 2026)
fix_status: fixed - A/B validated: identical crafted image + 512KB write on the fix kernel completes with console diagnostic HAMMER(t77) blockmap zone mismatch for zone 10 and no panic (baseline panic.txt)
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)
VERDICT.md + run.log + panic.txt + fix.diff + craft_3077.c (A/B: SURVIVED_3077_AB with zone-mismatch diagnostic)
PoC changes
adjusted from finding sketch to guest syscall/layout reality
Verified recommended fix
fix.diff adds a production zone check with early EINVAL return at both blockmap entry sites and turns the wrap equality checks into >=; A/B validated on the fixed kernel.
Verdict
Crafted-image reproduction using the DF-3040 forger technique: vol0_blockmap[zone 10 large-data].next_offset patched to HAMMER_ZONE_ENCODE(11, 0) (a foreign zone nibble; volume-header CRC is never verified per DF-3042). Read-only mount succeeds; the first large-data allocation (512KB dd) trips the cited entry guard exactly: panic assertion "HAMMER_ZONE_DECODE(blockmap->next_offset) == zone" failed in hammer_blockmap_reserve at hammer_blockmap.c:449, via hammer_ip_add_bulk/hammer_vop_strategy. On production (non-INVARIANTS) kernels the same crafted state would allocate cross-zone silently, as filed. Preconditions: root mount of crafted image + a >=16K write; local DoS / fs-internal zone confusion only.
No comments yet.