Target-controlled buffer offset in scsi_decap gives malicious iSCSI target an arbitrary-offset kernel heap write
Summary
_read_data() dispatches every inbound SCSI Data-In PDU to scsi_decap() which copies target-supplied data segment into csio->data_ptr+ntohl(rcmd->bo) without ever validating rcmd->bo (Data-Data-Offset) plus data length falls inside initiators SCSI read buffer. Malicious/compromised iSCSI target can write attacker-controlled bytes at attacker-controlled offset past end of kernel heap buffer. iscsi_subr.c:566 only checks ntohl(cmd->edtlen)>=pq->pdu.ds_len (chunk fits expected transfer length) never checks placement. offset=ntohl(rcmd->bo) and dp=csio->data_ptr+offset computed from rcmd->bo 32-bit field fully controlled by target. i_mbufcopy memcpy len bytes from target mbuf into dp. Any offset>0 with offset+len>edtlen OOB write. offset can be negative (int conversion of 0x80000000) for underflow write before buffer. Attacker: iSCSI target no mutual auth required by RFC default. Impact: heap-write primitive privilege escalation or remote DoS panic.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2458 · 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| mtarget2458.c | trigger-source | malicious iSCSI target: login + bad-bo Data-In | 10.2 KB | view raw |
| build.sh | build-script | cc -o mtarget2458 mtarget2458.c | 63 B | view raw |
| run.sh | run-script | mtarget2458 + iscontrol login->FFP->CAM INQUIRY | 1.1 KB | view raw |
| panic.txt | panic-signature | write fault in memcpy (scsi_decap i_mbufcopy) | 1.4 KB | view raw |
| dmesg.txt | dmesg | patched: scsi_decap bad data-in dropped | 99 B | view raw |
| env.txt | environment | uname, kern.version, module sha256 | 494 B | view raw |
| fix.diff | suggested-fix | bounds-check rcmd->bo in scsi_decap | 1.1 KB | view raw |
| VERDICT.md | verdict | full analysis + escalation analysis | 7.0 KB | ↓ raw |
| manifest.json | manifest | this catalog | 2.0 KB | view raw |
DF-2458 — Target-controlled buffer offset in scsi_decap → arbitrary-offset kernel heap write
Verdict
REPRODUCED — a malicious iSCSI target obtains an attacker-controlled
write-what-where primitive against the initiator kernel: the target's
Data-In PDU supplies a 32-bit Buffer Offset (rcmd->bo) that scsi_decap()
uses directly as dp = csio->data_ptr + ntohl(bo) and then memcpy()s the
target's data segment to that address, with no bounds check. Demonstrated as
a remote kernel panic (write to an unmapped page); the realistic ceiling is
remote kernel memory corruption / RCE on a host that connects to a malicious
target. Fix validated on a rebuilt module.
Mechanism (trigger → primitive → effect)
- A privileged user runs
iscontrolagainst the malicious target. The kernel receiver starts atISCSISETSOC(iscsi.c:430), the login exchange completes (Security→Operational→FullFeature), CAM probes LUN 0, and the initiator issues a SCSI INQUIRY (a read,R=1,edtlen= alloc length). The outstanding command is placed on the held list (isc_sm.c:527-528). - The malicious target replies with a SCSI Data-In (
opcode=0x25) whoseittmatches the INQUIRY and whose Buffer Offsetrcmd->bois attacker-chosen (here0x80000000). ism_recv→_read_data(isc_sm.c:144) →i_search_hldfinds the opq →scsi_decap(iscsi_subr.c:531). The only size guard isntohl(cmd->edtlen) >= pq->pdu.ds_len(iscsi_subr.c:566) — it never checks placement:c offset = ntohl(rcmd->bo); /* iscsi_subr.c:572 -- target-controlled */ dp = csio->data_ptr + offset; /* iscsi_subr.c:573 -- NO bounds check */ i_mbufcopy(pq->mp, dp, len); /* iscsi_subr.c:574 -- memcpy to dp */i_mbufcopymemcpy()s the target's data segment (attacker content, lengthds_len) tocsio->data_ptr + bo. Withbo = 0x80000000the destination is unmapped → write page-fault → panic. The offset isint, so values with the sign bit set (≥0x80000000) also yield underflow writes before the buffer.
Evidence (panic signature, unpatched #0)
With bo = 0x80000000 (and identically with bo = 0x40000000):
panic: assertion "obj != NULL" failed in vm_object_hold_shared at vm_object.c:330 vm_object_hold_shared() vm_object_hold_shared() vm_fault() trap_pfault() trap() --- trap 000000000000000c, rip=ffffffff80bcabf6, rbp=... --- memcpy() at memcpy+0x66
and (earlier run, bo = 0x40000000):
Fatal trap 12: page fault while in kernel mode fault virtual address = 0xfffff80157b32860 fault code = supervisor write data, page not present <-- WRITE fault Stopped at memcpy+0x66: movq %rdx,(%rdi) <-- i_mbufcopy
The supervisor write data fault inside memcpy (called by i_mbufcopy from
scsi_decap) at an address = data_ptr + bo is the proof that the target's
Buffer Offset is honored with no bounds check and that this is a write.
Reproduced by mtarget2458 (login + malicious Data-In) + iscontrol (root,
drives login→FFP→CAM INQUIRY). See panic.txt, mt2458.log.
Threat model / privilege boundary
- The attacker is the iSCSI target (malicious/compromised storage server).
The victim is the kernel of any host whose
iscsi_initiatorconnects to it. iSCSI needs no mutual auth by default (RFC 3720). The bug fires in normal full-feature operation (no special setup beyond connecting + a SCSI read probe, which CAM issues automatically on LUN discovery). The network→kernel boundary is crossed. - Starting the session needs a privileged
iscontrol(/dev/iscsiis0600 root:wheel), which is the normal operational model.
Phase 6 — escalation analysis
This is a write-capable primitive (write-what-where relative to
csio->data_ptr), so Phase 6 applies. What the attacker controls:
- destination offset bo (full 32-bit, signed → underflow possible),
- written content (the Data-In data segment bytes),
- length ds_len (bounded by the negotiated MaxRecvDataSegmentLength).
What limits a clean uid=0 / RCE chain on this guest:
- The destination is csio->data_ptr + bo — a relative write. To overwrite
a specific kernel object (function pointer / struct ucred *) the attacker
must know data_ptr's absolute heap address. KASLR is off, so kernel
symbol addresses are fixed, but data_ptr is a dynamic heap allocation
whose address is not known to a remote target without an info leak or
successful heap grooming (the target can influence slab churn only indirectly,
via the I/O it serves). The attacker is a network peer and has no local
process on the victim, so the usual "spray/fork/pipe to fix the layout then
pivot to userspace shellcode" local-escalation recipe is not directly
applicable; RCE would require (a) a heap info-leak to pin data_ptr, then
(b) a groomed adjacent victim object, then (c) redirecting a function pointer
to shellcode staged in a kernel mbuf the attacker controls.
- This is therefore reported at its demonstrated impact: remote kernel
panic / DoS, with remote-kernel-RCE as the realistic (but not demonstrated
in this PoC) ceiling. It is correctly rated Critical as a remote
attacker-controlled kernel write primitive; the unpriv→uid=0 framing does
not fit (there is no local unprivileged actor — the attacker is the network
peer, the victim is the kernel).
PoC changes
mtarget2458.c(new): malicious iSCSI target implementing the Login exchange (Security→Operational→FFP, permissive agreements) and replying to the first SCSI command with a Data-In carrying an attacker-chosenbo.fix.diff(new): bounds-checkrcmd->boinscsi_decap.
Fix (fix.diff)
In scsi_decap (iscsi_subr.c:572), before computing dp, require the
placement to lie entirely inside the expected transfer buffer:
offset >= 0 && offset + len <= edtlen; otherwise xdebug + drop the PDU
(the command stays outstanding for a valid completion). Supersedes the
finding markdown's proposal (which identified the unchecked rcmd->bo); this
implements the exact bounds check at the faulting site.
A sibling instance of the same unchecked-bo pattern exists in so_recv's
douio path (isc_soc.c:452, iov->iov_base = csio->data_ptr + ntohl(rcmd->bo))
which is reached only when sp->douio is enabled (default off); the same
bounds check should be applied there. This fix targets the cited/reproduced
scsi_decap path.
Fix validation (rebuilt single module)
- Baseline (unpatched
#0): malicious Data-Inbo=0x80000000→panic: assertion obj!=NULL in vm_object_hold_sharedviamemcpy+0x66(write fault) (panic.txt). Same withbo=0x40000000. - Patched module (sha256
aadc9875...): same malicious Data-In → bounds check fires, guest stays up, dmesg: ">>> scsi_decap: 0] bad data-in offset=-2147483648 len=16 edtlen=36 - dropping". No panic, no da device (the malicious target never served valid data, as expected).
Fix verification
fixedVALIDATED: mtarget2458(iscontrol) PANICS on unpatched #0 module ('panic: assertion obj!=NULL' write fault in memcpy from i_mbufcopy/scsi_decap) and does NOT panic on single-fix module (same bad-bo Data-In caught: dmesg '>>> scsi_decap: 0] bad data-in offset=-2147483648 len=16 edtlen=36 - dropping', guest stays up) => fix closes write path.
baseline: panic: assertion obj!=NULL in vm_object_hold_shared via memcpy+0x66 (supervisor WRITE data). patched: '>>> scsi_decap: 0] bad data-in offset=-2147483648 len=16 edtlen=36 - dropping' -- no panic, guest up.
Confirmed kernel references
Detail
Exploit chain
Write-what-where primitive relative to csio->data_ptr (attacker controls bo offset + data-segment content + ds_len). Demonstrated impact: remote kernel panic (DoS) via write to unmapped page. uid=0/RCE NOT achieved: attacker is REMOTE network peer (the iSCSI target) with no local process on victim, so destination is a RELATIVE write (data_ptr + bo) and a precise function-pointer/ucred overwrite requires absolute heap address of data_ptr, which a remote attacker cannot obtain without a separate heap info-leak (KASLR off so kernel symbol addrs fixed, but heap-object addrs not). Valid Phase 6 stop for escalation (no local presence to spray/leak), with concrete next iteration: chain with heap info-leak (e.g. DF-1870 R2T OOB-read) to pin data_ptr, groom an adjacent CAM/mbuf ops vector, overwrite a function pointer to mbuf-staged shellcode.
Evidence (decisive lines)
panic: assertion 'obj != NULL' failed in vm_object_hold_shared ... vm_fault() trap_pfault() trap() --- trap 0xc rip=memcpy+0x66 --- memcpy() at memcpy+0x66 [earlier run: 'Fatal trap 12 fault virtual address=0xfffff80157b32860 fault code=supervisor write data Stopped at memcpy+0x66: movq %rdx,(%rdi)']
PoC changes
Wrote mtarget2458.c (malicious iSCSI target: permissive Login Security->Operational->FFP exchange + bad-bo Data-In reply to first SCSI command) and fix.diff (bounds-check rcmd->bo in scsi_decap). No prior PoC existed.
Verified recommended fix
In scsi_decap (iscsi_subr.c:572) require offset>=0 && offset+len<=edtlen before dp=csio->data_ptr+offset, else xdebug+drop the PDU. Supersedes finding proposal (implements exact bounds check at faulting site). Sibling unchecked-bo exists in so_recv's douio path (isc_soc.c:452, sp->douio default off). Full git-apply-able diff in findings/poc/DF-2458/fix.diff.
Verdict
REPRODUCED. scsi_decap() (iscsi_subr.c:572-574) computes dp = csio->data_ptr + ntohl(rcmd->bo) from the target-controlled Data-In Buffer Offset with NO bounds check (only guard at iscsi_subr.c:566 checks edtlen>=ds_len, never placement) and memcpy()s the target's data segment there. A malicious iSCSI target (mtarget2458.c) completes Login->FFP, then on the CAM INQUIRY probe replies with a Data-In whose bo=0x80000000; confirmed by supervisor-WRITE page fault inside memcpy+0x66 (i_mbufcopy) -> vm_fault -> 'panic: assertion obj!=NULL in vm_object_hold_shared'. This is a write-what-where primitive (attacker controls offset, content, length) relative to csio->data_ptr.
No comments yet.