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

ism_stop frees session while CAM/ic_action and sysctl handlers can still dereference it (UAF)

Summary

ism_stop() tears session down in-place: calls ic_lost_target() drops from sc->isc_sess destroy_dev()s cdev uninits all mutexes i_freeopt()s options then sets sc->sessions[sp->sid]=NULL and finally kfree()s sp. CAM ic_action() reads sc->sessions[target_id] and dereferences sp with NO lock and per-session sysctl handlers hold raw sp pointer from arg1 no lifetime guarantee. Window between ic_lost_target() reporting device lost and sc->sessions[sid]=NULL during which CAM ccb or sysctl read can obtain about-to-be-freed sp and dereference it after kfree(). sysctl_ctx_free removes OIDs but does not wait for handlers already executing. [VERIFIED pass2: reproduced deterministically - parked ic_action reads attacker value 0x51515150 from freed reclaimed chunk, parked sysctl handler exports freed-chunk contents via SYSCTL_OUT as uid 1001 through world-readable OIDs, scsi_encap write path WRITES into the freed chunk and garbage reclaim variant Fatal trap 12 supervisor write at scsi_encap+0x445; xpt_freeze drain is ifdef 0d out; refcount-plus-drain-plus-sid-keyed-sysctl fix-validated - S5 guest survives, teardown drains while victims read live values; privesc closed - sysctl path read-only ints, write primitive needs privileged CAM access]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2462 Β· 31 files
FileTypeDescriptionSize
README.md β€” 7.4 KB ↓ raw
VERDICT.md β€” 9.2 KB ↓ raw
df_pre.c β€” 13.9 KB view raw
df_post.c β€” 28.3 KB view raw
gen.sh β€” 3.1 KB view raw
build.sh β€” 65 B view raw
run.sh β€” 89 B view raw
make_fixed.sh β€” 3.2 KB view raw
run_fixed.sh β€” 2.3 KB view raw
fix.diff β€” 6.8 KB view raw
build.log β€” 5.6 MB ↓ download
installkernel.log β€” 80.4 KB view raw
gen.log β€” 128 B view raw
run.log β€” 5.3 KB view raw
run.s7.log β€” 596.9 KB ↓ download
run.s5.log β€” 186 B view raw
panic.txt β€” 1.8 KB view raw
panic.s5.fresh.txt β€” 1.8 KB view raw
leak_sample.txt β€” 907 B view raw
fixed_gate.log β€” 1.8 KB view raw
fix_build.log β€” 5.6 MB ↓ download
fix_installkernel.log β€” 80.4 KB view raw
gen_fixed.log β€” 654 B view raw
run_fixed.log β€” 4.1 KB view raw
run_fixed_s5.log β€” 2.5 KB view raw
run_fixed_s7.log β€” 298.5 KB ↓ download
s6_fixed_read.log β€” 418 B view raw
serial_fixed_debug.txt β€” 2.1 KB view raw
env.txt β€” 1.6 KB view raw
verdict.json β€” 6.9 KB view raw
manifest.json β€” 2.4 KB view raw

