β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0869

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0869 Β· 18 files
FileTypeDescriptionSize
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
README.md readme PoC overview, build/run, expected output, threat model
↓ download 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 issues HAMMERIOC_ADD_VOLUME with boot_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 β€” compiles trigger.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).

VERDICT.md verdict full narrative: mechanism, panic signature, fix validation
↓ download raw

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/):

  1. hammer_ioctl.c:197 β€” caps_priv_check passes (root).
  2. 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 = 0x1FFFFFFFC0001 is positive, so the guard passes and the bogus header is committed.
  3. hammer_volume.c:107 β€” hammer_install_volume(hmp, ioc->device_name, NULL, &ondisk) succeeds (it accepts the bogus geometry without further checks); volume->maxbuf_off is set from the corrupt vol_buf_end.
  4. 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); Because HAMMER_OFF_SHORT_MASK = 0x000FFFFFFFFFFFFF is 52 bits (not 48 as the finding markdown supposed), vol_buf_size β‰ˆ 2^49 passes this KKASSERT. (My first trace assumed 48 bits and expected a panic here; the live kernel proved otherwise β€” see the simulator output referenced in panic.txt.)
  5. hammer_format_freemap's first layer-2 hammer_bread() translates the encoded zone-2 offset for vol_no=1 into a byte offset on vn2 equal to vol_buf_beg = 262143. The vn driver rejects the I/O: dscheck(vn2): bio_offset 262143 is not on a sector boundary (ssize 512) hammer_bread returns EINVAL.
  6. hammer_volume.c:118 β€” KKASSERT(error == 0); fires after hammer_format_freemap returns 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_freemap would instead run its 4 TiB-stride loop over the bogus aligned_vol_free_end, producing a sustained I/O storm / kernel livelock (also DoS). We did not separately reproduce this on noinv; 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 #0 kernel: trigger never returns; the guest dies mid-syscall. boot.log shows: 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 #1 kernel: trigger returns promptly ioctl returned -1 (errno=79 'Inappropriate file type or format'); guest stays up. dmesg shows: 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:

  1. The new-volume file path was originally passed as a regular file path; hammer_install_volume requires a vnode disk via vn_isdisk, so run.sh was updated to vnconfig -c vn2 the new-volume file and pass /dev/vn2.
  2. A 0-byte newvol file was reported as unused by diskinfo and rejected; run.sh was updated to truncate -s 1G the newvol file first. (This is fine for the threat model: the bug is that ioc->vol_size is 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).

Two-layer validation in hammer_format_volume_header, both returning EFTYPE (the existing error used by this function):

  1. Input validation β€” reject boot_area_size < 0, memory_log_size < 0, or vol_size <= 0 up front. These fields reserve on-disk space; negative values are nonsensical and are what produced the un-aligned vol_buf_beg in this PoC.
  2. Result validation β€” after the existing <0 check, also require HAMMER_VOL_BUF_SIZE(ondisk) <= HAMMER_OFF_SHORT_MASK (i.e. the buffer area fits in the zone-2 short-offset range, satisfying the KKASSERT at hammer_volume.c:407) and that vol_buf_beg is HAMMER_BUFSIZE-aligned (avoids the sector-boundary panic at the hammer_bread site).

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 in boot.log names hammer_ioc_volume_add at hammer_volume.c:118. (run.log + boot.log.unpatched_panic + panic.txt.)
  • Patched (#1, today's build, sha256 2ffacc98086014898d71ea0e421c21ceed0d2341f7b8c0ce590dbeac81af9556): PoC returns cleanly with errno=79 (EFTYPE); guest stays up across 3 consecutive runs; dmesg shows HAMMER(df0869) volume 1 has non-positive geometry. A legitimate hammer volume-add /dev/vn2 /mnt/df0869 succeeds on the patched kernel (volume-list shows both vn1 and vn2), proving the fix is non-breaking.

=> fix_status: fixed (clean before/after contrast).

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: baseline panics; patched returns EFTYPE (no panic); legitimate volume-add succeeds.

BEFORE: panic at :118. AFTER: errno=79 EFTYPE. Regression: volume-add works.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Tue Jul 14 07:08:03 UTC 2026

Confirmed kernel references

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.