hammer_cursor_moved_element() again2 parent-cursor leaf fixup indexes the PARENT's element array with the CHILD's index (oindex instead of pindex) β cursor->leaf left stale inside a node the cursor no longer references during rebalance
Summary
In the second adjustment loop of hammer_cursor_moved_element (triggered when the FIRST element of onode is moved away during a hammer rebalance), tracked cursors sitting on (oparent,pindex) are repointed to (nnode,nindex) and their node reference transferred (:1062-1075). But the leaf fixup compares cursor->leaf against &ondisk->elms[oindex].leaf where ondisk was just reassigned to oparent->ondisk (:1056) and oindex is an index into onode - always 0 on this path. A mirror/reblock-style cursor iterating the internal node has leaf == &oparent->ondisk->elms[pindex].leaf, so the comparison should use pindex; for any pindex != 0 the match fails, cursor->node/index move to nnode while cursor->leaf keeps pointing into oparent's element array - a node the cursor has just dropped its reference on and which rebalance may subsequently empty and delete. The stale pointer is in-bounds of oparent's 4K node while it lives, and hammer_btree_extract overwrites cursor->leaf before the main consumers dereference it, so demonstrated impact is iterator/RIPOUT-detection confusion (wrong element seen, mirror-stream mispositioning) rather than a proven OOB - likely/Low. When the accidental match occurs (pindex==0 == oindex), the fixup writes the correct target, confirming the intent was pindex. Requires a tracked cursor parked on the internal parent (mirror-read in EDEADLK recovery or reblock-style internal iteration - root-triggered ioctls) racing a concurrent hammer rebalance on the same subtree; unpriv-only reachability excluded (frontend cursors park on leaves, handled by again1 correctly). One-line fix: use pindex in the comparison. [VERIFIED pass2: oindex-vs-pindex confusion line-exact; needs mirror-reblock ioctl race; fix compile-validated]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3110 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | β | 801 B | view raw | |
| README.md | β | 430 B | β raw | |
| VERDICT.md | β | 1.8 KB | β raw | |
| build.sh | β | 163 B | view raw | |
| run.sh | β | 88 B | view raw | |
| manifest.json | β | 462 B | view raw | |
| verdict.json | β | 1.5 KB | view raw | |
| fix.diff | β | 801 B | view raw | |
| env.txt | β | 272 B | view raw |
DF-3110 -- verification pack
Finding: hammer_cursor_moved_element again2 leaf fixup uses oindex instead of pindex
Status: not_reproduced / impact corruption / confidence likely
What was run
n/a (analysis + compile validation via combined fixed kernel)
How to reproduce
n/a
Expected
n/a
See VERDICT.md for the full narrative and verdict.json for the persisted machine verdict. Artifacts: fix.diff.
DF-3110 verification
Kernel: DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64
Bottom line
Not dynamically reproducible: the defect requires a tracked cursor parked on the internal parent node (mirror-read EDEADLK recovery or reblock-style internal iteration, both root-triggered ioctls) racing a concurrent rebalance that moves element zero of a child node. Static confirmation is line-exact: in the again2 loop of hammer_cursor_moved_element the leaf fixup compares cursor->leaf against ondisk->elms[oindex].leaf after ondisk was reassigned to oparent->ondisk, while cursors in this loop sit at oparent index pindex (oindex is always 0 on this path, so any pindex != 0 fails to match and cursor->leaf keeps pointing into the dropped oparent element array). Fix swaps the comparison to pindex; compile-validated in the combined fixed-kernel build.
Kernel references
Exploit chain
(none: availability/correctness finding; no privilege chain)
Fix
one-line: use pindex in the again2 leaf comparison
See fix.diff in this directory (git-apply-able against the read-only sys/ tree; applied only inside the disposable verification guest).
Reproduction
build: n/a (analysis + compile validation via combined fixed kernel)
run: n/a
expected: n/a
Notes on the PoC (what changed vs the finding sketch)
(none)
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md source walk at hammer_cursor.c:1040-1077; fix.diff; kbuild.log (combined build compiles the fix)
Verified recommended fix
one-line: use pindex in the again2 leaf comparison
Verdict
Not dynamically reproducible: the defect requires a tracked cursor parked on the internal parent node (mirror-read EDEADLK recovery or reblock-style internal iteration, both root-triggered ioctls) racing a concurrent rebalance that moves element zero of a child node. Static confirmation is line-exact: in the again2 loop of hammer_cursor_moved_element the leaf fixup compares cursor->leaf against ondisk->elms[oindex].leaf after ondisk was reassigned to oparent->ondisk, while cursors in this loop sit at oparent index pindex (oindex is always 0 on this path, so any pindex != 0 fails to match and cursor->leaf keeps pointing into the dropped oparent element array). Fix swaps the comparison to pindex; compile-validated in the combined fixed-kernel build.
No comments yet.