DF-2462 PoC β€” ism_stop frees the session while CAM/ic_action and the per-session sysctl handlers can still dereference it (UAF)

  • File under test: sys/dev/disk/iscsi/initiator/isc_sm.c:724-777 (ism_stop), with the victim paths isc_cam.c:201-309 (ic_action) and isc_sm.c:630-676 (isc_dump_stats, isc_sysctl_targetName/Address).
  • Root cause: ism_stop() tears a session down IN PLACE. Between the CAM path teardown (ic_lost_target, isc_sm.c:752) and kfree(sp) (:777) the session stays reachable: sc->sessions[sp->sid] is cleared only at :772 and the per-session sysctl OIDs only at :774 (sysctl_ctx_free) β€” and neither ic_action() nor the sysctl handlers hold a lock or reference across their lookupβ†’use, so any reader that already holds sp keeps dereferencing freed memory. The xpt_freeze_devq drain that would stop in-flight CAM traffic is #if 0'd out in ic_freeze (isc_cam.c:57-63).
  • Windows (line-accurate):
  • W-CAM β€” from entry into ism_stop (the :740-744 xmitter wait loop can sleep up to hz per iteration; iscsi_cleanup does logout I/O) until sessions[sid]=NULL (:772): ic_action()'s unlocked sp = isp->sessions[target_id] (isc_cam.c:211) returns the dying session. After :772 any reader that already loaded sp still holds it (no refcount) β€” unbounded.
  • W-SYSCTL β€” the OIDs ...iscsiN.{targetname,targeaddress,stats,douio} stay registered until :774, and their handlers hold raw sp (stats: arg1 = sp) or raw &sp->opt.targetName pointers; a handler entered any time before removal dereferences sp with no lifetime guarantee. i_freeopt (:771) frees the option strings BEFORE sysctl_ctx_free (:774) removes the OIDs that resolve through them.
  • W-AGGRAVATOR β€” if sysctl_ctx_free fails under concurrent readers it returns an error that ism_stop merely logs (:774-775); the session is still kfree'd at :777 with the OIDs left REGISTERED pointing into the freed chunk (observed live on the guest: "can't re-use a leaf (stats)!" on the next session's registration while an unprivileged user hammered the OID).
  • Trigger preconditions (real driver): a configured iSCSI session being torn down (iscontrol exits / is killed β†’ iscsi_close β†’ ism_stop, iscsi.c:176; module unload β†’ iscsi_stop β†’ ism_stop, iscsi.c:851) while (a) CAM commands to the iscsi bus are in flight, or (b) ANY user reads the per-session sysctl OIDs (world-readable, no privilege needed β€” proven as uid 1001 in scenario S6).

Rig

Kernel-compiled verbatim interposition: df_pre.c + <verbatim isc_sm.c> + <verbatim isc_cam.c: ic_lost_target/_inq/_scsi_encap/ic_action> + <verbatim iscsi_subr.c: dwl/scsi_encap> + <verbatim isc_subr.c: i_freeopt> + df_post.c compiled into X86_64_GENERIC as dev/disk/iscsi/initiator/dfiscsi_harness.c (sha256-gated against the audit tree by build.sh). All interposition is #define-renames of external functions (xpt_async/xpt_free_path/xpt_done shims, isc_stop_receiver/iscsi_cleanup/i_pdu_flush/release_dev/destroy_dev shims, kfree β†’ free+reclaim-and-poison, ksnprintf/kprintf β†’ stall hooks); the driver text is byte-for-byte the gated source. Everything else is real: lockmgr, TAILQ ops, mtx_init/mtx_uninit, sysctl_ctx_free, kmalloc/kfree(M_ISCSI), i_freeopt, pdu_alloc, and the real userland sysctl path for the per-session OIDs.

Stall points (deterministic scheduling of the race): * teardown parked inside ic_lost_target (the xpt_async shim at isc_cam.c:90) β€” in-window; * CAM victim parked in ic_action between the sessions[] lookup (:211/:215) and the dereferences (:254 / _scsi_encap) β€” via the debug(4,"func_code...") kprintf at :217; * sysctl victim parked inside isc_dump_stats before touching sp β€” via the first ksnprintf (isc_sm.c:642).

Poison/reclaim: on the session kfree, the harness immediately reclaims the chunk (same size/type β€” the zone returns the same address) and fills it as an attacker-controlled object (FIXUP: valid mutexes/queues + chosen field values; FAULT: valid mutexes + garbage queue heads).

Build / run

./build.sh          # gate, push, generate TU, nativekernel, install, reboot
./run.sh            # baseline battery S0..S6, S7, then S5 (kills guest)
./make_fixed.sh     # apply fix.diff on the guest, rebuild, reboot
./run_fixed.sh      # A/B battery on the fixed kernel (S5 must survive)

Scenario map: * S0 control (live session; values are the A/B reference) * S1 in-window concurrent CAM read (teardown parked after ic_lost_target) * S2 UAF READ via verbatim ic_action (PATH_INQ reads sp->opt.maxluns from the freed chunk → cpi.max_lun) * S3 UAF READ/LEAK via verbatim isc_dump_stats (freed-chunk contents exported through SYSCTL_OUT) * S4 UAF WRITE via verbatim ic_action→scsi_encap→dwl/isc_qout/ i_nqueue_csnd (writes INTO the freed, attacker-reclaimed chunk) * S5 deterministic fault: same as S4 but the reclaim leaves garbage queue heads / lun table → kernel page fault (guest dies; baseline Fatal trap 12 ... scsi_encap+0x445, the dwl indexed write sp->target_lun[sp->target_nluns++] = lun with an attacker-controlled 32-bit index) * S6 unprivileged userland reads of the session OIDs while the teardown is parked in-window (uid 1001) * S7 natural race: unpriv userland sysctl hammer against a create/teardown loop with garbage-isc reclaim

