DF-2462 / verdict.json
{ "finding_id": "DF-2462", "status": "reproduced", "reproduced": 1, "impact": "leak", "confidence": "certain", "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.", "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": "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)", "kernel_refs": [ "sys/dev/disk/iscsi/initiator/isc_sm.c:724-777", "sys/dev/disk/iscsi/initiator/isc_sm.c:752", "sys/dev/disk/iscsi/initiator/isc_sm.c:771", "sys/dev/disk/iscsi/initiator/isc_sm.c:772", "sys/dev/disk/iscsi/initiator/isc_sm.c:774", "sys/dev/disk/iscsi/initiator/isc_sm.c:777", "sys/dev/disk/iscsi/initiator/isc_sm.c:630-653", "sys/dev/disk/iscsi/initiator/isc_cam.c:210-215", "sys/dev/disk/iscsi/initiator/isc_cam.c:254", "sys/dev/disk/iscsi/initiator/isc_cam.c:281", "sys/dev/disk/iscsi/initiator/iscsi_subr.c:441-528", "sys/dev/disk/iscsi/initiator/iscsi.c:176", "sys/dev/disk/iscsi/initiator/iscsi.c:851" ], "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.", "attempts": 5, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #4: Mon Oct 19 14:52:57 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline); #9 fixed", "runtime_sec": 10800, "guest_dirty": 0, "build_cmd": "./build.sh (verbatim gate + gen.sh + make -j6 nativekernel KERNCONF=X86_64_GENERIC + installkernel + reboot); A/B: ./make_fixed.sh", "run_cmd": "./run.sh (S0..S6,S7 then S5 last - S5 kills the baseline guest); A/B: ./run_fixed.sh", "code_hash": "6d3a2149552bc385a07706768e6a6a6b02b817540be6b9e1d56dcdd246f1a444 df_pre.c; b03a8aebb0eb2c541d0832687d7f72312421528b9d2d50e4737651888879b476 df_post.c", "notes": "Guest left clean: vm.sh reset with-src, pristine isc_sm.c sha256 ec8cb8b085556c897b69c3fe1aa150742d971c8dbf034b5ee77f2c9cab161bf0 re-verified after reset. S7 natural race not hit in two hammer runs (7531 and 14271 reads); ctx_free-failure aggravator observed (stale OIDs pointing into freed chunk, cannot re-use a leaf messages). Severity context: legacy iSCSI initiator, root-configured session, unpriv reader side.", "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.", "fix_status": "fixed", "fix_notes": "A/B on the same verbatim harness around the patched tree: S0 bit-identical; S1 CAM_DEV_NOT_THERE in-window; S2/S3/S4 drain held the teardown (stop_done=0, free_seen=0) and victims read live values only - no freed chunk existed to corrupt; S5 survived (baseline Fatal trap 12); S6 unprivileged reads surface no data." } |