Missing bounds validation in hammer_format_volume_header allows signed-int64 overflow in vol_buf_beg/end
Summary
hammer_volume.c:652-662 vol_alloc=root_ondisk->vol_bot_beg then += ioc->boot_area_size += ioc->memory_log_size both attacker int64 unchecked. :666 vol_buf_end=ioc->vol_size unchecked. :668 only check HAMMER_VOL_BUF_SIZE<0. boot_area_size=-1 makes vol_buf_beg small/negative vol_buf_size huge positive passes check. KKASSERT vol_buf_size mask compiled out non-INVARIANTS. hammer_format_freemap loops enormous range = kernel livelock I/O storm DoS. Requires root HAMMERIOC_ADD_VOLUME.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0869 Β· 18 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.c | trigger-source | HAMMERIOC_ADD_VOLUME with boot_area_size=-1, vol_size=2^49 | 4.1 KB | view raw |
| run.sh | run-script | sets up base HAMMER fs, vnconfig's the new vol, fires trigger as root | 2.3 KB | view raw |
| build.sh | build-script | cc -o trigger trigger.c | 192 B | view raw |
| README.md | readme | PoC overview, build/run, expected output, threat model | 2.0 KB | β raw |
| VERDICT.md | verdict | full narrative: mechanism, panic signature, fix validation | 9.0 KB | β raw |
| panic.txt | panic-signature | fatal KKASSERT 'error == 0' in hammer_ioc_volume_add at hammer_volume.c:118 + mechanism explanation | 1.9 KB | view raw |
| boot.log.unpatched_panic | boot-log | full boot.log snapshot of the unpatched-kernel panic | 14.1 KB | β download |
| boot_panic_snapshot.txt | panic-signature | grep excerpt of the panic block from boot.log | 741 B | view raw |
| run.log | run-log | decisive unpatched run output (truncated by panic) | 42 B | view raw |
| fix.diff | suggested-fix | two-layer bounds validation in hammer_format_volume_header | 1.7 KB | view raw |
| fix_build.log | build-log | full single-fix kernel build output (rc=0) | 5.6 MB | β download |
| fix_run.log | run-log | patched-kernel PoC run: returns errno=79 EFTYPE, guest up | 401 B | view raw |
| fix_run.2.log | run-log | patched-kernel PoC run #2: same clean result | 401 B | view raw |
| fix_run.3.log | run-log | patched-kernel PoC run #3: same clean result | 401 B | view raw |
| dmesg.patched.txt | dmesg | patched-kernel dmesg: 'HAMMER(df0869) volume 1 has non-positive geometry' | 511 B | view raw |
| env.txt | environment | uname, kern.version, cc version, kernel config | 348 B | 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-0869 β PoC: missing bounds validation in hammer_format_volume_header
Goal
Trigger the missing-bounds-validation defect in
hammer_format_volume_header (sys/vfs/hammer/hammer_volume.c:617-671)
by issuing HAMMERIOC_ADD_VOLUME with attacker-controlled int64 fields
(boot_area_size, memory_log_size, vol_size) that produce an
out-of-range vol_buf_size. The only validation in the formatter is a
signed-<0 check, which a huge-positive vol_buf_size happily passes;
the resulting KKASSERT in hammer_format_freemap
(hammer_volume.c:407) then panics the default GENERIC kernel.
Files
trigger.cβ opens a HAMMER mountpoint and issuesHAMMERIOC_ADD_VOLUMEwithboot_area_size = -1,memory_log_size = 0,vol_size = 2^49(~512 TiB).run.shβ sets up a base HAMMER filesystem (newfs_hammer), mounts it, creates the second-volume file, then runs the trigger. Must run as root.build.shβ compilestrigger.c.
Build & run
# build (as unprivileged user, from this dir) ./build.sh # run (as root, on the guest) ./run.sh
Expected output (unpatched #0 kernel, INVARIANTS on)
[+] mounted base HAMMER fs at /mnt/df0869 [+] new-volume file: /root/df0869/newvol.img (0 bytes) [*] issuing HAMMERIOC_ADD_VOLUME on '/root/df0869/newvol.img' [*] boot_area_size = -1 [*] memory_log_size = 0 [*] vol_size = 562949953421312 (~512 TiB, has bit 48 set)
β¦and the guest dies mid-syscall. The panic signature in
dfbsd-qemu/boot.log names hammer_format_freemap and the
KKASSERT is the (vol_buf_size & ~HAMMER_OFF_SHORT_MASK) == 0
assertion at hammer_volume.c:407.
Privilege / threat model
HAMMERIOC_ADD_VOLUME is gated behind caps_priv_check(cred,
SYSCAP_NOVFS_IOCTL) at hammer_ioctl.c:197 β root only. This is a
root-only kernel-panic DoS, not an unprivileged escalation. CVSS
matches the filed PR:H (the bug requires a HAMMER admin / a
compromised userspace HAMMER tool to issue the malicious ioctl).
DF-0869 β Verdict
Verdict: REPRODUCED (panic / local DoS via root-only ioctl) β fix VALIDATED
Summary
The finding is real. hammer_format_volume_header in
sys/vfs/hammer/hammer_volume.c:617-671 only validates the result of its
geometry computation with a single signed-<0 check on vol_buf_size
(line 664). The three attacker-controlled int64 inputs to that
computation β ioc->boot_area_size, ioc->memory_log_size, and
ioc->vol_size β are never sanity-checked, so a hand-crafted
HAMMERIOC_ADD_VOLUME ioctl produces an on-disk volume header whose
vol_buf_beg is un-sector-aligned (and/or whose vol_buf_size exceeds
the zone-2 addressable range). The kernel then panics in
hammer_format_freemap while servicing the same ioctl.
HAMMERIOC_ADD_VOLUME is gated behind
caps_priv_check(cred, SYSCAP_NOVFS_IOCTL) at
sys/vfs/hammer/hammer_ioctl.c:197 β i.e. root only. This is therefore
a root-only kernel-panic / local DoS, exactly matching the filed
severity (Low) and CVSS PR:H / A:L. It is not an unprivileged
escalation.
Mechanism (trigger β primitive β effect)
Trigger: HAMMERIOC_ADD_VOLUME issued against a mounted HAMMER
filesystem with:
- boot_area_size = -1
- memory_log_size = 0
- vol_size = 2^49 (~512 TiB)
- device_name = /dev/vn2 (a vnconfig'd 1 GiB sparse file)
Code path (cited to sys/):
hammer_ioctl.c:197β caps_priv_check passes (root).hammer_volume.c:103βhammer_format_volume_header(hmp, ioc, &ondisk, free_vol_no): -vol_alloc = root_ondisk->vol_bot_beg;// 0x40000 -vol_alloc += ioc->boot_area_size;// -1 -vol_alloc += ioc->memory_log_size;// 0 -ondisk->vol_buf_beg = vol_alloc;// 0x3FFFF = 262143 (NOT 512-aligned) -ondisk->vol_buf_end = ioc->vol_size & ~(int64_t)HAMMER_BUFMASK;// 0x2000000000000 - The only guard:if (HAMMER_VOL_BUF_SIZE(ondisk) < 0) return EFTYPE;vol_buf_size = 0x1FFFFFFFC0001is positive, so the guard passes and the bogus header is committed.hammer_volume.c:107βhammer_install_volume(hmp, ioc->device_name, NULL, &ondisk)succeeds (it accepts the bogus geometry without further checks);volume->maxbuf_offis set from the corruptvol_buf_end.hammer_volume.c:117βhammer_format_freemap(trans, volume)is called. Inside it (hammer_volume.c:406-407):c vol_buf_size = HAMMER_VOL_BUF_SIZE(ondisk); // 0x1FFFFFFFC0001 KKASSERT((vol_buf_size & ~HAMMER_OFF_SHORT_MASK) == 0);BecauseHAMMER_OFF_SHORT_MASK = 0x000FFFFFFFFFFFFFis 52 bits (not 48 as the finding markdown supposed),vol_buf_size β 2^49passes this KKASSERT. (My first trace assumed 48 bits and expected a panic here; the live kernel proved otherwise β see the simulator output referenced inpanic.txt.)hammer_format_freemap's first layer-2hammer_bread()translates the encoded zone-2 offset forvol_no=1into a byte offset on vn2 equal tovol_buf_beg = 262143. The vn driver rejects the I/O:dscheck(vn2): bio_offset 262143 is not on a sector boundary (ssize 512)hammer_breadreturnsEINVAL.hammer_volume.c:118βKKASSERT(error == 0);fires afterhammer_format_freemapreturns the error:panic: assertion "error == 0" failed in hammer_ioc_volume_add at /usr/src/sys/vfs/hammer/hammer_volume.c:118
Trace (from panic.txt, captured from dfbsd-qemu/boot.log):
hammer_ioc_volume_add() at hammer_ioc_volume_add+0x5c6
hammer_ioctl() at hammer_ioctl+0xf0a
hammer_vop_ioctl() at hammer_vop_ioctl+0x48
vop_ioctl() at vop_ioctl+0x63
vn_ioctl() at vn_ioctl+0xb1
Debugger("panic")
So the bug is real and the kernel panics on the default #0 GENERIC
kernel (INVARIANTS on). The exact KKASSERT that fires is error == 0
at line 118 (not the (vol_buf_size & ~HAMMER_OFF_SHORT_MASK) == 0
assertion at line 407), but the root cause is identical: missing
bounds validation in hammer_format_volume_header lets an un-aligned /
out-of-range vol_buf_beg propagate into hammer_format_freemap's
I/O. Either KKASSERT is a panic β both are reachable from the same
unchecked inputs.
Privilege / threat model
HAMMERIOC_ADD_VOLUME requires caps_priv_check(cred,
SYSCAP_NOVFS_IOCTL) (root). The bug is a root-only kernel-panic DoS,
not an unprivileged escalation. Realistic impact:
- A HAMMER admin (or a buggy / compromised userspace HAMMER tool) can panic the kernel by issuing a single malformed ADD_VOLUME ioctl.
- On a non-INVARIANTS kernel the KKASSERTs are compiled out, and
hammer_format_freemapwould instead run its 4 TiB-stride loop over the bogusaligned_vol_free_end, producing a sustained I/O storm / kernel livelock (also DoS). We did not separately reproduce this onnoinv; the panic on the default GENERIC kernel is the more important and more realistic demonstration.
PoC
trigger.cβ opens the mountpoint and issues the crafted ioctl.run.shβ sets up the base HAMMER fs (newfs_hammer), mounts it, creates a 1 GiB vn-backed file for the new volume, then runs the trigger. Run as root.
Build & run
./build.sh # cc -o trigger trigger.c ssh dfbsd cd /root/df0869_staged/DF-0869 && ./run.sh
Expected output
- Unpatched
#0kernel: trigger never returns; the guest dies mid-syscall.boot.logshows:HAMMER(df0869) Initialize freemap volume 1 dscheck(vn2): bio_offset 262143 is not on a sector boundary (ssize 512) panic: assertion "error == 0" failed in hammer_ioc_volume_add at /usr/src/sys/vfs/hammer/hammer_volume.c:118 - Patched
#1kernel: trigger returns promptlyioctl returned -1 (errno=79 'Inappropriate file type or format'); guest stays up.dmesgshows:HAMMER(df0869) volume 1 has non-positive geometry HAMMER(df0869) An error occurred: 79
PoC changes from initial draft
There was no initial PoC (findings/poc/DF-0869/ did not exist). The
runner authored the entire evidence pack from scratch: trigger.c,
run.sh, build.sh, README.md, panic.txt, env.txt,
fix.diff, plus the run/build logs and manifest.json. Two iterative
fixes were required during development:
- The new-volume file path was originally passed as a regular file
path;
hammer_install_volumerequires a vnode disk viavn_isdisk, sorun.shwas updated tovnconfig -c vn2the new-volume file and pass/dev/vn2. - A 0-byte newvol file was reported as
unusedbydiskinfoand rejected;run.shwas updated totruncate -s 1Gthe newvol file first. (This is fine for the threat model: the bug is thatioc->vol_sizeis never validated against the real device size, so a 1 GiB file happily "advertises" 2^49 bytes of buffer area.)
A small C simulator (/tmp/sim.c) was used to compute vol_buf_size
exactly and resolve why the line-407 KKASSERT did not fire (the macro
masks 52 bits, not 48 as the finding markdown supposed).
Recommended fix (fix.diff)
Two-layer validation in hammer_format_volume_header, both returning
EFTYPE (the existing error used by this function):
- Input validation β reject
boot_area_size < 0,memory_log_size < 0, orvol_size <= 0up front. These fields reserve on-disk space; negative values are nonsensical and are what produced the un-alignedvol_buf_begin this PoC. - Result validation β after the existing
<0check, also requireHAMMER_VOL_BUF_SIZE(ondisk) <= HAMMER_OFF_SHORT_MASK(i.e. the buffer area fits in the zone-2 short-offset range, satisfying theKKASSERTathammer_volume.c:407) and thatvol_buf_begisHAMMER_BUFSIZE-aligned (avoids the sector-boundary panic at thehammer_breadsite).
The fix is minimal and targeted; it does not change the function
signature, the success path, or any legitimate add-volume operation.
A hammer volume-add of a real 1 GiB vn device to a 2 GiB HAMMER fs
succeeds on the patched kernel (negative-regression test passed).
The full standalone git-apply-able diff is in fix.diff. It
supersedes the finding markdown's ## Recommended fix proposal
(none was filed); the runner authored it post-verification with
line-accurate root-cause understanding.
Phase 8 β fix validation
- Baseline (unpatched
#0): PoC panics the kernel mid-syscall; guest dies; panic signature inboot.lognameshammer_ioc_volume_addathammer_volume.c:118. (run.log+boot.log.unpatched_panic+panic.txt.) - Patched (
#1, today's build, sha2562ffacc98086014898d71ea0e421c21ceed0d2341f7b8c0ce590dbeac81af9556): PoC returns cleanly witherrno=79 (EFTYPE); guest stays up across 3 consecutive runs;dmesgshowsHAMMER(df0869) volume 1 has non-positive geometry. A legitimatehammer volume-add /dev/vn2 /mnt/df0869succeeds on the patched kernel (volume-listshows both vn1 and vn2), proving the fix is non-breaking.
=> fix_status: fixed (clean before/after contrast).
Fix verification
fixedVALIDATED: baseline panics; patched returns EFTYPE (no panic); legitimate volume-add succeeds.
BEFORE: panic at :118. AFTER: errno=79 EFTYPE. Regression: volume-add works.
Confirmed kernel references
- sys/vfs/hammer/hammer_volume.c:652
- sys/vfs/hammer/hammer_volume.c:654
- sys/vfs/hammer/hammer_volume.c:656
- sys/vfs/hammer/hammer_volume.c:661
- sys/vfs/hammer/hammer_volume.c:662
- sys/vfs/hammer/hammer_volume.c:664
- sys/vfs/hammer/hammer_volume.c:117
- sys/vfs/hammer/hammer_volume.c:118
- sys/vfs/hammer/hammer_volume.c:407
- sys/vfs/hammer/hammer_ioctl.c:197
- sys/vfs/hammer/hammer_disk.h:83
Detail
Exploit chain
none -- pure bounds-validation gap, no corruption primitive.
Evidence (decisive lines)
BEFORE: panic KKASSERT error==0 at hammer_volume.c:118 (dscheck sector-misalignment). AFTER: errno=79 EFTYPE, guest up 3/3 runs. Legitimate volume-add still works.
PoC changes
Authored from scratch: trigger.c (HAMMERIOC_ADD_VOLUME boot_area_size=-1 vol_size=2^49), run.sh (newfs_hammer + vnconfig + trigger), fix.diff (reject negative sizes + require vol_buf_size<=SHORT_MASK + vol_buf_beg alignment), VERDICT.md, manifest.json.
Verified recommended fix
Two-layer: (1) reject boot_area_size<0 || memory_log_size<0 || vol_size<=0; (2) require vol_buf_size<=HAMMER_OFF_SHORT_MASK + vol_buf_beg HAMMER_BUFSIZE-aligned. Both return EFTYPE. Full diff in findings/poc/DF-0869/fix.diff.
Verdict
REPRODUCED. hammer_format_volume_header missing bounds: boot_area_size=-1 + vol_size=2^49 -> vol_buf_beg unaligned -> format_freemap hammer_bread sector-misalignment -> KKASSERT(error==0) panic at hammer_volume.c:118. Root-only HAMMERIOC_ADD_VOLUME.
No comments yet.