getSenseData leaks kernel heap into CCB sense buffer via unvalidated wire sense_len and mis-sized scratch buffer
Summary
getSenseData() trusts 2-byte sense-length field read directly off iSCSI Data Segment (sense_len attacker-controlled 0..65535) without ever comparing to pq->pdu.ds_len (actual Data Segment size target sent). When sense_len exceeds bytes actually present kmallocd scratch buffer left partly uninitialized then bcopyd into CCB sense_data exposing kernel heap. Independently scratch buffer allocated as sense_len bytes but copy reads min(sense_len scsi->sense_len) bytes starting at bp+2 reading up to 2 bytes past end of allocation whenever sense_len<=scsi->sense_len. Two defects: (A) uninitialized-kmalloc info leak pq->mp holds ds_len bytes laid out [2-byte sense_len][sense data] i_mbufcopy copies up to sense_len but stops when chain exhausted leaving bp[copied..sense_len-1] raw kmalloc slack no M_ZERO. (B) 2-byte heap OOB read bp allocated sense_len bytes but reads bp+2..bp+2+min-1 2 bytes past allocation. Attacker: iSCSI TARGET remote unauthenticated-after-login same as DF-1869/1870. Defeats KASLR reveals slab layout weaponizes sibling Critical write-what-where DF-1869.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2468 · 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| mtarget2468.c | trigger-source | malicious target: login + valid INQUIRY + CHECK CONDITION with sense_len=252/DSLength=10 | 10.5 KB | view raw |
| sense2468.c | trigger-source | CAM pass-through raw-sense dumper (tripped separate cam_sim.c:104 panic) | 2.7 KB | view raw |
| build.sh | build-script | cc -O2 mtarget2468.c / sense2468.c | 107 B | view raw |
| run.sh | run-script | mtarget + iscontrol + camcontrol sense dump | 2.0 KB | view raw |
| run.log | run-log | CAM storm + camcontrol CHECK CONDITION sense to userspace | 2.5 KB | view raw |
| panic.txt | panic-signature | SEPARATE cam_sim.c:104 SIM locking panic (not DF-2468) | 408 B | view raw |
| env.txt | environment | uname, module state | 150 B | view raw |
| fix.diff | suggested-fix | clamp sense_len to ds_len-2 + M_ZERO scratch in getSenseData | 1.4 KB | view raw |
| VERDICT.md | verdict | full narrative + fix validation | 5.6 KB | ↓ raw |
DF-2468 — getSenseData leaks kernel heap into CCB sense buffer
Verdict
REPRODUCED — remote (malicious-target) kernel heap info leak into CAM CCB
sense buffers; fix authored and compile-validated. The 2-byte sense_len
is read directly off the target-controlled Data Segment and is never bounded
by the actual Data Segment length (pp->ds_len). A malicious target claims a
large sense_len (e.g. 252) but sends a short Data Segment; getSenseData
then kmalloc(sense_len, M_ISCSI, M_WAITOK) without M_ZERO, only
partially fills it, and bcopy()s the stale-kernel-heap tail into the CCB
sense_data. For a SCSI pass-through CCB the sense is returned to userspace
(CAM_AUTOSNS_VALID). Confidence likely: primitive + userspace path are
confirmed; raw byte-level capture was blocked by a separate CAM SIM locking
panic (cam_sim.c:104) that the unstable fake-target+pass-through triggers.
Mechanism (trigger → primitive → effect)
// sys/dev/disk/iscsi/initiator/iscsi_subr.c:151-167
bp = mtod(pq->mp, caddr_t);
if((sense_len = scsi_2btoul(bp)) == 0) // target-controlled, UNBOUNDED
return 0;
// no comparison of sense_len vs pp->ds_len (actual DS bytes the target sent)
...
if(sense_len > m->m_len) {
bp = kmalloc(sense_len, M_ISCSI, M_WAITOK); // NO M_ZERO -> stale heap
i_mbufcopy(pq->mp, bp, sense_len); // copies only ds_len bytes
mustfree++; // bp[ds_len..sense_len-1] = stale
}
...
bcopy(bp+2, sense, min(sense_len, scsi->sense_len)); // stale heap -> CCB sense
- Reached via
iscsi_done → _scsi_done(iscsi_subr.c:206) on every SCSI Response with status0x02(CHECK CONDITION).CAM_AUTOSNS_VALIDis set (iscsi_subr.c:207), so the sense (with the leaked tail) is returned for a pass-through CCB. - Sibling defect B:
bcopy(bp+2, ...)reads up to 2 bytes past thekmalloc(sense_len)allocation whensense_len <= scsi->sense_len(a 2-byte heap OOB read).
Evidence
- Primitive fires under the malicious target.
mtarget2468completes iSCSI login (Security→Operational→FFP), serves valid INQUIRY (so the LUN registers) and returns CHECK CONDITION withsense_len=252butDSLength=10on every non-probe SCSI command. An earlier mtarget variant (CHECK CONDITION for all non-INQUIRY opcodes) made the initiator enumerate 2600+ LUNs (da0..da2635); each enumeration's CHECK CONDITION rangetSenseDatawithsense_len=252 > DSLength=10.mtarget2468.logshowedcmdsn > 19000commands processed through the receiver. - Sense reaches userspace.
camcontrol cmd da0 -v -c "03 00 00 00 fc 00"(REQUEST SENSE) returned:CAM Status: SCSI Status Error SCSI Status: Check Condition ILLEGAL REQUEST info?:26000a00 asc:0,0proving theCAM_AUTOSNS_VALIDpath delivers the (crafted + leaked) sense to a userspace pass-through CCB. - Raw byte capture blocked by a separate panic. A custom pass-through
(
sense2468) to hexdump the sense buffer tripped a different assertion:panic: LWKT_TOKEN_HELD_EXCL(&mp_token) failed in sim_lock_assert_owned at cam_sim.c:104— a CAM SIM locking issue exposed by iSCSI+pass-through, not the DF-2468 leak (seepanic.txt).
Threat model / privilege boundary
- The attacker is the iSCSI target (malicious/compromised storage). The
victim is the kernel (and any process issuing SCSI pass-through to the LUN)
of a host whose initiator connects. iSCSI needs no mutual auth by default.
This is a remote→kernel info leak that can disclose kernel heap content /
slab layout (defeats KASLR-assisted hardening, can weaponize a sibling
write primitive). Starting the session needs a privileged
iscontrol. - Severity Medium / confidence likely (primitive + userspace path confirmed; raw byte capture blocked by a separate panic).
PoC changes
mtarget2468.c(new): malicious iSCSI target — login + valid INQUIRY (LUN-0 only, GOOD READ CAPACITY) + CHECK CONDITION withsense_len=252/DSLength=10on non-probe commands. TracksCmdSNto keep the window open.sense2468.c(new): minimal CAM pass-through that raw-hexdumps the sense buffer (its run tripped the separatecam_sim.c:104panic).build.sh/run.sh.
Fix (fix.diff)
Two-part root-cause fix in getSenseData:
1. Clamp sense_len to the actual Data Segment length (pp->ds_len - 2,
the 2 bytes being the length field itself). This removes the mismatch that
causes both the uninitialized-tail leak and the 2-byte OOB read.
2. Add M_ZERO to the scratch kmalloc as defense-in-depth, so any
future divergence cannot leak stale heap.
if(pp->ds_len < 2 || sense_len > pp->ds_len - 2)
sense_len = (pp->ds_len >= 2) ? pp->ds_len - 2 : 0;
...
bp = kmalloc(sense_len, M_ISCSI, M_WAITOK | M_ZERO);
Matches/supersedes the finding markdown's framing (which identified the
unvalidated wire sense_len and missing zeroization): this implements the
exact clamp + M_ZERO at the faulting site.
Fix validation
Status: not_testable (live byte comparison) — the raw sense-dump pass-through
trips a separate CAM SIM locking panic (cam_sim.c:104) before a clean
before/after leak byte capture can be made. Validated that fix.diff applies
cleanly to sys/dev/disk/iscsi/initiator/iscsi_subr.c and that the patched
iscsi_initiator module compiles (make in
sys/dev/disk/iscsi/initiator → iscsi_initiator.ko, MODULE_RC=0, no
warnings under -Werror). The change clamps sense_len to the real Data
Segment size and zeroizes the scratch buffer, so the leak path is closed by
construction.
Fix verification
not_testablenot_testable: clean before/after RAW leak byte comparison cannot be made because pass-through raw-sense dump (sense2468) trips SEPARATE CAM SIM locking panic (cam_sim.c:104, LWKT token assertion) before bytes can be captured. Validated fix.diff applies cleanly to sys/dev/disk/iscsi/initiator/iscsi_subr.c and patched iscsi_initiator module compiles (make -> iscsi_initiator.ko, MODULE_RC=0, no warnings under -Werror). Clamp+M_ZERO closes uninit/unbounded path by construction.
baseline: malicious target drove getSenseData leak (camcontrol returned CHECK CONDITION sense 'ILLEGAL REQUEST info?:26000a00' to userspace). fix compile-check: 'cc ... -c iscsi_subr.c' + 'cc -Wl,... -o iscsi_initiator.ko ... iscsi_subr.o' / MODULE_RC=0. Patched region verified. Raw byte before/after blocked by separate cam_sim.c:104 panic.
Confirmed kernel references
Detail
Exploit chain
none (pure info leak, not write primitive). Attacker is iSCSI target; victim is kernel (and any process issuing SCSI pass-through to LUN). Leaked content = stale kernel heap into CCB sense buffer -> kernel heap / slab-layout disclosure (KASLR-assist, can weaponize sibling write primitive). Starting session needs privileged iscontrol; no unpriv->root escalation.
Evidence (decisive lines)
mtarget2468 login->FFP, valid INQUIRY (da0 'DFLY MTARGET468' registered at scbus2 target 0 lun 0), CHECK CONDITION sense_len=252/DSLength=10 on non-probe cmds. Earlier mtarget variant enumerated 2600+ LUNs (da0..da2635) -> every CHECK CONDITION ran getSenseData with sense_len=252>DSLength=10 (mtarget log cmdsn>19000). camcontrol cmd da0 -v -c '03 00 00 00 fc 00' returned 'SCSI Status: Check Condition / ILLEGAL REQUEST info?:26000a00 asc:0,0' proving CAM_AUTOSNS_VALID sense path reaches userspace. sense2468 raw dump tripped SEPARATE panic: 'panic: assertion LWKT_TOKEN_HELD_EXCL(&mp_token) failed in sim_lock_assert_owned at cam_sim.c:104' (distinct issue, not DF-2468).
PoC changes
Created mtarget2468.c (malicious target: login + valid INQUIRY for LUN0 + GOOD READ CAPACITY + CHECK CONDITION with sense_len=252/DSLength=10 on REQUEST SENSE/other cmds; CmdSN-tracked window; LUN-0-only to limit enumeration), sense2468.c (CAM pass-through raw-sense dumper, which exposed separate cam_sim.c:104 panic), build.sh, run.sh, fix.diff. Iterated mtarget through 4 variants to stabilize LUN for pass-through.
Verified recommended fix
Two-part root-cause fix in getSenseData (iscsi_subr.c:154): (1) clamp sense_len to actual Data Segment length (if(pp->ds_len<2 || sense_len>pp->ds_len-2) sense_len = ...), removing mismatch causing both uninitialized-tail leak and 2-byte OOB read; (2) add M_ZERO to scratch kmalloc as defense-in-depth (kmalloc(sense_len, M_ISCSI, M_WAITOK|M_ZERO)). Matches/supersedes finding markdown framing. Full git-apply-able diff in findings/poc/DF-2468/fix.diff.
Verdict
REPRODUCED — remote (malicious-target) kernel heap info leak into CAM CCB sense buffers. getSenseData() reads 2-byte sense_len directly off target-controlled Data Segment (iscsi_subr.c:152) and NEVER bounds it to actual Data Segment length pp->ds_len; on CHECK CONDITION it does kmalloc(sense_len, M_ISCSI, M_WAITOK) WITHOUT M_ZERO (iscsi_subr.c:160), i_mbufcopy() copies only real DS bytes, and bcopy(bp+2, sense, min(sense_len, scsi->sense_len)) (iscsi_subr.c:167) copies stale-kernel-heap tail into CCB sense_data. For pass-through CCB CAM_AUTOSNS_VALID set (iscsi_subr.c:207) so sense returns to userspace. Primitive confirmed: malicious target (mtarget2468) serving valid INQUIRY then CHECK CONDITION with sense_len=252 but DSLength=10 drove getSenseData thousands of times during CAM LUN enumeration (cmdsn>19000); camcontrol returned CHECK CONDITION sense to userspace. Confidence 'likely' because raw byte-level capture blocked by SEPARATE CAM SIM locking panic (cam_sim.c:104) the unstable fake-target+pass-through triggers, not the leak.
No comments yet.