Overlapping key ranges in on-disk blockref arrays unvalidated β chain-insert collision panic or RB_REMOVE wipes the parent's rbtree root
| Field | Value |
|---|---|
| ID | DF-2618 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:H |
| CWE | CWE-20 Improper Input Validation (β resource leak / RB-tree topology corruption) |
| File | sys/vfs/hammer2/hammer2_chain.c |
| Lines | 313-320 |
| Area | vfs |
| Confidence | certain |
| Discovered | 2026-08-28 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | hammer2 |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
hammer2_chain_cmp() counts overlapping key ranges as a match, but nothing
validates that a blockref array read from media contains non-overlapping
ranges. When a lookup instantiates a chain for a second, partially
overlapping bref, RB_INSERT in hammer2_chain_insert() returns the
existing chain: on INVARIANTS kernels an immediate KASSERT panic; on release
builds the code nevertheless sets HAMMER2_CHAIN_ONRBTREE, chain->parent
and bumps core.chain_count on a chain never linked into the tree. When that
chain is later removed (lastdrop or delete-helper), RB_REMOVE on the
zeroed, never-linked node takes the parent==NULL path of
sys/sys/tree.h:641-652 and assigns RB_ROOT(head)=NULL β wiping the
parent's entire live tree while its chains are still linked, orphaning them
permanently.
Root cause
chain.c:104-117 (cmp: overlap == match); chain.c:313-316:
xchain = RB_INSERT(...); KASSERT(xchain == NULL, ...) β KASSERT is
INVARIANTS-only (sys/sys/systm.h:96-118); chain.c:317-320 then
unconditionally sets ONRBTREE/parent/++chain_count/++generation on the
unlinked chain; removal paths RB_REMOVE it at chain.c:623-624 (lastdrop)
and 3556/3623 (delete helper). The chain was allocated M_ZERO
(chain.c:203-204) so its rbnode links are NULL: RB_REMOVE's
both-children-NULL + parent-NULL branch executes RB_ROOT(head) = child =
NULL (sys/sys/tree.h:641-652). Reachable with A=[K,K+0xF] in the rbtree
and crafted base entry B=[K+8,K+0x17]: a lookup of [K+0x10,K+0x17] makes
base_find return B (chain.c:5035) while hammer2_chain_find misses A, so
chain_get(B) (chain.c:2610) runs the colliding insert β from plain
readdir iteration or a single lookup with the right hash range.
Adjacent-range media writes also hit the unconditional panic at
chain.c:5308-5311.
Threat model & preconditions
- Attacker position: crafted filesystem image, mounted; unprivileged user
triggers with
ls/readdir/name lookup whose key falls in the non-overlapped sub-range of the second entry. - Privileges gained or impact: deterministic panic on INVARIANTS
kernels; on release builds permanent kernel-memory leak of every cached
chain under the parent (unreachable but still counted in
core.chain_count), chain_count/live_count desynchronization wedging indirect-block collapse and lastdrop, and silent lookup misses of live entries. - Required config or capabilities: mount of crafted image.
- Reachability: readdir/lookup through the crafted directory.
Proof of concept
Build & run
craft image: two dirent brefs with partially overlapping ranges in a
directory inode's blockset, e.g. A{key=0x1000,keybits=4} and
B{key=0x1008,keybits=4} (recompute parent check); mount; ls /mnt/dir
Expected output
INVARIANTS: panic "assertion xchain == NULL failed" at chain.c:314. Release: after B's last drop, chain stats show chain_count>0 on a parent with an 'empty' tree; leaked chains never freed; cached entries vanish from lookups.
Impact
Panic (INVARIANTS) or permanent kernel heap leak + topology corruption (release). No direct memory corruption; integrity/availability.
Recommended fix
Treat an RB_INSERT collision as a media-corruption error on all builds instead of asserting; poison the parent's error so iterating lookups terminate instead of spinning to the maxloops panic.
--- a/sys/vfs/hammer2/hammer2_chain.c
+++ b/sys/vfs/hammer2/hammer2_chain.c
@@ -310,10 +310,23 @@ hammer2_chain_insert(hammer2_chain_t *parent, hammer2_chain_t *chain,
/*
* Insert chain
*/
xchain = RB_INSERT(hammer2_chain_tree, &parent->core.rbtree, chain);
- KASSERT(xchain == NULL,
- ("hammer2_chain_insert: collision %p %p (key=%016jx)",
- chain, xchain, chain->bref.key));
+ if (xchain != NULL) {
+ /*
+ * Overlapping key range vs an existing chain β the
+ * on-disk blockref array is corrupt. We MUST NOT set
+ * ONRBTREE/parent/chain_count on an unlinked chain: on
+ * release builds the later RB_REMOVE() in lastdrop or
+ * the delete helper would wipe RB_ROOT of the parent's
+ * live tree (never-linked node, NULL parent -> root=0).
+ *
+ * Error the parent so lookup/scan iteration terminates
+ * (chain.c:2473 / 2876) instead of retrying forever.
+ */
+ parent->error = HAMMER2_ERROR_CHECK;
+ error = HAMMER2_ERROR_CHECK;
+ goto failed;
+ }
atomic_set_int(&chain->flags, HAMMER2_CHAIN_ONRBTREE);
chain->parent = parent;
References
- sys/sys/tree.h:641-652 (RB_REMOVE NULL-parent root wipe)
- DF-0763 class (crafted blockref arrays)
Timeline
- 2026-08-28 Discovered during automated audit (pass 2, GLM 5.3).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2618 Β· 25 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.9 KB | β raw | |
| VERDICT.md | β | 8.3 KB | β raw | |
| mkbase2618.sh | β | 808 B | view raw | |
| forge_df2618.py | β | 11.1 KB | view raw | |
| trigger.sh | β | 1.3 KB | view raw | |
| build.sh | β | 728 B | view raw | |
| run.sh | β | 682 B | view raw | |
| base2618.img | β | 64.0 MB | β download | |
| craft2618.img | β | 64.0 MB | β download | |
| code_hashes.txt | β | 398 B | view raw | |
| control.log | β | 686 B | view raw | |
| run.log | β | 175 B | view raw | |
| run2.log | β | 175 B | view raw | |
| craft_run.log | β | 175 B | view raw | |
| panic.txt | β | 1.1 KB | view raw | |
| panic2.txt | β | 789 B | view raw | |
| env.txt | β | 1.7 KB | view raw | |
| dmesg.txt | β | 243 B | view raw | |
| fix.diff | β | 5.1 KB | view raw | |
| fix_build.log | β | 5.7 MB | β download | |
| fix_control_run.log | β | 686 B | view raw | |
| fix_run.log | β | 631 B | view raw | |
| fix_write_smoke.log | β | 111 B | view raw | |
| manifest.json | β | 1.6 KB | view raw | |
| verdict.json | β | 6.6 KB | view raw |
DF-2618 β Overlapping key ranges in on-disk blockref arrays unvalidated β chain-insert collision panic / rbtree root wipe
- Verified 2026-08-28 by df-bsd-reviewer (verify mode) on the QEMU/KVM DragonFly guest, stock INVARIANTS kernel #0 (Thu Jul 2 06:02:54 2026) and fix-validated on rebuilt kernel #1. *
What the finding claims
hammer2_chain_cmp() (sys/vfs/hammer2/hammer2_chain.c:97-118) treats
overlapping [key, key + 2^keybits - 1] ranges as a match (cmp == 0). A
crafted on-disk parent whose blockref array contains two overlapping brefs
drives RB_INSERT in hammer2_chain_insert() (chain.c:313-320) into a
collision: INVARIANTS builds panic at the KASSERT (chain.c:314); release
builds continue with a phantom chain (ONRBTREE set but never linked) whose
later RB_REMOVE wipes parent->core.rbtree's root (sys/sys/tree.h:641-652).
Result
REPRODUCED (panic), then FIXED.
-
Baseline (stock INVARIANTS kernel): mounting the forged image and
stating a file whose inum-keyed lookup crosses the overlapping entries panics deterministically:panic: hammer2_chain_insert: collision 0xfffff80118c22100 0xfffff80118c21980 (key=0000000000000401)
hammer2_chain_insert() at hammer2_chain_insert+0x15b hammer2_chain_get() at hammer2_chain_get+0x64 hammer2_chain_lookup() at hammer2_chain_lookup+0x615 hammer2_chain_inode_find() at hammer2_chain_inode_find+0x117 hammer2_xop_nresolve() at hammer2_xop_nresolve+0x1a5
(panic.txt, serial capture; guest down.) The trigger is pure
namei/readdir activity on a mounted image β no special privileges beyond
the mount itself.
-
Release-build consequence (rbtree root wipe): verified by line-precise source trace β see VERDICT.md Β§3. Not demoed on a no-INVARIANTS kernel (build cost); the trace is exact and the INVARIANTS panic proves the primitive.
-
Fix validation (
fix.diff, rebuilt kernel #1): the same crafted image mounts,lslists every dirent, lookups that cross the corrupt entries fail with ENOENT instead of panicking, the fix's rate-limitedhammer2_chain_insert: collision ... (key=0000000000000401)message appears exactly once on the console, a write to the corrupt directory succeeds, umount is clean, and the system (which roots on hammer2) stays up. Control image: 100% normal behavior on both kernels.
Reproduce
Host side (from this directory):
./build.sh # mkbase2618.sh in guest -> base2618.img; forge -> craft2618.img; push ./run.sh # control run (no panic), then craft run (panic on stock kernel)
Guest side:
sh /root/poc/df2618/trigger.sh control # pristine image: everything works
sh /root/poc/df2618/trigger.sh craft # forged image: panic at `stat c`
# (serial: vm.sh log; see panic.txt)
For fix validation: apply fix.diff in the guest /usr/src, make -j6
nativekernel && make installkernel, reboot, rerun both triggers
(fix_control_run.log / fix_run.log / fix_write_smoke.log).
The forged image
forge_df2618.py walks volhdr -> sroot -> PFS "testvol" inode -> indirect
array and patches two file-INODE brefs so their key ranges overlap:
INODE(a) key=1024 keybits 0->1 => [1024,1025] (swallows 1025) INODE(b) key=1025 keybits 0->1 => [1025,1026] (extends beyond)
Sorted array order is preserved (only keybits changed). CHECK_NONE (methods=0x00) is set on the covering indirect bref, the PFS inode bref and the sroot bref, and all volhdr CRC32Cs are recomputed (technique proven in DF-2616/DF-2617/DF-2620).
Trigger mechanics (see VERDICT.md for the full trace): stat a pins
chain(1024)=[1024,1025] in the indirect chain's core.rbtree via the inode
cache; stat c -> hammer2_chain_inode_find(1026) -> base_find skips
[1024,1025] (its end < 1026) and stops at the overlapping [1025,1026] bref;
hammer2_chain_find misses (pinned chain ends at 1025); chain_get ->
RB_INSERT -> cmp == 0 -> KASSERT panic.
DF-2618 VERDICT β verified reproduced (panic) on stock INVARIANTS kernel; fix validated on rebuilt kernel
Guest: DragonFly dfbsd 6.5-DEVELOPMENT #0 x86_64 (stock, INVARIANTS), dfbsd-qemu/vm.sh. Baseline panic captured 2026-08-28 ~19:44 UTC; fix validated 19:52-19:58 UTC on rebuilt kernel #1.
1. Reproduction (baseline, stock kernel #0)
Crafted image craft2618.img (forge details in README.md):
root@dfbsd# mount -t hammer2 /dev/vn0@testvol /mnt/h2 # OK root@dfbsd# ls /mnt/h2 # a b c d (OK) root@dfbsd# stat /mnt/h2/a # OK (inum 1024) root@dfbsd# stat /mnt/h2/c panic: hammer2_chain_insert: collision 0xfffff80118c22100 0xfffff80118c21980 (key=0000000000000401)
Full serial capture in panic.txt; guest went down (vm.sh status => down).
The panic is 100% deterministic across the two runs performed (run.log stops
mid-script at the same stat; panic2.txt = second capture during re-test of
the identical image β identical signature).
Why it fires (line-precise)
forge_df2618.pysets INODE(1024).keybits=1 => [1024,1025] and INODE(1025).keybits=1 => [1025,1026] in the indirect array at media offset 0x1c01000 (array order by key preserved: 1024 < 1025 < 1026 < 1027).stat a(nresolve) ->hammer2_chain_inode_find(1024)(chain.c:5640) ->hammer2_chain_lookup[1024,1024] ->base_find(chain.c:4921) stops at INODE(1024) ->hammer2_chain_get(chain.c:2051) ->hammer2_chain_insert(chain.c:292) inserts chain(1024)=[1024,1025] into the indirect parent's core.rbtree. The inode (ip) keeps the chain referenced (pinned) after the stat.stat c->hammer2_chain_inode_find(1026)-> lookup [1026,1026]: -base_find: INODE(1024).end=1025 < 1026 -> advance; INODE(1025).end= 1026 >= 1026 -> break at the overlapping entry (chain.c:4975-4986). -hammer2_chain_find(chain.c:1931, RB_SCAN of the rbtree): pinned chain(1024) covers [1024,1025], not 1026 -> no match. - combined_find (chain.c:5020): only the blockref matched ->hammer2_chain_geton INODE(1025) (chain.c:2613).hammer2_chain_insert->RB_INSERT(chain.c:313) walks to pinned chain(1024):hammer2_chain_cmp(chain.c:97-118): c1=[1024,1025] (new chain), c2=[1024,1025] (existing) -> overlap -> returns 0 ->RB_INSERTreturns the existing node (sys/sys/tree.h:674-675) ->KASSERT(xchain == NULL)(chain.c:314) -> panic, exactly the finding's claim.
Impact on the stock kernel: local kernel panic (DoS) from mounting and stat'ing files on a corrupt/crafted hammer2 volume. Precondition: the mount itself (root, or unprivileged with vfs.usermount=1 + accessible device) β matching the Medium severity and hammer2 bucket of the filed finding.
2. Control (stock kernel #0 and fixed kernel #1)
Pristine base2618.img: mount, ls, all stats, open, read-back β all succeed
on both kernels (control.log, fix_control_run.log). The forge β not the
base image or the CHECK_NONE edits β is what triggers the bug.
3. Release-build consequence (rbtree root wipe) β source trace
Not demoed on a no-INVARIANTS kernel (extra ~35-min build for an effect that is fully determined by the code); traced line-by-line instead:
- Chains are allocated M_ZERO (chain.c:203-204), so a fresh chain's
rbnode(rbe_left/rbe_right/rbe_parent/rbe_color) is all-zero. - On collision
RB_INSERTreturns without linking orRB_SET-ting the new element (tree.h:674-675 returnstmp;RB_SETat tree.h:677 is never reached), so the phantom chain's rbnode stays all-NULL. - Release build compiles out the KASSERT; chain.c:317-319 then sets
HAMMER2_CHAIN_ONRBTREE,chain->parent = parent, and bumpsparent->core.chain_countfor a chain that is not in the tree. hammer2_chain_getreturns the phantom as a normal chain (error==0; thebcmpat chain.c:2634 passes since bref was copied from the same bref).- When its last ref drops,
hammer2_chain_lastdrop(chain.c:618-628) sees ONRBTREE set and callsRB_REMOVE(&parent->core.rbtree, chain)(chain.c:623). Inname##_RB_REMOVE(tree.h:585):RB_LEFT(elm)==NULL->child = RB_RIGHT(elm) = NULL(tree.h:597-598);parent = RB_PARENT(elm) = NULL(tree.h:641);color = RB_COLOR(elm) = RB_BLACK(0)-> parent==NULL branch executesRB_ROOT(head) = child = NULL(tree.h:651-652), wiping the parent's live rbtree root even though it contains other referenced chains.RB_REMOVE_COLOR(head, NULL, NULL)(tree.h:654-655) is a no-op (loop condition tree.h:510-511 false). - Consequences: every chain still linked in that tree becomes unreachable by
lookup (they keep their refs/locks;
chain_countis left inflated), later lookups re-create chains for the same brefs into the now-empty tree, and the orphaned chains can never be found for deletion/flush -> permanent chain leak + lookup misses, and duplicated in-memory state for the same media bref β structural memory corruption of the chain topology, exactly as the finding describes. (Also note tree.h:641-652 runs with the parent core spinlock held, so at least the wipe itself is not racy.)
So the finding's release-path claim is code-confirmed; only the KASSERT panic is directly observable on this guest's INVARIANTS kernel.
4. Fix validation (fix.diff, kernel #1 built 19:52:28 UTC 2026)
fix.diff (git-apply-able, verified with git apply --check against the
read-only sys/ tree; applied inside the guest's /usr/src copy):
hammer2_chain_insert(chain.c:313-320): onRB_INSERTcollision, rate-limitedkrateprintf+error = HAMMER2_ERROR_CHECK, no ONRBTREE/parent/chain_count/generation mutation (removes the KASSERT and the phantom-state bug in one stroke).hammer2_chain_get(chain.c:2089-2101): EAGAIN keeps the existing unlock/drop/NULL race path; corruption (CHECK) returns the chain locked withchain->errorset so callers can skip the entry.hammer2_chain_lookup(chain.c:2616) andhammer2_chain_scan(chain.c:2985): on such an errored unlinked chain, unlock/drop and resume the scan atbref->key + (1 << bref->keybits)β the same advance the DELETED-skip path uses β so iteration can never spin on the bad entry.- Flush-time
chain_getsites (create_indirect chain.c:3942, indirect maintenance chain.c:4195): skip past the corrupt bref (key advanced), keeping those loops finite. hammer2_chain_create(chain.c:3325): insert failure markschain->error(chain destroyed cleanly on drop since ONRBTREE is unset).
Rebuild: cd /usr/src && make -j6 nativekernel && make installkernel
(fix_build.log, BUILD_RC=0, 38176 lines untrimmed), reboot into #1.
Rerun of the exact same PoC:
trigger.sh control(pristine): identical-to-stock behavior, all operations succeed (fix_control_run.log).trigger.sh craft(forged): mount OK,lslists a b c d,stat aOK,stat b/stat cfail ENOENT (graceful: the overlapping entries shadow keys 1025/1026 β pre-fix the same lookups either ENOENT'd (b) or PANICKED (c)), script completes, guest stays up (fix_run.log).- Console shows the fix's detection message exactly once:
hammer2_chain_insert: collision 0xfffff80118d82880 0xfffff80118d82100 (key=0000000000000401)(dmesg.txt). - Write smoke test on the corrupt image: create/read/sync/umount of /mnt/h2/newfile all succeed; system (hammer2 root) stable (fix_write_smoke.log).
fix_status: fixed β baseline behavior (deterministic panic) gone, no regression on the control image, sane degradation on the corrupt image.
5. Notes / limitations
- The panic needs only VOP lookup activity; the explicit fd-pin originally planned for the trigger turned out unnecessary (the inode cache alone pins the colliding chain across the two stats).
- The release-build root-wipe was verified by trace, not runtime (see Β§3); building a no-INVARIANTS kernel was out of time budget and would not change the classification (the INVARIANTS panic proves the overlap primitive; the trace proves the release consequence).
not-a-bugcheck:hammer2_chain_cmp's overlap==match semantics are intentional (comment chain.c:104-107) and load-bearing forhammer2_chain_find; the defect is the absence of corruption handling when the on-disk array violates the no-overlap invariant, which the fix adds at the single choke point all media-driven inserts pass through.
Fix verification
fixedfixed: identical PoC on the patched kernel mounts/lists/stats without panic (corrupt-shadowed names degrade to ENOENT, one rate-limited collision message on console); control image identical to stock; write path on the corrupt volume + clean umount + system stability verified; kernel boots and roots on hammer2 throughout
fix.diff (git apply --check clean, 8 hunks applied in-guest), fix_build.log (BUILD_RC=0), fix_control_run.log, fix_run.log, fix_write_smoke.log, dmesg.txt (fix collision message on patched kernel)
Confirmed kernel references
- sys/vfs/hammer2/hammer2_chain.c:97-118
- sys/vfs/hammer2/hammer2_chain.c:203-204
- sys/vfs/hammer2/hammer2_chain.c:313-320
- sys/vfs/hammer2/hammer2_chain.c:618-628
- sys/vfs/hammer2/hammer2_chain.c:2089-2101
- sys/vfs/hammer2/hammer2_chain.c:2610-2617
- sys/vfs/hammer2/hammer2_chain.c:4975-4986
- sys/vfs/hammer2/hammer2_chain.c:5640-5731
- sys/sys/tree.h:641-652
- sys/sys/tree.h:661-688
Detail
Exploit chain
unprivileged (post-mount) namei on a crafted/corrupt hammer2 volume -> hammer2_chain_inode_find(inum) -> chain_lookup: base_find selects the overlapping bref while the pinned sibling chain does not cover the key -> chain_get -> RB_INSERT -> hammer2_chain_cmp overlap==match -> collision -> KASSERT panic (INVARIANTS) or phantom chain (release) whose lastdrop RB_REMOVE nulls parent->core.rbtree.rbh_root (tree.h:651-652), structurally corrupting the chain topology. Impact ceiling: kernel DoS (verified) + in-memory chain-cache corruption/leak on release builds (traced); no R/W primitive demonstrated, consistent with the filed Medium severity.
Evidence (decisive lines)
["panic.txt + panic2.txt: two identical serial captures of 'panic: hammer2_chain_insert: collision ... (key=0000000000000401)' with the full backtrace (fresh boot each)", "run.log / run2.log: trigger output stops exactly at 'stat /mnt/h2/c' both times", 'control.log / fix_control_run.log: pristine image fully functional on stock and patched kernels (forge, not the base image, causes the bug)', 'forge_df2618.py: walks volhdr->sroot->PFS->indirect array, sets keybits so sibling INODE ranges overlap, CHECK_NONE ancestors, recomputes volhdr CRC32Cs', 'VERDICT.md section 3: line-by-line release-build root-wipe trace', "fix_build.log (38176 lines, BUILD_RC=0) + fix_run.log + fix_write_smoke.log + dmesg.txt: patched kernel #1 handles the same image gracefully (console 'hammer2_chain_insert: collision ... key=...0401' rate-limited message, no panic, write path OK)"]
PoC changes
Seed had no runnable code. Wrote mkbase2618.sh (4-file base image), forge_df2618.py (overlap forge inside the root directory's INDIRECT array - the seed sketch assumed direct blockset entries, but hammer2 pushes children into indirects even at 4 files; cribbed the volhdr/sroot/PFS walk, CHECK_NONE ancestors and CRC32C recompute from DF-2616/DF-2617/DF-2620), and trigger.sh. Deterministic holder: stat a pins chain(1024) via the inode cache; stat c crosses the overlap - the planned fd-pin turned out unnecessary. The readdir-only trigger from the seed does NOT fire (readdir sweeps only the dirent-hash space, vnops.c:673); the inum-keyed inode_find path is the working trigger.
Verified recommended fix
hammer2_chain_insert: on RB_INSERT collision, rate-limited kprintf + return HAMMER2_ERROR_CHECK without setting ONRBTREE/parent/chain_count; hammer2_chain_get returns the errored unlinked chain; lookup/scan (and the two flush-time chain_get sites) skip past bref->key + (1<<keybits) so iteration stays finite (see fix.diff)
Verdict
REPRODUCED on the stock INVARIANTS kernel: a forged hammer2 image whose indirect blockref array contains two overlapping [key,key+2^keybits-1] ranges (INODE(1024)->[1024,1025], INODE(1025)->[1025,1026]) panics deterministically at hammer2_chain_insert's KASSERT (chain.c:314) on mount+stat, with the exact claimed backtrace (hammer2_xop_nresolve -> hammer2_chain_inode_find -> hammer2_chain_lookup -> hammer2_chain_get -> hammer2_chain_insert, panic key=0x401). Verified twice across fresh boots (panic.txt, panic2.txt); control image fully clean; guest down each time. The release-build consequence (RB_INSERT collision -> phantom chain with ONRBTREE set but never linked -> later RB_REMOVE takes the NULL-parent branch and executes RB_ROOT(head)=NULL at sys/sys/tree.h:651-652, wiping the parent's live rbtree -> chain leak + lookup misses) is verified by line-precise source trace (chain.c:203-204 M_ZERO alloc, tree.h:674-677 no RB_SET on collision, chain.c:317-319 flags set anyway, chain.c:618-628 lastdrop RB_REMOVE) but not demoed on a no-INVARIANTS kernel. fix.diff (graceful collision refusal at chain_insert + loop-safe skip-past-corrupt-entry at all chain_get call sites) rebuilt as kernel #1: the identical crafted image mounts, lists, and stats without panic (corrupt-shadowed names ENOENT, one rate-limited console collision message), writes to the corrupt directory succeed, control image unchanged, system stable. fix_status=fixed.
No comments yet.