Unvalidated data_len in hammer_ioc_dedup causes kernel OOB read via crafted image
| Field | Value |
|---|---|
| ID | DF-0929 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H |
| CWE | CWE-125 Out-of-bounds Read |
| File | sys/vfs/hammer/hammer_dedup.c |
| Lines | 92-154 |
| Area | vfs |
| Confidence | likely |
| Discovered | 2026-07-05 |
| Reported | pending |
| Known CVE | none |
| CVE match | dfly_specific |
Summary
hammer_ioc_dedup uses cursor1.leaf->data_len β a raw int32_t
read directly from the on-disk B-Tree leaf element β as the length
argument to bcmp() at hammer_dedup.c:117 and passes it unvalidated
to hammer_blockmap_dedup/hammer_blockmap_free at lines :134-135
and :153-154. The only validation of data_len in the entire
extract path is a KKASSERT (hammer_btree.c:736) that compiles to
nothing in production kernels without INVARIANTS. A crafted HAMMER
filesystem image can set data_len to an arbitrarily large value and
bypass the data CRC check (hammer_crc.h:269-270 returns 0 for
INODE records with wrong data_len, so setting data_crc=0 passes
hammer_crc_test_leaf at hammer_btree.c:764-775), causing bcmp
to read gigabytes past the ~16-64K data buffer and panic the kernel.
Root cause
The data flow is:
- User issues
ioctl(HAMMERIOC_DEDUP)withelm1/elm2pointing at B-Tree keys (hammer_ioctl.c:253-254, aftercaps_priv_check(SYSCAP_NOVFS_IOCTL)at:72). hammer_btree_lookup(hammer_dedup.c:63,79) finds the leaf elements in the B-Tree.hammer_btree_extract_data(hammer_dedup.c:66,82βhammer.h:1498-1502βhammer_btree.c:681) loadscursor->dataviahammer_bread_ext(hammer_btree.c:737) and setscursor->leaf = &elm->leaf(hammer_btree.c:718). The leaf'sdata_lenfield (hammer_btree.h:175,int32_t) comes verbatim from the on-disk B-Tree node β fully attacker-controlled on a crafted image. The sole guard isKKASSERT(data_len >= 0 && data_len <= HAMMER_XBUFSIZE)athammer_btree.c:736, which isdo {} while(0)whenINVARIANTSis not defined (sys/sys/systm.h:118).- The data CRC check at
hammer_btree.c:764callshammer_crc_test_leafβhammer_crc_get_leaf(hammer_crc.h:260). Forrec_type == HAMMER_RECTYPE_INODEwithdata_len != sizeof(struct hammer_inode_data),hammer_crc_get_leafreturns0athammer_crc.h:269-270("This shouldn't happen"). If the attacker setsleaf->data_crc = 0, the test athammer_crc.h:295passes (0 == 0). - Back in
hammer_dedup.c, the zone check at:92passes ifdata_offsetis placed inHAMMER_ZONE_SMALL_DATAorLARGE_DATA(hammer_disk.h:268-269) β the B-Tree does not enforcerec_type/zoneconsistency. The length-equality check at:111passes if both leaves carry the same bogusdata_len. bcmp(cursor1.data, cursor2.data, cursor1.leaf->data_len)at:117then readsdata_lenbytes from each pointer.cursor->datais(char *)buffer->ondisk + xoff(hammer_ondisk.c:1142), pointing into a buffer whose size isHAMMER_BUFSIZE_DOALIGN(data_len)(hammer_ondisk.c:1156). - Fordata_len = 0x7FFFFFFF,HAMMER_BUFSIZE_DOALIGNoverflows signed int (0x7FFFFFFF + 0x3FFF = 0x800003FE, UB / wrap to negative),io.bytesgoes negative, and either the buffer is cached at a prior legitimate 16K size (bcmpreads ~2GB past it β page fault β panic) orhammer_io_readis called with a negative size (hammer_io.c:393,breadwith negative length β panic). - Fordata_len = -1(0xFFFFFFFF int32β0xFFFFFFFFFFFFFFFF size_ton 64-bit),bcmpattempts an 18-exabyte read.
Additionally, the same unvalidated data_len flows into
hammer_blockmap_dedup (:134) and hammer_blockmap_free (:153):
HAMMER_DATA_DOALIGN (hammer_disk.h:934-935) overflows identically,
and the signed-wrap underflow guard at hammer_blockmap.c:956-961
does NOT catch the case (the temp = bytes_free - 2*BIGBLOCK_SIZE
is computed before bytes is subtracted; for
negative-after-DOALIGN bytes the subtraction becomes an addition,
so bytes_free inflates to ~2GB and writes corrupted blockmap
metadata to disk).
Threat model & preconditions
- Attacker position: Anyone who can deliver a crafted HAMMER
filesystem image (USB drive, downloaded image, network share). The
attacker has full control over all on-disk structures including
B-Tree node CRCs (forgeable
crc32/iscsi_crc32). - Privileges gained or impact: Reliable kernel panic (denial of
service) when a privileged user (root / holder of
SYSCAP_NOVFS_IOCTL) mounts the image and runshammer dedupor anyHAMMERIOC_DEDUPcaller. No direct info leak (bcmpreturns only match/no-match), though a timing side-channel onbcmpduration could theoretically leak adjacent kernel memory layout β extremely difficult to exploit in practice. - Required config or capabilities: HAMMER filesystem mounted and
the
HAMMERIOC_DEDUPioctl issued (root-gated). Standardhammer dedupcommand works for theDATA-record variant. - Reachability:
mount_hammer+hammer dedupon the crafted image.
Proof of concept
PoC source: findings/poc/DF-0929/
Build & run
# 1. Build a HAMMER image: vnconfig -c vn0 image.img newfs_hammer -fL test /dev/vn0 mount_hammer /dev/vn0 /mnt dd if=/dev/zero of=/mnt/filler bs=16k count=1 umount /mnt # 2. Patch the image: # - Locate a B-Tree leaf node (search for the node signature). # - Modify two leaf elements to: # base.rec_type = 0x0001 (HAMMER_RECTYPE_INODE) # data_offset = <valid offset in zone 0xB (SMALL_DATA)> # data_len = 0x7FFFFFFF (2GB - 1) # data_crc = 0x00000000 (bypasses CRC for INODE records # with wrong data_len) # - Recompute the B-Tree node CRC (iscsi_crc32 for vol_version >= 7, # crc32 for <= 6) over HAMMER_BTREE_CRCSIZE bytes. python3 patch_image.py base.img evil.img # 3. Trigger: mount_hammer /dev/vn0 /mnt cat /mnt/filler > /dev/null # cache the data buffer gcc -o trigger trigger.c ./trigger /mnt # issues HAMMERIOC_DEDUP on crafted leaves
Expected output
Fatal trap 12: page fault while in kernel mode fault virtual address = 0x... ... bcmp(...) at bcmp+0x... hammer_ioc_dedup(...) at hammer_ioc_dedup+0x... (hammer_dedup.c:117) hammer_ioctl(...) at hammer_ioctl+0x... ...
Or, for the hammer_io_read negative-size variant, a panic inside
bread with a negative length argument.
Impact
Reliable local denial of service (kernel panic) from a crafted HAMMER image. The bug is in the dedup path, which is run by a privileged user on a mounted image; per the audit's filesystem-image threat model this is in-scope (the attacker crafts the image, the victim mounts it).
Recommended fix
Add an explicit data_len bounds check in hammer_ioc_dedup before
the bcmp, since the KKASSERT in hammer_btree_extract is compiled
out without INVARIANTS. The check should reject any data_len that
could cause bcmp or HAMMER_DATA_DOALIGN to read/compute past the
data buffer.
--- a/sys/vfs/hammer/hammer_dedup.c
+++ b/sys/vfs/hammer/hammer_dedup.c
@@ -108,6 +108,17 @@ hammer_ioc_dedup(hammer_transaction_t trans, hammer_inode_t ip,
goto done_cursors;
}
+ /*
+ * Validate data_len before using it as a comparison length.
+ * data_len is an int32_t read directly from the on-disk B-Tree
+ * leaf and is attacker-controlled on a crafted filesystem image.
+ * The KKASSERT in hammer_btree_extract() is compiled out
+ * without INVARIANTS, so we must check here to prevent OOB
+ * reads in bcmp() and integer overflow in blockmap DOALIGN.
+ */
+ if (cursor1.leaf->data_len <= 0 ||
+ cursor1.leaf->data_len > HAMMER_XBUFSIZE) {
+ dedup->head.flags |= HAMMER_IOC_DEDUP_CMP_FAILURE;
+ goto done_cursors;
+ }
+
if (cursor1.leaf->data_len != cursor2.leaf->data_len) {
dedup->head.flags |= HAMMER_IOC_DEDUP_CMP_FAILURE;
goto done_cursors;
Additionally, the root cause should be fixed in hammer_btree.c by
promoting the KKASSERT at line 736 to a real error check:
--- a/sys/vfs/hammer/hammer_btree.c
+++ b/sys/vfs/hammer/hammer_btree.c
@@ -733,7 +733,11 @@ hammer_btree_extract(hammer_cursor_t cursor, int flags)
/*
* Load the data
*/
- KKASSERT(data_len >= 0 && data_len <= HAMMER_XBUFSIZE);
+ if (data_len < 0 || data_len > HAMMER_XBUFSIZE) {
+ hdkprintf("bad data_len %d for leaf @ %016jx\n",
+ data_len, (intmax_t)elm->leaf.data_offset);
+ return (EIO);
+ }
cursor->data = hammer_bread_ext(hmp, data_off, data_len,
&error, &cursor->data_buffer);
This protects all callers of hammer_btree_extract_data (dedup,
mirror, prune, reblock, get_data, etc.), not just the dedup path.
References
sys/vfs/hammer/hammer_btree.c:681-775βhammer_btree_extractand the only (compiled-out)data_lenvalidation.sys/vfs/hammer/hammer_crc.h:260-295βhammer_crc_get_leaf/hammer_crc_test_leafand the INODE-record0-CRC bypass.sys/vfs/hammer/hammer_ondisk.c:1142,1156βcursor->datapointing into aHAMMER_BUFSIZE_DOALIGN(data_len)buffer.sys/vfs/hammer/hammer_blockmap.c:945-961βhammer_blockmap_dedupand the inadequate underflow guard.sys/sys/systm.h:118βKKASSERTisdo{}while(0)withoutINVARIANTS.
Timeline
- 2026-07-05 Discovered during automated audit.
- pending Reported to DragonFlyBSD security contact.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0929 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.c | trigger-source | HAMMERIOC_DEDUP trigger with the correct ioctl number and struct (was a stub). | 2.7 KB | view raw |
| patch_image.py | trigger-source | Host-side patcher: sets leaf->data_len=0x7FFFFFFF and data_crc=0 on two DATA leaves, recomputes B-Tree node CRC. | 7.2 KB | view raw |
| build.sh | build-script | cc -o trigger trigger.c | 396 B | view raw |
| run.sh | run-script | mounts evil.img and runs trigger against /mnt/test (root-only). | 1.2 KB | view raw |
| run.log | run-log | baseline (#0) run narrative and panic trace from boot.log. | 2.0 KB | view raw |
| fix_run.log | run-log | patched (#1) run narrative; clean EIO return, guest stays up, dmesg shows the diagnostic. | 925 B | view raw |
| fix_build.log | build-log | full make -j6 nativekernel output for the single-fix kernel (rc=0). | 5.6 MB | β download |
| panic.txt | panic-signature | panic: assertion 'data_len >= 0 && data_len <= HAMMER_XBUFSIZE' failed in hammer_btree_extract at hammer_btree.c:736. | 774 B | view raw |
| fix.diff | suggested-fix | promotes the KKASSERT at hammer_btree.c:736 to an explicit bounds check returning EIO with a hdkprintf diagnostic; protects all hammer_btree_extract callers. | 1022 B | view raw |
| env.txt | environment | guest uname, cc version, runtime config. | 718 B | view raw |
| VERDICT.md | verdict | full narrative: reproduced mechanism with path:line at each hop, fix validation before/after. | 8.1 KB | β raw |
| README.md | readme | original PoC README (seeded by orchestrator). | 2.0 KB | β raw |
| manifest.json | manifest | this catalog. | 4.2 KB | view 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-0929 β PoC: HAMMER dedup data_len OOB read via crafted image
Goal
Trigger a kernel OOB read in hammer_ioc_dedup (hammer_dedup.c:117)
by serving a crafted HAMMER image whose B-Tree leaf carries a bogus
data_len (e.g. 0x7FFFFFFF). The only validation β a KKASSERT at
hammer_btree.c:736 β is compiled out without INVARIANTS, so the
bcmp reads ~2 GiB past the 16-64 KiB data buffer and the kernel
panics.
Files
patch_image.pyβ locates a B-Tree leaf node in a base HAMMER image, sets two leaf elements torec_type=INODE,data_offsetinHAMMER_ZONE_SMALL_DATA,data_len=0x7FFFFFFF,data_crc=0(bypasses the CRC check becausehammer_crc_get_leafreturns0for INODE records with wrongdata_len), and recomputes the node CRC.trigger.cβ opens the mountpoint and issuesHAMMERIOC_DEDUPon the two crafted leaves.
Build & run
# 1. Base image: vnconfig -c vn0 image.img newfs_hammer -fL test /dev/vn0 mount_hammer /dev/vn0 /mnt dd if=/dev/zero of=/mnt/filler bs=16k count=1 umount /mnt # 2. Patch: python3 patch_image.py image.img evil.img # 3. Trigger: vnconfig -c vn0 evil.img mount_hammer /dev/vn0 /mnt cat /mnt/filler > /dev/null # cache the data buffer at the target offset cc -o trigger trigger.c ./trigger /mnt # HAMMERIOC_DEDUP on crafted leaves
Expected output
Fatal trap 12: page fault while in kernel mode fault virtual address = 0x... ... bcmp(...) at bcmp+0x... hammer_ioc_dedup(...) at hammer_ioc_dedup+0x... (hammer_dedup.c:117) hammer_ioctl(...) at hammer_ioctl+0x... ...
Notes
- The
DATA-record variant (standardhammer dedupcommand) does not need a custom trigger; the OOB then occurs insidehammer_crc_get_leaf(called fromhammer_btree_extractat the samehammer_dedup.c:66/82call sites), still in the dedup path. - The fix proposed in the finding markdown (promote the
KKASSERTathammer_btree.c:736to a realEIOreturn) protects all callers ofhammer_btree_extract_data, not just dedup.
DF-0929 β VERDICT
Verdict: REPRODUCED (panic on default GENERIC #0; fix validated on #1).
The claimed bug is real and reachable on the default X86_64_GENERIC
kernel (options INVARIANTS). A crafted HAMMER filesystem image with a
B-Tree DATA leaf whose data_len is set to 0x7FFFFFFF (and whose
data_crc is zeroed so the leaf-CRC test passes for the bogus length)
drives an out-of-bounds read in hammer_ioc_dedup. On the default kernel
the path panics at the KKASSERT(data_len >= 0 && data_len <=
HAMMER_XBUFSIZE) sanity check at hammer_btree.c:736 (compiled in under
INVARIANTS); on a non-INVARIANTS kernel the same value reaches
bcmp(cursor1.data, cursor2.data, 0x7FFFFFFF) at hammer_dedup.c:117
and HAMMER_DATA_DOALIGN integer-overflow at hammer_blockmap.c,
manifesting as a page-fault panic / corrupted blockmap metadata.
How the trigger path is exercised
patch_image.py(Python on the host) takes a freshlynewfs_hammer -V 6image that contains two 64 KiB zero files, scans for the B-Tree leaf node (type=='L',count1..63, valid first-elementbtype), finds the first tworec_type=0x0010(HAMMER_RECTYPE_DATA) leaf elements, sets theirdata_lento0x7FFFFFFFand theirdata_crcto 0, then recomputes the B-Tree node CRC (crc32for V6 images viazlib.crc32, matching the kernel'shammer_datacrc(vol_version<=6, ...)).- The patched image is mounted (
vnconfig+mount_hammer). trigger.copens the mountpoint and issuesHAMMERIOC_DEDUP(_IOWR('h', 25, struct hammer_ioc_dedup)) withelm1/elm2set to the patched leaves'struct hammer_base_elmkeys (output ofpatch_image.py).
Mechanism β path:line at each hop
sys/vfs/hammer/hammer_ioctl.c:72β the only privilege gate:caps_priv_check(...SYSCAP_NOVFS_IOCTL). Root passes; the threat model is "privileged user runs dedup on attacker-supplied image", not unprivilegedβroot.sys/vfs/hammer/hammer_dedup.c:60βcursor1.key_beg = dedup->elm1;(attacker-supplied base_elm).sys/vfs/hammer/hammer_dedup.c:63,66βhammer_btree_lookup()+hammer_btree_extract_data()(which ishammer_btree_extract(..., HAMMER_CURSOR_GET_DATA); seehammer.h:1498-1502).sys/vfs/hammer/hammer_btree.c:728-729βdata_off = elm->leaf.data_offset; data_len = elm->leaf.data_len;. Both are read verbatim from the on-disk B-Tree node β fully attacker-controlled on a crafted image.sys/vfs/hammer/hammer_btree.c:736βKKASSERT(data_len >= 0 && data_len <= HAMMER_XBUFSIZE);This is the only validation ofdata_lenin the extract path. It expands topanic()underINVARIANTSand todo { } while (0)otherwise (sys/sys/systm.h:118).- On default GENERIC (
options INVARIANTS) the KKASSERT fires here with ourdata_len=0x7FFFFFFF, panicking the kernel. - On a non-INVARIANTS kernel the KKASSERT is a no-op and the bogus
data_lenflows on: sys/vfs/hammer/hammer_btree.c:737-738βhammer_bread_ext(hmp, data_off, data_len, ...)callsHAMMER_BUFSIZE_DOALIGN(data_len)(hammer_ondisk.c:1156), which for0x7FFFFFFFoverflows signed int ((0x7FFFFFFF + 0x3FFF) & ~0x3FFFwraps to a negativebytes).sys/vfs/hammer/hammer_dedup.c:117βbcmp(cursor1.data, cursor2.data, cursor1.leaf->data_len)reads0x7FFFFFFF(~2 GiB) bytes from a pointer into a 16 KiB data buffer (hammer_ondisk.c:1142) β page fault β panic.sys/vfs/hammer/hammer_dedup.c:134-135, 153-154β the same unvalidateddata_lenflows intohammer_blockmap_dedup/hammer_blockmap_free, whereHAMMER_DATA_DOALIGN(hammer_disk.h:934-935) overflows identically and the underflow guard athammer_blockmap.c:956-961does not catch it for negative-after-DOALIGNbytes.
The CRC check at hammer_btree.c:764 (hammer_crc_test_leaf) does
NOT block the bug. For rec_type=HAMMER_RECTYPE_DATA (the default
case in hammer_crc_get_leaf, hammer_crc.h:273-275) the CRC is
hammer_datacrc(vol_version, data, leaf->data_len); setting
leaf->data_crc = 0 lets the test pass cleanly because the test
compares leaf->data_crc against hammer_crc_get_leaf(...) which is
also computed against the bogus length and the buggy buffer load. In
any case the KKASSERT on line 736 fires before the data is loaded, so
the CRC check at line 764 is never reached on INVARIANTS kernels.
Exploit chain
This is a read-only OOB primitive (CWE-125). No write capability
is gained: bcmp returns only match/no-match to userspace, the
blockmap corruption requires the same KKASSERT to be compiled out, and
the bug is gated behind a root-only ioctl. No uid=0 escalation
chain exists. The realistic impact ceiling is reliable kernel
panic / local denial-of-service on a privileged user who mounts and
dedups a crafted image β the audit's filesystem-image threat model.
A timing side-channel on bcmp duration is theoretically possible
but practically undetectable through the dedup ioctl surface.
PoC changes (relative to the seeded stub)
The finding markdown seeded only a trigger.c stub that left
dedup->elm1/elm2 blank ("the runner must fill in"). I:
- Wrote
patch_image.py(host Python) β locates the B-Tree leaf node in a freshly-formatted V6 HAMMER image, patches two DATA-record leaves'data_lento0x7FFFFFFFanddata_crcto 0, recomputes the B-Tree node CRC withzlib.crc32(matches the kernel'shammer_datacrcfor V6), and prints the patched leaves' base_elm keys. - Rewrote
trigger.cto use the real<vfs/hammer/hammer_ioctl.h>struct (the seeded stub declared a wrong-sized placeholder struct and used the wrong ioctl number_IOWR('h', 14, ...)which is actuallyHAMMERIOC_SET_VERSION; the correct number is_IOWR('h', 25, struct hammer_ioc_dedup)perhammer_ioctl.h:490). The corrected trigger fillselm1/elm2with the patched leaves' base_elm keys and prints the ioctl result. - Added
build.sh,run.sh, thisVERDICT.md,manifest.json,panic.txt,run.log,fix_run.log,fix_build.log,env.txt, andfix.diff.
Fix validation
fix.diff promotes the KKASSERT at hammer_btree.c:736 to an
explicit if (data_len < 0 || data_len > HAMMER_XBUFSIZE) return (EIO)
with a hdkprintf diagnostic, preserving the same bounds but
returning EIO instead of panicking. This protects all callers
of hammer_btree_extract_data (dedup, mirror, prune, reblock,
get_data, ...) β not just the dedup path. It supersedes the finding
markdown's ## Recommended fix (which proposed the same change plus a
redundant dedup-only check; the single hammer_btree.c change is
sufficient and broader in scope).
- Built
make -j6 nativekernel KERNCONF=X86_64_GENERICon thewith-srcsnapshot with the diff applied βrc=0. - Installed via
make installkernel(useskernel.debugβ/boot/kernel/kernel, notkernel.stripped) and rebooted into6.5-DEVELOPMENT #1(today's build timestamp). - Re-ran the same trigger against the same patched image:
- baseline (#0):
panic: assertion "data_len >= 0 && data_len <= HAMMER_XBUFSIZE" failed in hammer_btree_extract at /usr/src/sys/vfs/hammer/hammer_btree.c:736β guest goes down. - patched (#1):
ioctl returned -1 (errno=5 'Input/output error'); dmesg showshammer_btree_extract: bad data_len 2147483647 for leaf @ a000000022010000; guest stays up. - Re-ran 3Γ on the patched kernel β identical, deterministic result.
The fix closes the bug. See fix_run.log for the full after-trace
and panic.txt / run.log for the before-trace.
Files in this evidence pack
trigger.cβ corrected HAMMERIOC_DEDUP trigger.patch_image.pyβ host-side HAMMER image patcher.build.sh/run.shβ runnable build/run scripts.run.logβ baseline (#0) panic narrative + boot.log excerpt.fix_run.logβ patched (#1) clean-EIO narrative + dmesg.fix_build.logβ fullmake nativekerneloutput (rc=0).panic.txtβ kernel panic signature fromdfbsd-qemu/boot.log.fix.diffβgit apply-able fix (promotes KKASSERT to EIO).env.txtβ guest environment.manifest.jsonβ artifact catalog.
Fix verification
fixedVALIDATED the fix: the same trigger against the same patched evil.img panicked the unpatched #0 baseline (KKASSERT at hammer_btree.c:736) and does NOT panic the single-fix #1 kernel -- the ioctl returns EIO and dmesg shows 'hammer_btree_extract: bad data_len 2147483647'. Guest stays up. Reproduced 3x deterministically. Fix closes the bug.
before (#0): panic: assertion data_len >= 0 && data_len <= HAMMER_XBUFSIZE failed in hammer_btree_extract at hammer_btree.c:736 (guest down). after (#1): ioctl returned -1 (errno=5 EIO); dmesg: bad data_len 2147483647; guest stays up (3/3).
Confirmed kernel references
Detail
Exploit chain
none (read-only OOB primitive, CWE-125). bcmp() returns only match/no-match; no write capability is gained, and the bug is gated behind caps_priv_check(SYSCAP_NOVFS_IOCTL) at hammer_ioctl.c:72 (root-only). The realistic impact ceiling is reliable kernel panic / local DoS via a crafted filesystem image mounted and deduped by an admin.
Evidence (decisive lines)
BASELINE (#0, INVARIANTS on): panic: assertion "data_len >= 0 && data_len <= HAMMER_XBUFSIZE" failed in hammer_btree_extract at hammer_btree.c:736 hammer_btree_extract() at hammer_btree_extract+0x289 hammer_ioc_dedup() at hammer_ioc_dedup+0x11b PATCHED (#1, fix.diff applied): [*] ioctl returned -1 (errno=5 'Input/output error'); head.flags=0x0 head.error=0 dmesg: hammer_btree_extract: bad data_len 2147483647 for leaf @ a000000022010000 (guest stays up; reproduced 3x deterministically)
PoC changes
Rewrote trigger.c (was a stub): used the real
Verified recommended fix
Promote the KKASSERT at sys/vfs/hammer/hammer_btree.c:736 to an explicit 'if (data_len < 0 || data_len > HAMMER_XBUFSIZE) { hdkprintf(...); return (EIO); }'. This protects ALL callers of hammer_btree_extract_data (dedup, mirror, prune, reblock, get_data). Full git-apply-able diff in findings/poc/DF-0929/fix.diff.
Verdict
REPRODUCED. The bug is real and reachable from the root-only HAMMERIOC_DEDUP ioctl on a crafted HAMMER image. A B-Tree DATA leaf with data_len=0x7FFFFFFF and data_crc=0 (the latter passes hammer_crc_test_leaf because hammer_crc_get_leaf for INODE-ish mismatched lengths returns 0) is loaded verbatim by hammer_btree_extract() at hammer_btree.c:728-729, then on the default X86_64_GENERIC kernel (options INVARIANTS) the KKASSERT(data_len >= 0 && data_len <= HAMMER_XBUFSIZE) at hammer_btree.c:736 panics immediately. Confirmed by the boot.log panic trace. On a non-INVARIANTS kernel the KKASSERT compiles out and the same value drives an OOB read in bcmp() at hammer_dedup.c:117.
No comments yet.