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

TOCTOU in i_create_session: sc->sessions[n] assigned outside the search lock

Summary

i_create_session() searches for lowest unused session slot under sc->lock then writes sc->sessions[n]=sp AFTER releasing lock. Two concurrent ISCSISETSES callers can both observe same slot n as free both insert into sc->isc_sess both bump nsess both attempt make_dev(n) for same minor. Result: leaked/orphaned session and make_dev collision on duplicate minor. sc->sessions/isc_sess/TAILQ/nsess bookkeeping inconsistent. make_dev on duplicate minor can fail caller gets half-built session sp->dev==NULL subsequent NULL deref in ism_stop destroy_dev(dev). Root only. [VERIFIED pass2: reproduced - deterministic in-window interleave hands both ISCSISETSES callers sid 0, double make_dev, sessions count 1 vs TAILQ 2 with orphan; root-only 0600; lock fix A/B sid 0/1]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2467 Β· 8 files
FileTypeDescriptionSize
README.md β€” 1014 B ↓ raw
VERDICT.md β€” 2.5 KB ↓ raw
build.sh β€” 99 B view raw
run.log β€” 663 B view raw
env.txt β€” 963 B view raw
fix.diff β€” 553 B view raw
manifest.json β€” 794 B view raw
verdict.json β€” 2.6 KB view raw

DF-2467 -- TOCTOU in i_create_session: slot published outside the lock

Verification used the shared rig tree at /root/rigs on the audit guest (see manifest). The rig for this finding: dfv_iscsi (rig.c = harness, gen.sh = verbatim extractor, dfv_*_extract.h = generated production text). The decisive outputs below were captured live on the guest during the 2026-10-24 verification session; full session logs are summarized per run.

Build

sh build.sh            # extracts verbatim driver text + builds the KLD

Run (guest, root)

kldload ./dfv_rig.ko

kldload dfv_iscsi.ko; sysctl kern.dfv_iscsi_run=1; sysctl -n kern.dfv_iscsi_status

Expected

run.log: A sid=0 B sid=0 (SAME SLOT), sessions used=1 vs TAILQ 2, nsess=2, make_dev unit=0 twice; fixed run: A sid=0 B sid=1, RESULT=clean.

Fix validation

fix.diff moves the slot publish under the lock; A/B on the same rig shows distinct sids and consistent bookkeeping.

Verdict: reproduced / impact=corruption (see verdict.json, VERDICT.md)

VERDICT.md
↓ download raw

DF-2467 verdict -- TOCTOU in i_create_session: slot published outside the lock

status: reproduced reproduced: 1 impact: corruption confidence: certain attempts: 2 runtime_sec: 900

The verbatim i_create_session (iscsi.c:609-656) searches the lowest unused session slot under sc->lock but executes sc->sessions[n] = sp at :639 AFTER lockmgr(LK_RELEASE) at :635. A deterministic in-window interleave (thread A parked by the interposed lockmgr_release right after the unlock - semantically identical to preemption at that instruction) let a second ISCSISETSES caller run to completion: both were handed sid=0, make_dev(iscsi unit=0) was called twice, sessions[] ended with 1 reachable entry while the TAILQ and nsess counted 2 - the overwritten session is orphaned/leaked with a duplicate dev minor, and a make_dev failure on the duplicate leaves sp->dev NULL for the ism_stop destroy_dev arm. Root-only (control node 0600 root:wheel, iscsi.c:762). Fix (publish the slot under the lock) validated A/B on the same rig: B gets sid=1, sessions[]/TAILQ/nsess consistent.

Evidence (decisive lines)

DF2467[stalled-race]: sessions[] used=1 TAILQ entries=2 nsess=2 dup_minors=1 DF2467: sessions[0] = sp sid=0 OCCUPIED DF2467: sessions on TAILQ but unreachable via sessions[] (orphaned/leaked): 1 DF2467[stalled-race]: RESULT=RACE OBSERVED (duplicate slot/minor, inconsistent sessions[]/isc_sess/nsess bookkeeping) log: make_dev(iscsi%d unit=0) make_dev(iscsi%d unit=0) DF2467: A was told sid=0, B was told sid=0 (SAME SLOT handed to both) --- fixed source A/B --- DF2467[stalled-race]: sessions[] used=2 TAILQ entries=2 nsess=2 dup_minors=0 DF2467[stalled-race]: RESULT=clean (slot assignment serialized with the search) DF2467: A was told sid=0, B was told sid=1

Exploit chain / privesc assessment

steps: root ioctl ISCSISETSES x2 racing - deterministic interleave on verbatim function; impact is bookkeeping corruption, orphaned sessions and duplicate minors; privesc ceiling: none beyond the filed root-only confusion/leak (no unpriv path - /dev/iscsi is 0600).

Fix + A/B

fix.diff moves the slot publish under the lock; A/B on the same rig shows distinct sids and consistent bookkeeping.

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

fixed
↓ fix.diffper-fix-DF-2467

Confirmed kernel references

Detail

Exploit chain

steps: root ioctl ISCSISETSES x2 racing - deterministic interleave on verbatim function; impact is bookkeeping corruption, orphaned sessions and duplicate minors; privesc ceiling: none beyond the filed root-only confusion/leak (no unpriv path - /dev/iscsi is 0600).

Evidence (decisive lines)

run.log: A sid=0 B sid=0 (SAME SLOT), sessions used=1 vs TAILQ 2, nsess=2, make_dev unit=0 twice; fixed run: A sid=0 B sid=1, RESULT=clean.

PoC changes

none - rig worked first after the lockmgr interposition was routed through the out-of-line lockmgr_release (lockmgr is a static inline in sys/lock.h).

Verified recommended fix

move sc->sessions[n] = sp under sc->lock before LK_RELEASE

Verdict

The verbatim i_create_session (iscsi.c:609-656) searches the lowest unused session slot under sc->lock but executes sc->sessions[n] = sp at :639 AFTER lockmgr(LK_RELEASE) at :635. A deterministic in-window interleave (thread A parked by the interposed lockmgr_release right after the unlock - semantically identical to preemption at that instruction) let a second ISCSISETSES caller run to completion: both were handed sid=0, make_dev(iscsi unit=0) was called twice, sessions[] ended with 1 reachable entry while the TAILQ and nsess counted 2 - the overwritten session is orphaned/leaked with a duplicate dev minor, and a make_dev failure on the duplicate leaves sp->dev NULL for the ism_stop destroy_dev arm. Root-only (control node 0600 root:wheel, iscsi.c:762). Fix (publish the slot under the lock) validated A/B on the same rig: B gets sid=1, sessions[]/TAILQ/nsess consistent.