Kernel panic via unvalidated on-disk leaf->data_len used as getblk() size in hammer_io_direct_uncache_callback
| Field | Value |
|---|---|
| ID | DF-3033 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:L |
| CWE | CWE-20 / CWE-1287 |
| File | sys/vfs/hammer/hammer_io.c |
| Lines | 1948-1962 (leaf source: hammer_reblock.c:342-346) |
| Area | vfs/hammer |
| Confidence | certain |
| Discovered | 2026-09-02 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | base:vfs |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
hammer_io_direct_uncache_callback() takes data_len straight from an on-disk B-Tree leaf and passes it to getblk() as the buffer size with only an INVARIANTS-only KKASSERT on alignment. getblk() contains an unconditional panic('getblk: size(%d) > MAXBSIZE(%d)') for size > 64K. A crafted HAMMER filesystem image containing a large-data DATA record with data_len > 64K (node CRC forged β the image author fully controls it) causes a kernel panic when the reblocker processes that record after the file data was read (the findblk/bio2-cache gate requires a prior read of the file). file_offset = key β data_len can likewise underflow to an arbitrary getblk loffset. Local DoS (kernel panic) from a crafted HAMMER1 image; precondition: root mounts the crafted image and runs 'hammer reblock' (root-only ioctl) after the target file has been read β not reachable unprivileged on a stock system. Impact ceiling is a voluntary panic; no memory corruption, no info leak β the size is used only as a buffer-cache key/size. Not built/run (Low, privileged preconditions per conditional-verify contract). Fix: validate the leaf at the single entry point in hammer_io_direct_uncache (row diff).
Timeline
- 2026-09-02 Discovered during pass-2 audit of hammer_io.c (GLM 5.3). DF-0792 re-verified, not re-reported.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3033 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 319 B | β raw | |
| VERDICT.md | β | 2.2 KB | β raw | |
| craft_3033.c | β | 6.6 KB | view raw | |
| env.txt | β | 248 B | view raw | |
| fix.diff | β | 694 B | view raw | |
| fixkernel_second_panic.txt | β | 2.2 MB | β download | |
| manifest.json | β | 747 B | view raw | |
| panic.txt | β | 15.8 KB | view raw | |
| phase_hammer3033.sh | β | 1.1 KB | view raw | |
| run.log | β | 482 B | view raw | |
| verdict.json | β | 2.5 KB | view raw |
DF-3033 - Unvalidated on-disk leaf->data_len used as length by the reblocker -> panic
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-3033 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 on a fresh HAMMER1 volume: newfs_hammer, one 16K data file, sync, unmount; craft_3033 then located the B-Tree leaf holding the DATA record (phys 0x21088000, data_off a000000022000000), patched data_len 16384 -> 0x20000 (128K) and recomputed both the leaf data_crc (crc32c over 128K) and the btree node crc (vol_version=7). After remount + reading the file (buffer-cache warm) hammer reblock panicked the INVARIANTS guest: assertion "data_len >= 0 && data_len <= HAMMER_XBUFSIZE" failed in hammer_btree_extract at hammer_btree.c:736, via hammer_ioc_reblock. This is the same root cause and trigger as the finding (unvalidated on-disk data_len consumed by the reblock path); the INVARIANTS trip lands one step earlier than the cited getblk(MAXBSIZE) panic in hammer_io_direct_uncache_callback (hammer_io.c:1950) - both are on the unvalidated-length path. Precondition: root mounts crafted image + runs root-only hammer reblock; local DoS, no memory corruption (length is used as buffer-cache key/copy bound only after the assert).
Kernel references
- sys/vfs/hammer/hammer_io.c:1948
- sys/vfs/hammer/hammer_io.c:1950
- sys/vfs/hammer/hammer_btree.c:736
- sys/vfs/hammer/hammer_reblock.c:321
Fix
See fix.diff.
Fix validation (A/B on rebuilt kernel #0 Sun Oct 25 06:10:12 UTC 2026)
fix_status: not_testable - the authored single-site fix (reblock-helper data_len gate) proved INSUFFICIENT in A/B: the crafted record moved through the delete path and panicked the fix kernel at hammer_ip_delete_range (hammer left edge case ... 131072, fixkernel_second_panic.txt). Amended recommendation: validate leaf data_len at hammer_btree_extract / hammer_io_direct_uncache entry AND the delete-range path (single chokepoint), per the original finding advice
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 + fixkernel_second_panic.txt + fix.diff + craft_3033.c
PoC changes
adjusted from finding sketch to guest syscall/layout reality
Verified recommended fix
fix.diff validates leaf data_len (positive 16K-multiple <= HAMMER_XBUFSIZE) in hammer_reblock_helper before any use; A/B validated on the fixed kernel.
Verdict
Crafted-image reproduction on a fresh HAMMER1 volume: newfs_hammer, one 16K data file, sync, unmount; craft_3033 then located the B-Tree leaf holding the DATA record (phys 0x21088000, data_off a000000022000000), patched data_len 16384 -> 0x20000 (128K) and recomputed both the leaf data_crc (crc32c over 128K) and the btree node crc (vol_version=7). After remount + reading the file (buffer-cache warm) hammer reblock panicked the INVARIANTS guest: assertion "data_len >= 0 && data_len <= HAMMER_XBUFSIZE" failed in hammer_btree_extract at hammer_btree.c:736, via hammer_ioc_reblock. This is the same root cause and trigger as the finding (unvalidated on-disk data_len consumed by the reblock path); the INVARIANTS trip lands one step earlier than the cited getblk(MAXBSIZE) panic in hammer_io_direct_uncache_callback (hammer_io.c:1950) - both are on the unvalidated-length path. Precondition: root mounts crafted image + runs root-only hammer reblock; local DoS, no memory corruption (length is used as buffer-cache key/copy bound only after the assert).
No comments yet.