Expected on the verbatim kernel

  • S0: PATH_INQ: status=0x1 max_lun=2, stats recv=111 sent=222 ..., clean teardown.
  • S1: S6: reads succeed against the being-torn-down session (sessions[7] still set; live values returned to uid 1001).
  • S2: *** UAF READ PROVEN *** β€” cpi.max_lun=0x51515150 (0x51515151 planted in the reclaimed chunk).
  • S3: *** UAF READ/LEAK PROVEN *** β€” planted values (flags=0x50505050, cws/cmd/exp/max/stat/itt = 0x65..-0x6a..) surface in the handler output.
  • S4: *** UAF WRITE PROVEN *** β€” ncsnd incremented, csnd.tqh_first linked, target_lun[0]/target_nluns written inside the freed chunk.
  • S5: guest dies: Fatal trap 12 ... supervisor write data ... Stopped at scsi_encap+0x445: movl %r13d,0x9c(%rbx,%rdx,4).
  • S7: no natural hit in the runs performed (probabilistic); the ctx_free-failure aggravator (stale registered OIDs pointing at the freed session) was observed live.

Expected on the fixed kernel (A/B)

  • S0 bit-identical to baseline (same status, same stats string).
  • S1: PATH_INQ: status=0x8 (CAM_DEV_NOT_THERE) during the window.
  • S2/S3/S4: teardown state after 3s: stop_done=0 free_seen=0 β€” ism_stop is HELD in the reference drain while the victim uses the session; victims observe LIVE values only; no freed chunk exists to read or corrupt while they run.
  • S5: GUEST SURVIVED (baseline: Fatal trap 12).
  • S6: the unprivileged reads surface no session data (handler returns ENODEV via the sid lookup; the sysctl tool prints no value).

Fix

fix.diff (4 files): detach sc->sessions[sid]=NULL to the top of ism_stop under sc->lock; add sp->refs + isc_sess_lookup()/isc_sess_rele(); ic_action looks up under sc->lock and takes a reference (released before returning); the per-session sysctl handlers take the sid as arg1 and resolve the session through isc_sess_lookup() (returning ENODEV once detached); ism_stop drains sp->refs before destroying the session and runs sysctl_ctx_free before i_freeopt.

VERDICT.md
↓ download raw

DF-2462 VERDICT β€” verified

Finding. ism_stop() (sys/dev/disk/iscsi/initiator/isc_sm.c:724-777) destroys a session in place while two unlocked reader populations can still hold sp: CAM's ic_action() (sp = isp->sessions[target_id], isc_cam.c:211, no lock, no refcount; dereferenced at :215/:254 and through _scsi_encap→scsi_encap→dwl/isc_qout/i_nqueue_*) and the per-session sysctl handlers (isc_dump_stats etc., raw sp as arg1; OIDs registered until :774). The session stays in sc->sessions[] until :772 and is kfree'd at :777.

