⬢ 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 65 B view raw
run.sh run-script mtarget + iscontrol + camcontrol sense dump 89 B 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.9 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/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 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

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.