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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3110 Β· 9 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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_testable
↓ fix.diffper-fix-DF-3110

Confirmed 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.