DragonFlyBSD Kernel Audit
← triage · dashboard
DF-2468

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2468 · 9 files
FileTypeDescriptionSize
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
VERDICT.md verdict full narrative + fix validation
↓ download 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 status 0x02 (CHECK CONDITION). CAM_AUTOSNS_VALID is 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 the kmalloc(sense_len) allocation when sense_len <= scsi->sense_len (a 2-byte heap OOB read).

Evidence

  1. Primitive fires under the malicious target. mtarget2468 completes iSCSI login (Security→Operational→FFP), serves valid INQUIRY (so the LUN registers) and returns CHECK CONDITION with sense_len=252 but DSLength=10 on 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 ran getSenseData with sense_len=252 > DSLength=10. mtarget2468.log showed cmdsn > 19000 commands processed through the receiver.
  2. 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,0 proving the CAM_AUTOSNS_VALID path delivers the (crafted + leaked) sense to a userspace pass-through CCB.
  3. 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 (see panic.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 with sense_len=252 / DSLength=10 on non-probe commands. Tracks CmdSN to keep the window open.
  • sense2468.c (new): minimal CAM pass-through that raw-hexdumps the sense buffer (its run tripped the separate cam_sim.c:104 panic).
  • 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/initiatoriscsi_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_testable
baseline reproduced→ patch + rebuild →patched clean

not_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.
↓ fix.diffn/a (module-only compile check; live byte comparison 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.