Status: REPRODUCED on the QEMU guest with a kernel-compiled verbatim-interposition harness (whole isc_sm.c + the ic_lost_target/_inq/_scsi_encap/ic_action/dwl/scsi_encap/ i_freeopt functions compiled byte-for-byte from the sha256-gated audit tree; all interposition via #define renames of externals). Every primitive was demonstrated by driving the REAL code:

  • S2 β€” UAF read (CAM), certain. A victim thread parked inside verbatim ic_action between the sessions[] lookup and the sp->opt.maxluns read (the debug(4,"func_code…") call at isc_cam.c:217 compiles, through iscsivar.h:40-41, to a kprintf the harness intercepts). The teardown completed for real (ism_stop to kfree); the harness then reclaimed the freed chunk (same address β€” the M_ISCSI zone is LIFO) and planted opt.maxluns = 0x51515151. Resuming the victim: cpi.max_lun = 0x51515150 β€” the value read out of the FREED chunk was attacker-controlled. (run.log: "UAF READ PROVEN".)
  • S3 β€” UAF read/info-leak (sysctl), certain. A victim parked inside verbatim isc_dump_stats before touching sp (first ksnprintf, isc_sm.c:642); teardown completed (sysctl_ctx_free really deregistered the OIDs, kfree really freed, chunk reclaimed with planted values); the handler then formatted and exported flags=0x50505050 pdus-alloc=0 pdus-max=0 cws=1701143909 cmd=66666666 exp=67676767 max=68686868 stat=69696969 itt=6a6a6a6a β€” seven fields read from the freed chunk, surfaced through SYSCTL_OUT. This handler is exactly what an unprivileged user invokes with sysctl iscsi.iscsiN.stats.
  • S4 β€” UAF write (CAM), certain. Victim parked in ic_action with a SCSI_IO CCB; teardown freed + reclaimed the chunk (FIXUP: attacker object with valid mutexes/queues); the resumed verbatim path WROTE into the freed chunk, all observed: stats.ncsnd 0x52525252β†’0x52525253 (i_nqueue_csnd), csnd.tqh_first linked to a live pdu (TAILQ_INSERT_TAIL), target_lun[0] 0xbebebebeβ†’0 and target_nluns 0β†’1 (dwl), plus flags |= ISC_OQNOTEMPTY (isc_qout).
  • S5 β€” deterministic kernel fault, certain. Same as S4 but the reclaim models an unrelated allocation's garbage: the victim's dwl executes sp->target_lun[sp->target_nluns++] = lun with the garbage 32-bit index β†’ write at sp + 0x9c + 4*idx: Fatal trap 12, supervisor write data, page not present … Stopped at scsi_encap+0x445: movl %r13d,0x9c(%rbx,%rdx,4) β€” guest down in DDB (panic.txt, panic.s5.fresh.txt). This is a relative-write primitive with an attacker-influenced index and value (idx = reclaimed target_nluns, value = CCB lun).
  • S1/S6 β€” window reality + unprivileged reachability, certain. With the teardown parked just after ic_lost_target (the xpt_async shim = isc_cam.c:90), sc->sessions[7] is still set and the OIDs are still registered; uid 1001 (maxx) read all four session OIDs from userland and got live values. The unprivileged user can be the racing reader; only the teardown side needs the root-configured session (iscontrol dying, or the remote target/network dropping the connection β€” iscsi.c:176 / iscsi_subr.c:344-372 paths).
  • S7 β€” natural race, not hit in the runs performed (7531 and 14271 unprivileged hammer reads against ~1-2.6k teardown cycles; the exploitable preemption window inside the 10-line handler is ~Β΅s). The deterministic proofs above carry the finding. S7 did however surface the aggravator live: under concurrent readers sysctl_ctx_free FAILS, ism_stop only logs it (:774-775), and the session is kfree'd with the OIDs still registered β€” the next registration printed "can't re-use a leaf (stats)!" β€” i.e. the tree keeps OIDs whose arg1 points into freed memory until reboot.

Fix validation (A/B, certain). fix.diff (early sessions[sid]=NULL under sc->lock at the top of ism_stop; sp->refs + isc_sess_lookup/rele; ic_action lookup under the lock + reference; sid-based sysctl handlers returning ENODEV; drain before destruction; sysctl_ctx_free before i_freeopt): same harness around the PATCHED verbatim text, same battery: * S0 bit-identical to baseline (status/max_lun/stats string) β€” normal path unchanged; * S1: status=0x8 (CAM_DEV_NOT_THERE) during the window; * S2/S3/S4: teardown state after 3s: stop_done=0 free_seen=0 β€” ism_stop is HELD in the drain while the victim runs; victims observe only LIVE values (S2: max_lun=0x2; S3: the exact S0 string; S4: "no freed chunk exists to corrupt"); * S5: GUEST SURVIVED (baseline: Fatal trap 12); * S6: unprivileged reads surface no session data (handler ENODEV).

Privilege-escalation assessment (closed with facts). No unpriv→root chain is demonstrated, and none is realistic here: 1. The unprivileged reader (sysctl) reaches isc_dump_stats/ targetname handlers — a READ-only path. Demonstrated ceiling for that user: (a) leak of freed-chunk contents (S3 — with attacker reclaim this is controlled-data echo, without it stale session fields), and (b) a kernel fault when the freed chunk is reused by unrelated data (S5-class dereference of garbage sp->isc / queue heads) → local DoS from an unprivileged user racing a root-initiated teardown. Turning (a) into a useful KASLR/heap leak yields fixed-offset ints, not pointers; the strings handlers read *cp which i_freeopt NULLs, so they do not leak adjacent heap either. 2. The WRITE primitive (S4/S5: TAILQ links + indexed target_lun[] write with attacker-controlled index/value) rides the CAM path (ic_action → scsi_encap), which in the real driver is reachable only through the iscsi SIM's registered bus — issuing CCBs to it requires the da periph, i.e. root/operator access to the iscsi-backed disk, or in-flight I/O at teardown time (root-configured workload). An already-privileged actor corrupting freed memory has no escalation to gain. 3. The reclaim-controlling attacker in S4 writes into memory they already own (their own reallocated chunk); the dangerous direction — the kernel later walking attacker-forged queue heads in sp->hld/csnd — requires the session's sm thread and receiver to still run, but ism_stop stops both (:734-744) before the free. The post-free readers demonstrated here (ic_action, sysctl) only read fields and push one pdu onto a queue nobody drains. Conclusion: real, proven memory-safety violation whose realistic impact is unprivileged info-leak/DoS during legitimate teardown plus privileged-context corruption — consistent with (and arguably at the top of) the filed Medium severity; NOT a practical uid-0 vehicle. (The upstream FreeBSD initiator was rewritten wholesale (iscsi/icl) years ago; this DF driver is on the legacy list.)

Harness bugs found and fixed during verification (documented so the rig is reusable): (1) blocking inside a sysctl handler deadlocks against sysctl_ctx_free's SYSCTL_XLOCK because kern_sysctl.c:1573-1576 holds the pcpu SHARED sysctllock across the handler β€” the control interface now returns immediately and run.sh polls; (2) a single sleep channel for all gate flags livelocked concurrent waiters β€” per-flag channels now; (3) S2/S3/S4 originally ran the teardown inline, which the FIXED kernel's drain (correctly) blocks β€” teardown now runs in its own thread with a settle gap. None of these touched the driver text or the demonstrated outcomes.

Artifacts. run.log (baseline battery, kernel #4), run.s7.log, run.s5.log, panic.txt / panic.s5.fresh.txt (identical S5 fault signatures from two independent baseline batteries), leak_sample.txt (S3/S2 across runs), run_fixed.log, run_fixed_s5.log ("GUEST SURVIVED"), run_fixed_s7.log, s6_fixed_read.log, build.log / fix_build.log / fix_installkernel.log / fixed_gate.log, gen.log / gen_fixed.log, serial_fixed_debug.txt, env.txt, df_pre.c/df_post.c/gen.sh, build.sh/run.sh/make_fixed.sh/ run_fixed.sh, fix.diff.

Bottom line: the UAF is real, reachable, and mechanically proven on both victim populations (CAM read+write, sysctl read/leak), it kills the guest when the freed chunk is reused, the proposed fix.diff closes every demonstrated window while keeping the normal path bit-identical, and the escalation ceiling is leak/DoS — no unpriv→root chain exists on this path.

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-2462

Confirmed kernel references

Detail

Exploit chain

Unprivileged user (any uid) polls sysctl iscsi.iscsiN.stats / targetname while the root-configured session is torn down (iscontrol exit, kill, connection loss). If the handler is entered before sysctl_ctx_free (isc_sm.c:774) and runs past kfree (:777), it reads the freed chunk: stale-session info leak, or controlled-data echo if the chunk was reclaimed; if the reclaim holds unrelated data (iscs garbage, queue heads) the read faults - unprivileged kernel panic (deterministic analogue proven as S5 via the CAM victim: Fatal trap 12 write at scsi_encap+0x445 from dwl sp->target_lun[sp->target_nluns++]=lun with attacker-influenced index). The write primitive (S4: ncsnd/csnd links/target_lun table inside the freed chunk) requires issuing CAM CCBs to the iscsi bus, i.e. privileged disk access in the real driver. No uid-0 chain.

Evidence (decisive lines)

run.log lines DF2462[S2]/[S3]/[S4] PROVEN; panic.s5.fresh.txt (Fatal trap 12, Stopped at scsi_encap+0x445, movl %r13d,0x9c(%rbx,%rdx,4)); leak_sample.txt (planted values in two independent baseline runs); run_fixed.log (teardown state stop_done=0 free_seen=0, live values); run_fixed_s5.log (GUEST SURVIVED); s6_fixed_read.log (uid 1001 reads empty on fixed vs live values on baseline run.log); fixed_gate.log (patched-tree sha256s)

PoC changes

Evidence pack authored from scratch (no seed). Harness iterations: (1) fixed a self-deadlock - blocking inside the control sysctl handler pins the pcpu SHARED sysctllock (kern_sysctl.c:1573) against sysctl_ctx_free SYSCTL_XLOCK; control now returns immediately with a busy flag; (2) fixed a gate livelock - all waiters slept on one channel and wakeup-ping-ponged, burning the iteration budget in ms; per-flag channels; (3) moved S2/S3/S4 teardown into its own thread so the FIXED kernel drain (correctly) blocking it does not stall the victim release; (4) -Werror fixes (redundant-decls ordering, unused shims). None touched the verbatim driver text or the demonstrated outcomes.

Verified recommended fix

ism_stop: NULL sc->sessions[sid] under sc->lock FIRST; add sp->refs drained before destruction; ic_action + per-session sysctl handlers look the session up under sc->lock with a reference (handlers keyed by sid, ENODEV after detach); run sysctl_ctx_free before i_freeopt.

Verdict

Reproduced with kernel-compiled verbatim interposition on the QEMU guest (whole isc_sm.c plus the ic_action/_inq/_scsi_encap/ic_lost_target/dwl/scsi_encap/i_freeopt functions compiled byte-for-byte from the sha256-gated tree; interposition only via define renames). ism_stop tears the session down in place while ic_action reads sessions[target_id] with no lock or refcount (isc_cam.c:211) and the per-session sysctl handlers hold raw sp as arg1 until sysctl_ctx_free at isc_sm.c:774. Demonstrated: UAF READ via ic_action with attacker-planted 0x51515151 surfacing as cpi.max_lun=0x51515150 after the real kfree+reclaim (S2); UAF READ/leak via isc_dump_stats exporting seven planted freed-chunk fields through SYSCTL_OUT (S3), the same handler unprivileged users invoke; UAF WRITE via scsi_encap-isc_qout-i_nqueue_csnd and dwl writing stats.ncsnd, csnd.tqh_first, target_lun[0]/target_nluns into the freed attacker-reclaimed chunk (S4); deterministic kernel fault when the chunk holds unrelated-allocation garbage - Fatal trap 12 supervisor write at scsi_encap+0x445, the dwl indexed write sp->target_lun[sp->target_nluns++]=lun with an attacker-influenced 32-bit index - guest down in DDB (S5); and unprivileged (uid 1001) reads of the session OIDs succeeding mid-teardown (S6), so the unpriv user can be the racing reader while the session teardown itself is root-driven. Natural race S7 was not hit in the hammer runs performed (microsecond window) but the ctx_free-failure aggravator was observed live: OIDs left registered pointing at the freed chunk. fix.diff (early sessions[sid]=NULL under sc->lock, sp->refs with isc_sess_lookup/rele, locked ic_action lookup, sid-based sysctl handlers, drain before destroy, ctx_free before i_freeopt) validated A/B on the same harness: S0 bit-identical, S1 CAM_DEV_NOT_THERE, S2/S3/S4 victims see live values only while the drain holds the teardown (stop_done=0 free_seen=0 after 3s), S5 GUEST SURVIVED, S6 returns no data. Privesc closed with facts: the unprivileged path is read-only (leak of fixed-offset ints, not pointers; the string handlers read a pointer i_freeopt NULLs), the write primitive rides the CAM path which requires privileged access to the iscsi bus, and the attacker-reclaim direction writes into memory the attacker already owns; realistic ceiling is unprivileged info-leak/DoS during a root-initiated teardown plus privileged-context corruption - no unpriv-to-root chain on this path.