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)
PoC verification
Evidence pack
findings/poc/DF-3153 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | file | 3.0 KB | β raw | |
| verdict.json | file | 2.2 KB | view 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):
- sys/vfs/hammer/hammer_volume.c:553-554 (free_freemap, first pass)
- sys/vfs/hammer/hammer_volume.c:589-590 (free_freemap, second pass)
- sys/vfs/hammer/hammer_volume.c:803-804 (count_bigblocks β reached on the del path at hammer_volume.c:207 BEFORE any EBUSY check)
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 intodefault: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).
No comments yet.