No radix-alignment/64KB-window validation of on-disk data_off β chain->data + chain->bytes overruns the DIO buffer (OOB read/write)
| Field | Value |
|---|---|
| ID | DF-2616 |
| Status | new |
| Severity | High |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-787 Out-of-bounds Write / CWE-125 Out-of-bounds Read |
| File | sys/vfs/hammer2/hammer2_chain.c |
| Lines | 938-1100 |
| Area | vfs |
| Confidence | certain |
| Discovered | 2026-08-28 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | hammer2 |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
hammer2_chain_alloc/load_data derive chain->bytes purely from the low 6
radix bits of the attacker-supplied bref->data_off and never check that the
block offset is aligned to its radix or fits inside one 64KB DIO window. The
only guard is a KKASSERT in hammer2_io_alloc (hammer2_io.c:122-126) which
is compiled out without INVARIANTS. With a valid radix (<=16) but a misaligned
or window-crossing offset (e.g. data_off=0x18010: radix 16, lbase 0x18000),
hammer2_io_data() returns bp->b_data+0x8000 inside a 65536-byte buffer
while chain->bytes=65536, so the last 32KB of every access to chain->data
lands past the end of the kernel buffer.
Root cause
chain.c:189-192 computes bytes = 1U << (data_off & HAMMER2_OFF_MASK_RADIX)
with no geometry check. chain.c:998-1000 (load_data) and 1793-1799
(modify COW) pass bref->data_off straight to hammer2_io_bread/io_new;
chain.c:1048 hammer2_io_data() returns
bp->b_data + ((data_off & ~HAMMER2_OFF_MASK_RADIX) - pbase) whose own
KKASSERT (hammer2_io.c:565) passes because the offset itself is in-window;
chain.c:1100 sets chain->data to that pointer. The window-crossing
condition is only asserted at hammer2_io.c:122-126, a no-op on
non-INVARIANTS builds. Consequences: (a) indirect-block parents compute
count = parent->bytes/128 (chain.c:2532) so combined_find/base_find
iterate base[] entries up to 64KB past the buffer (OOB read interpreted as
blockrefs); (b) the COW bcopy(chain->data, bdata, chain->bytes) at
chain.c:1827 copies past-buffer heap into a file's new data block (direct
kernel-heap disclosure via read()); (c) hammer2_base_delete
bzero(scan,...) (chain.c:5195) and hammer2_base_insert
base[i]=*elm / bcopy shifts (chain.c:5303, 5323-5334) write
attacker-chosen 128-byte blockrefs past the buffer end (heap corruption
primitive). Check hashes (XXH64/ICRC32/SHA256 over chain->bytes at
5391-5416, 5538-5584) also over-read. Distinct from DF-0763/DF-2605 (radix
magnitude 17-31): fires with a perfectly valid radix; needs an
alignment/window-bounds check, not a radix range check.
Threat model & preconditions
- Attacker position: crafted filesystem image (mount-time; same
precondition class as DF-0763). The bref lives in an on-disk blockref array
whose CRC the attacker computes. Any traversal that instantiates the chain
(
ls/stat/readdir through the crafted indirect) reaches load_data. - Privileges gained or impact: on INVARIANTS kernels (stock
X86_64_GENERIC) a deterministic panic at
hammer2_io.c:126; on non-INVARIANTS builds (MINI64, custom performance kernels) a kernel heap OOB read AND write of up to 64KB with attacker-influenced content and offset β info leak into file contents/readdir names plus heap corruption suitable for privilege escalation. - Required config or capabilities: mount of the crafted image (root, or
vfs.usermount=1+ owned device). - Reachability: one path-resolution syscall through the crafted indirect.
Proof of concept
PoC seed: findings/poc/DF-2616/ (image-builder recipe).
Build & run
mkfs image; patch a directory inode's blockset with
{type=INDIRECT, keybits=10, methods=0x00 (CHECK_NONE), data_off=0x00018010};
recompute parent check; mount; ls /mnt/dir; touch /mnt/dir/newname
Expected output
INVARIANTS: panic at hammer2_io.c:126 ("Illegal:").
Non-INVARIANTS: readdir returns names read from past-buffer heap; the touch
performs an OOB 128-byte write past the DIO buffer; COW bcopy leaks 32KB of
adjacent heap into readable file data.
Impact
Kernel heap OOB read + write (up to 64KB) from a mounted image, on non-INVARIANTS kernels; reliable panic on stock INVARIANTS kernels. Heap corruption primitive suitable for unprivβroot on the former.
Recommended fix
Validate the data_off geometry (radix bound, radix alignment, single 64KB
window) in hammer2_chain_load_data before any I/O, and mirror the check
where dedup_off is installed in hammer2_chain_modify (chain.c:1611):
--- a/sys/vfs/hammer2/hammer2_chain.c
+++ b/sys/vfs/hammer2/hammer2_chain.c
@@ -935,8 +935,43 @@ hammer2_chain_load_data(hammer2_chain_t *chain)
if ((chain->bref.data_off & ~HAMMER2_OFF_MASK_RADIX) == 0)
- return;
+ return; /* embedded (validated below by type) */
+
+ /*
+ * Validate the media reference geometry. bref fields come
+ * from untrusted on-disk data. The radix must be within the
+ * DIO size, the block must be aligned to its radix, and the
+ * block must not cross a 64KB DIO window boundary. The DIO
+ * layer only asserts these conditions (hammer2_io.c), which is
+ * compiled out without INVARIANTS.
+ */
+ {
+ int radix;
+ hammer2_off_t lbase;
+ hammer2_off_t lsize;
+
+ radix = (int)(chain->bref.data_off & HAMMER2_OFF_MASK_RADIX);
+ lbase = chain->bref.data_off & ~HAMMER2_OFF_MASK_RADIX;
+ lsize = (hammer2_off_t)1 << radix;
+ if (radix > HAMMER2_PBUFRADIX ||
+ (lbase & (lsize - 1)) != 0 ||
+ (lbase & (hammer2_off_t)HAMMER2_PBUFMASK) + lsize >
+ (hammer2_off_t)HAMMER2_PBUFSIZE) {
+ chain->error = HAMMER2_ERROR_CHECK;
+ krateprintf(&krate_h2chk,
+ "chain %016jx.%02x bad data_off "
+ "geometry\n",
+ chain->bref.data_off,
+ chain->bref.type);
+ return;
+ }
+ }
hmp = chain->hmp;
References
- DF-0763 / DF-2605 (radix magnitude class β this is the geometry class)
- hammer2_io.c:122-126 (assert-only guard), hammer2_io.c:565 (in-window check)
Timeline
- 2026-08-28 Discovered during automated audit (pass 2, GLM 5.3).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2616 Β· 27 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 4.1 KB | β raw | |
| VERDICT.md | β | 6.5 KB | β raw | |
| mkbase.sh | β | 837 B | view raw | |
| forge_E.py | β | 8.2 KB | view raw | |
| forge_df2616.py | β | 11.0 KB | view raw | |
| h2_A_sroot_cross.img | β | 64.0 MB | β download | |
| h2_B_data_cross.img | β | 64.0 MB | β download | |
| h2_E_groom10.img | β | 64.0 MB | β download | |
| h2_E_groom.img | β | 64.0 MB | β download | |
| h2_E_flushed.img | β | 64.0 MB | β download | |
| trigger_leak.sh | β | 718 B | view raw | |
| trigger_write.sh | β | 1.3 KB | view raw | |
| trigger_D.sh | β | 1.7 KB | view raw | |
| trigger_E.sh | β | 1.9 KB | view raw | |
| panic_A.txt | β | 2.1 KB | view raw | |
| panic_B.txt | β | 1.2 KB | view raw | |
| fault_B_noinv.txt | β | 710 B | view raw | |
| run_E_noinv.log | β | 3.0 KB | view raw | |
| forensic_E.txt | β | 1.3 KB | view raw | |
| env.txt | β | 678 B | view raw | |
| build_noinv.log | β | 5.6 MB | β download | |
| build_fix.log | β | 5.7 MB | β download | |
| fix.diff | β | 3.8 KB | view raw | |
| fix_v1_too_strict.txt | β | 1.2 KB | view raw | |
| fix_run.log | β | 862 B | view raw | |
| code_hashes.txt | β | 231 B | view raw | |
| verdict.json | β | 7.7 KB | view raw |
DF-2616 β OOB read/write past the 64KB DIO buffer from crafted hammer2 data_off geometry
Finding: DF-2616 (High, hammer2 bucket) β hammer2_chain_alloc/
hammer2_chain_load_data derive chain->bytes from the radix of the
attacker-supplied bref.data_off and never validate the geometry: the
offset's alignment to its radix or containment within one 64KB DIO window.
The only guard is a KKASSERT in hammer2_io_alloc (hammer2_io.c:126),
compiled out without INVARIANTS. A crafted image with a perfectly valid
radix but a misaligned / window-crossing offset makes chain->data +
chain->bytes overrun the kernel DIO buffer.
What is in this pack
| file | what it is |
|---|---|
mkbase.sh |
guest-side script: builds a clean 64MB hammer2 image with f1 + N marker files |
forge_E.py |
host-side forger: walks volhdrβsrootβPFSβfile-inode blocksets/indirects, relocates each w file's 1KB data block into its own 64KB window (valid geometry), crosses f1's DATA data_off (lbase\|0xFF00, radix kept), sets methods=0x00 (CHECK_NONE) + modify_tid (in-place path), CHECK_NONEs all ancestor brefs, recomputes the 3 volhdr CRC32Cs |
forge_df2616.py |
earlier forger for variants A/B/C (small direct-blockset fs) |
h2_A_sroot_cross.img |
variant A: volhdr.sroot_blockset[0].data_off = 0x180fd0a (radix 10 valid, misaligned+crossing) β mount-time trigger |
h2_B_data_cross.img |
variant B: file f1's DATA bref crossed (0x1c0ff0a), CHECK_NONE |
h2_E_groom10.img |
variant E: f1 crossed (0x200ff0a, radix 10) + 32 marker files relocated one-per-window (grooming) |
h2_E_groom.img |
variant E with f1 = 64KB file (radix-16 crossed, OOB reach 0xFF00) |
trigger_leak.sh, trigger_write.sh, trigger_D.sh, trigger_E.sh |
guest-side trigger sequences |
panic_A.txt |
mount-time panic on stock INVARIANTS kernel (hammer2_io.c:126) |
panic_B.txt |
panic loading the crafted FILE data chain on stock kernel |
fault_B_noinv.txt |
kernel page fault in memmove (OOB bcopy source) on the no-INVARIANTS kernel |
run_E_noinv.log |
groomed run on no-INVARIANTS kernel: crossed read survives (mapped neighbor), full sequence |
forensic_E.txt |
post-run image forensics: attacker pattern planted through the crossed pointer at 0x200ff05 |
h2_E_flushed.img |
the flushed crafted image (evidence) |
build_noinv.log |
full build log of the no-INVARIANTS kernel #1 |
fix.diff |
the verified fix (geometry validation in load_data/modify/dedup + io.c log guard) |
build_fix.log / fix_run_*.log |
fix kernel build + before/after validation runs |
verdict.json / manifest.json / VERDICT.md |
machine + human verdicts |
Reproduce
- Base image: run
mkbase.shon the guest (root), pullbase.imgto the host. - Forge:
python3 forge_E.py base.img h2_E_groom.img(needs python3 on host). - Push back, then on the guest as root:
sh trigger_E.sh. - Stock INVARIANTS kernel: mounting variant A (or reading f1 on B/E)
panics at
hammer2_io.c:126β seepanic_A.txt/panic_B.txt. - no-INVARIANTS kernel (
options INVARIANTScommented out,make -j6 nativekernel KERNCONF=X86_64_GENERIC): the KKASSERT is gone; the crossed access either page-faults inmemmove(ungroomed:fault_B_noinv.txt) or, with the 32-marker grooming, reads/writes the kernel buffer in the adjacent KVA slot (run_E_noinv.log+forensic_E.txt). - Fix:
git apply fix.diff, rebuild, reboot β mount of A fails cleanly, reading f1 on E fails cleanly withillegal data_off geometry(seefix_run_*.log), no panic, no fault.
Expected observable results
- INVARIANTS kernel:
Illegal: ...+panic: assertion "pbase != 0 && ..."athammer2_io_allocβ deterministic. - no-INVARIANTS + ungroomed:
Fatal trap 12: page fault while in kernel mode ... memmove+0x28(DDB stop). - no-INVARIANTS + groomed: no fault;
read(2)of f1 returns bytes from beyond the DIO buffer;write(2)plants attacker content through the crossed pointer (on-media proof at image offset0x200ff05). - Fixed kernel: clean
EINVAL/EIOfailures,illegal data_off geometryconsole message.
DF-2616 β Verdict
REPRODUCED β Missing geometry validation of on-disk bref.data_off in
hammer2 chain loading. Impact demonstrated end-to-end:
- Stock kernel (X86_64_GENERIC, INVARIANTS ON): deterministic panic
(mount-time and file-read-time) at the
KKASSERTinhammer2_io_alloc(hammer2_io.c:126) β reliable local DoS from a crafted image. - Non-INVARIANTS (release-style) kernel: kernel heap OOB read AND write past the 64KB DIO buffer, plus a kernel-mode page fault (DoS) when the adjacent KVA slot is unmapped.
uid=0 escalation was NOT achieved within this run; the write primitive was characterized (below) but the adjacent-object placement could not be controlled deterministically on this guest (see "Exploit chain").
The bug, confirmed in source
hammer2_chain_alloc(hammer2_chain.c:189-192) deriveschain->bytespurely from the low 6 radix bits of the attacker-controlledbref.data_off(HAMMER2_OFF_MASK_RADIX = 0x3F,hammer2_disk.h:461). No geometry check anywhere on the load path.hammer2_chain_load_data(hammer2_chain.c:919-1102) only checks the embedded case (data_off & ~MASK == 0, line 938-939), then issues I/O (994-1000) and installschain->data = hammer2_io_data(dio, data_off)(line 1048, 1100) =bp->b_data + (lbase & 0xFFFF).- The only geometry guard is
hammer2_io_allochammer2_io.c:122-126: an unconditionalkprintf("Illegal: ...")(so the pass-through is visible on release kernels) followed by aKKASSERTthat is compiled out without INVARIANTS. On non-INVARIANTS builds execution continues andchain->data + chain->bytesruns past the end of the 64KB DIO buffer. - Distinct from DF-0763/DF-2605 (radix magnitude 17-63): DF-2616 fires with
a perfectly valid radix (10 or 16) β the violation is the geometry
(offset not contained in one 64KB window). Note one refinement to the
finding text discovered during verification: hammer2's freemap allocates
at 1KB granularity, so a radix-N block at a merely-1KB-aligned offset is
legal on real filesystems (the over-strict alignment check in fix v1
broke the root fs boot β see below). The actual unchecked invariant is
exactly what
hammer2_io.c:126asserts:pbase != 0and[lbase, lbase+lsize)within one 64KB window (plus radix β€ 16).
Demonstrated sinks
- OOB read to userspace:
hammer2_strategy_read_completion(hammer2_strategy.c:487)bcopy(data, bp->b_data, focus->bytes)withdata = chain->datacrossed;read(2)of the crafted file returns bytes from beyond the DIO buffer. - OOB write:
hammer2_write_bp(hammer2_strategy.c:1310-1356):hammer2_io_newnz(... data_off ...)βhammer2_io_data()βbcopy(data, bdata, chain->bytes)writes attacker file data through the crossed pointer β in-window part lands at the crossed media offset (forensically recovered), past-end part lands in the adjacent kernel buffer. The overwrite-in-place path (hammer2_chain_modify,chain.c:1504-1516, CHECK_NONE + modify_tid > pfs_lsnap_tid) keeps the crosseddata_offinstead of COWing away from it.
Reproduction summary (full logs in this pack)
- panic_A.txt β variant A (sroot bref
0x180fd0a: radix 10 VALID, lbase0x180fd00, misaligned + crossing), stock kernel:Illegal: 0000000001800000 000000000180fd00+00000400βpanic: assertion ... hammer2_io_alloc at hammer2_io.c:126at MOUNT time. - panic_B.txt β variant B (file f1 DATA bref
0x1c0ff0a): mount OK, first read of/mnt/h2/f1β same panic viahammer2_chain_load_data. - fault_B_noinv.txt β same variant B on kernel #1 (INVARIANTS off):
Fatal trap 12: page fault while in kernel mode ... memmove+0x28 movq (%rsi),%rdx, fault VA0xfffff8006be66000=bp->b_data + 0x10000β the OOB bcopy source ran off the end of the 64KB buffer onto an unmapped page. Kernel stopped in DDB; ssh dead (DoS). - run_E_noinv.log + forensic_E.txt β variant E on kernel #1 with 32
marker files groomed into their own windows (one live DIO buffer each):
the crossed read survived (adjacent KVA slot mapped β returned its
content, zeros in this run, to userspace: the OOB read primitive), and
the crossed write planted the attacker pattern through the crossed
pointer β recovered from the flushed image at media offset
0x200ff05(inside the crossed window tail, exactly wherechain->datapointed). TheIllegal:console marker fired on every crossed access (proving the kernel sailed past the compiled-out guard).
Exploit chain (what was and was not achieved)
Demonstrated: crafted image β mount β read(2) returns out-of-bounds
kernel-buffer memory; write(2) plants attacker-controlled (LZ4-wrapped)
content at a chosen out-of-bounds offset relative to a 64KB kernel buffer
(0x300 bytes past its end in the radix-10 forge; up to 0xFF00 with a
radix-16 chain β that variant faulted on an unmapped intermediate slot).
NOT achieved: deterministic placement of a chosen victim object in the
adjacent KVA slot (so no uid=0). The adjacent slot identity is set by the
fixed per-header KVA scheme (vfs_bio.c:638, b_kvabase =
vm_map_min(buffer_map) + MAXBSIZE*n) and the per-CPU buffer queues, which
the attacker cannot steer precisely from a mounted filesystem in this
setup; in the successful run the slot held a clean, zero-filled buffer
(mapped but never flushed), so the OOB write could not be recovered from
media. The primitives (chosen-offset read/write beyond a kernel buffer
object with attacker content) are in place; weaponization requires a
victim-object grooming strategy against the buffer cache arena.
Fix validation
- fix v1 (alignment-strict) was WRONG: it rejected legitimate radix-N
blocks at 1KB-aligned offsets that real filesystems contain (root fs boot
logged dozens of
illegal data_off geometry 0x...0b/0c/0drejections β radix 11/12/13 blocks at 1KB granularity β and degraded the boot). This is itself a verification artifact: the check hooks every chain load. - fix v2 (fix.diff in this pack) enforces exactly the io.c invariant:
radix 0-with-offset / radix > 16 /
lbase < 64KB/ window-crossing rejected inhammer2_chain_load_data(setschain->error = HAMMER2_ERROR_CHECK, cleanly failing reads viastrategy.c:350and writes viastrategy.c:809), the same check for OPTDATA modify (chain.c:1525+) beforehammer2_write_bpcan issue I/O, a guard ondedup_offinstallation, and a widened diagnostic condition inhammer2_io_alloc. Seefix_run_*.logfor the before/after.
Fix verification
fixedfix v2 (data_off geometry validation at every I/O issue point: radix 1..16, lbase>=64KB, block contained in one 64KB window) VALIDATED on guest: root fs boots with zero false rejections (legit radix-11/12/13 at 1KB granularity accepted), crafted mount fails cleanly EINVAL, crossed file reads fail cleanly EIO, no panic, no fault. v1 alignment-based predicate was too strict and rejected legal blocks (fix_v1_too_strict.txt).
fix_build.log (rc=0); fix_run.log (EINVAL/EIO clean rejections, root fs OK); fix_v1_too_strict.txt (why v1 wrong); fix.diff
Confirmed kernel references
- sys/vfs/hammer2/hammer2_chain.c:189-192
- sys/vfs/hammer2/hammer2_chain.c:938-939
- sys/vfs/hammer2/hammer2_chain.c:994-1000
- sys/vfs/hammer2/hammer2_chain.c:1048
- sys/vfs/hammer2/hammer2_chain.c:1100
- sys/vfs/hammer2/hammer2_chain.c:1504-1516
- sys/vfs/hammer2/hammer2_chain.c:1819-1827
- sys/vfs/hammer2/hammer2_io.c:113-126
- sys/vfs/hammer2/hammer2_io.c:555-566
- sys/vfs/hammer2/hammer2_strategy.c:487
- sys/vfs/hammer2/hammer2_strategy.c:1310-1356
- sys/vfs/hammer2/hammer2_disk.h:458-461
- sys/kern/vfs_bio.c:638
Detail
Exploit chain
crafted hammer2 image (root or vfs.usermount+owned device) -> mount -> chain load of crossed bref -> [INVARIANTS: panic; release: OOB] -> read(2) discloses bytes past the 64KB DIO buffer; write(2) via CHECK_NONE in-place path (chain.c:1504-1516) + hammer2_write_bp (strategy.c:1356) plants chosen content 0x300-0xFF00 bytes past the buffer end into the adjacent kernel buffer object. Demonstrated mechanically + forensically (in-window half on media); deterministic victim-object placement (->uid0) not achieved.
Evidence (decisive lines)
["panic_A.txt: mount-time panic, 'Illegal: 0000000001800000 000000000180fd00+00000400' + KKASSERT panic at hammer2_io.c:126 (stock kernel, valid radix 10)", 'panic_B.txt: same panic loading the crafted FILE DATA chain (mount OK, cat /mnt/h2/f1)', 'fault_B_noinv.txt: no-INVARIANTS kernel #1, Fatal trap 12 in memmove+0x28, fault VA 0xfffff8006be66000 = bp->b_data+0x10000 (OOB bcopy source off the 64KB buffer)', 'run_E_noinv.log: groomed run on kernel #1 - crossed read survives (ILLEGAL_AFTER_READ=1), full write sequence, clean umount', 'forensic_E.txt + h2_E_flushed.img: attacker pattern DF2616OOBWRITE recovered at media 0x200ff05 (write went through chain->data = bdata+0xFF00)', 'fix_v1_too_strict.txt: legitimate radix-11/12/13 brefs at 1KB granularity that the over-strict v1 alignment check rejected (proof of the real invariant)', 'fix_run.log: fixed kernel (#1 Aug 28 13:14, no-INVARIANTS + fix v2): variant A mount -> EINVAL clean; E10/E16 crossed reads -> EIO clean; root fs zero false rejections; guest stays up', 'build_noinv.log / build_fix.log: full kernel build logs (rc=0)']
PoC changes
Seed sketch replaced entirely: wrote a python3 image forger that walks volhdr->sroot->PFS-dir (incl. INDIRECT blockset arrays, BREF_TYPE_INDIRECT=2) to the file DATA brefs, sets methods=0x00 (CHECK_NONE, testcheck returns 1 - avoids all CRC recomputation) + modify_tid (enables overwrite-in-place), crosses data_off to lbase|0xFF00 with the ORIGINAL VALID radix, and recomputes only the three volhdr CRC32Cs (DF-0763's proven technique). Added grooming: 32 marker files each relocated (valid geometry) into their own 64KB window so their DIO buffers occupy adjacent KVA slots. Learned en route: hammer2 compresses small files (markers must be incompressible), radix-16 allocations burn ~2MB freemap each on small volumes, and buffer KVA slots are preassigned per header (vfs_bio.c:638).
Verified recommended fix
Validate data_off geometry (radix<=16 and nonzero, lbase>=64KB, block within one 64KB window - exactly the io.c KKASSERT invariant) in hammer2_chain_load_data, the OPTDATA path of hammer2_chain_modify, and dedup_off installation; fix.diff validated on-guest.
Verdict
REPRODUCED end-to-end. (1) Stock X86_64_GENERIC (INVARIANTS ON): a crafted hammer2 image whose sroot_blockset[0].data_off = 0x180fd0a (radix 10 = PERFECTLY VALID, lbase 0x180fd00 misaligned and crossing the 64KB DIO window) panics the kernel at mount time at hammer2_io.c:126 (panic_A.txt); the file-data variant (data_off=0x1c0ff0a) panics on the first read of the file (panic_B.txt) - deterministic local DoS from a mounted image. (2) A rebuilt kernel without INVARIANTS (release-style, build_noinv.log) removes the KKASSERT: the unconditional 'Illegal:' kprintf at hammer2_io.c:122-125 fires and execution continues, giving chain->data = bp->b_data + 0xFF00 with chain->bytes = 0x400: the OOB bcopy either page-faults in kernel mode (fault_B_noinv.txt: Fatal trap 12 at memmove+0x28, fault VA = bp->b_data+0x10000, DDB stop, ssh dead - DoS) or, with 32 marker files groomed one-per-64KB-window so the adjacent buffer KVA slot is mapped, read(2) returns out-of-bounds kernel-buffer bytes to userspace and write(2) plants attacker content through the crossed pointer (run_E_noinv.log + forensic_E.txt: pattern recovered from the flushed image at media offset 0x200ff05, exactly where chain->data pointed). uid=0 was NOT achieved: the adjacent-slot victim object could not be placed deterministically (fixed per-header KVA scheme, vfs_bio.c:638; the observed neighbor was a mapped but clean zero-filled buffer, so the past-end write half could not be recovered from media). One refinement to the finding discovered during verification: hammer2's freemap allocates at 1KB granularity, so radix-N blocks at merely-1KB-aligned offsets are LEGAL on real filesystems - the actually-missing invariant is exactly what io.c asserts (pbase!=0, radix<=16, no 64KB-window crossing); an alignment-based fix (v1) broke the root fs boot (fix_v1_too_strict.txt). Fix v2 (fix.diff) implements the correct predicate at every I/O issue point and was VALIDATED on the guest: root fs boots with zero false rejections, variant A mount fails cleanly (EINVAL), crossed file reads fail cleanly (EIO, 'illegal data_off geometry'), no panic, no fault (fix_run.log).
No comments yet.