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

volume-del on a volume hosting UNDO-zone big-blocks: bare KKASSERT(0) panics INVARIANTS kernels; on production kernels the case falls through and bzero()s live undo FIFO entries (silent crash-recovery corruption)

Summary

hammer_free_freemap and hammer_count_bigblocks treat a zone-3 layer2 entry on a non-root volume as impossible via bare KKASSERT(0) (:554, :590, :804 - the count runs first on the del path at :207). volume-del only refuses the ROOT volume (:195-200), yet vol0_undo_array[] is an on-disk-legal mechanism for spanning the undo FIFO across volumes; only in-tree tooling convention keeps undo on root. With a crafted multi-volume image (CRC-correct layer1/2, verified active at hammer_blockmap.c:54-60), a privileged volume-del of an undo-bearing volume panics INVARIANTS kernels; on production kernels the asserts compile out, the second pass falls into default: and bzero()s the undo layer2 entries (:591-596) while the root undomap still references those offsets - the undo FIFO is silently truncated and crash recovery later replays garbage. Not executed (needs a two-volume crafted image; untested - line-accurate path documented). Fix: replace the three KKASSERT(0) undo cases with EBUSY rejection.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3153 Β· 2 files
FileTypeDescriptionSize
README.md file 3.0 KB ↓ raw
verdict.json file 2.2 KB view raw
README.md file
↓ download raw

DF-3153 β€” volume-del on a volume carrying UNDO-zone big-blocks: KKASSERT(0) panic (INVARIANTS) / silent undo-FIFO wipe (production)

What

hammer_free_freemap and hammer_count_bigblocks treat a layer2 entry in zone HAMMER_ZONE_UNDO_INDEX (3) on the volume being removed as an impossible state via bare KKASSERT(0):

hammer_ioc_volume_del only refuses the ROOT volume (hammer_volume.c:195-200). The undo FIFO is described by vol0_undo_array[] on the root volume's ondisk (hammer_disk.h:789) whose entries are zone2 offsets that the format permits pointing at ANY volume β€” the array exists precisely because undo may span volumes. All in-tree tooling happens to allocate undo big-blocks on the root volume only (sbin/hammer/ondisk.c alloc_undo_bigblock(root_vol)), so reaching the assertion requires a hand-crafted multi-volume image with a CRC-correct zone-3 layer2 entry on a non-root volume, then hammer volume-del of that volume (privileged).

Impact

  • INVARIANTS kernels (incl. stock audit guest): deterministic kernel panic from the admin volume-del ioctl (KKASSERT(0)).
  • Production (non-INVARIANTS) kernels: KKASSERTs compile out; the case FALLS THROUGH β€” first pass treats the undo block as ignorable (continue), second pass falls into default: and bzero()s the undo layer2 entry (hammer_volume.c:591-596) while the root volume's undomap/vol0_undo_array still references those offsets. The undo FIFO is silently truncated: later undo writes/reads hit blocks the freemap now reports free (and the volume is being erased) β†’ crash-recovery corruption (stale/garbage undo replay) with no error reported to the operator.

Preconditions / why untested

Root (SYSCAP_NOVFS_IOCTL) issues the del on a crafted multi-volume HAMMER1 image. The code path is certain (dead switch on an on-disk-permitted state); the trigger requires assembling a two-volume image with layer1/layer2 CRCs correct (hammer_blockmap.c:59 panics on layer1 CRC mismatch), which was not built for this run β€” no PoC executed, verdict untested. Severity Low: privileged operator + crafted metadata.

Fix sketch

The del path must reject a volume hosting undo big-blocks explicitly (return EBUSY/EINVAL with a clear operator message), never assert:

--- a/sys/vfs/hammer/hammer_volume.c
+++ b/sys/vfs/hammer/hammer_volume.c
@@ -550,7 +550,9 @@ hammer_free_freemap(hammer_transaction_t trans, hammer_volume_t volume)
            switch (layer2->zone) {
            case HAMMER_ZONE_UNDO_INDEX:
-               KKASSERT(0);
+               krateprintf(&hmp->kdiag, "volume %d holds "
                    "undo big-blocks\n", volume->vol_no);
+               error = EBUSY;
+               goto end;
            case HAMMER_ZONE_FREEMAP_INDEX:

(the same replacement applies at :589 and :803; at :803 count_bigblocks should return EBUSY so the del aborts before the reblock).

Confirmed kernel references

Detail

Exploit chain

privileged: mount crafted 2-volume hammer1 image with zone-3 layer2 (valid CRCs) on vol 1, then hammer volume-del

Evidence (decisive lines)

['README.md fix sketch and line references']

PoC changes

no seed; no PoC built (crafted-image precondition, Low severity, privileged)

Verified recommended fix

Replace the three KKASSERT(0) undo cases with an explicit EBUSY/EINVAL rejection of volumes hosting undo big-blocks (del must abort before reblock/freemap wipe)

Verdict

Code path certain but not executed: hammer_free_freemap/hammer_count_bigblocks handle a zone-3 (UNDO) layer2 entry on a non-root volume with bare KKASSERT(0) (hammer_volume.c:554/590/804) while volume-del only guards the root volume (hammer_volume.c:195). In-tree tooling never places undo off-root (sbin/hammer/ondisk.c alloc_undo_bigblock(root_vol)), so triggering requires a CRC-correct crafted multi-volume image plus a privileged HAMMERIOC_DEL_VOLUME - not built this run. INVARIANTS: deterministic panic from the admin ioctl; production: the assert compiles out and the second pass bzero()s the undo layer2 entry while the root undomap still references it, silently truncating the undo FIFO (crash-recovery corruption, no error surfaced).