β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2459

Re-queued NOP-IN PDU in _nop_in causes bcopy(NULL) panic in isc_sendPDU

Summary

When target sends NOP-In with itt=0xFFFFFFFF and ttt!=0xFFFFFFFF _nop_in() mutates received PDU into NOP-OUT and re-queues via isc_qout(). Received PDUs have pp->ahs==NULL (so_recv sets pp->ahs_len from BHS AHSLength AHS bytes in pq->mp never in pp->ahs). If target sets AHSLength!=0 in NOP-In pp->ahs_len nonzero while pp->ahs NULL and when proc_out() runs re-queued PDU through isc_sendPDU() bcopy(pp->ahs ...) bcopy from NULL panics kernel. Remote DoS on default config. Attacker: iSCSI target post-Login full-feature phase. NOP-In/T logic normal iSCSI keep-alive so target can send without suspicion.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2459 Β· 9 files
FileTypeDescriptionSize
mtarget.c trigger-source malicious target: NOP-IN itt=ffffffff ttt=1 AHSLength=1 3.6 KB view raw
idrv.c trigger-source minimal initiator driver (ISCSISETSOC pre-login) 2.8 KB view raw
build.sh build-script cc -o mtarget mtarget.c; cc -o idrv idrv.c 52 B view raw
run.sh run-script launch mtarget nopin + idrv 582 B view raw
panic.txt panic-signature Fatal trap 12 va=0x0 memmove+0xb5 642 B view raw
env.txt environment uname, kern.version, cc, module sha256 482 B view raw
fix.diff suggested-fix in _nop_in re-queue: clear stale ahs/ds lens, force i_prepPDU 1.1 KB view raw
VERDICT.md verdict full analysis 3.7 KB ↓ raw
manifest.json manifest this catalog 1.9 KB view raw
VERDICT.md verdict full analysis
↓ download raw

DF-2459 β€” Re-queued NOP-IN PDU in _nop_in causes bcopy(NULL) in isc_sendPDU

Verdict

REPRODUCED β€” remote kernel panic (DoS) from a malicious iSCSI target. The bug is a NULL-pointer read (page fault @0x0) inside bcopy/memmove, not a write primitive; no escalation chain applies (Phase 6 read-only hard blocker). Fix validated on a rebuilt module.

Mechanism (trigger β†’ primitive β†’ effect)

  1. Same precondition as DF-2460: a privileged user passes a connected iSCSI socket to the kernel via ISCSISETSOC, starting the receiver thread before login (iscsi.c:430).
  2. The malicious target sends a NOP-IN PDU (opcode=0x20) with itt = 0xffffffff and ttt != 0xffffffff, and a non-zero AHSLength (e.g. AHSLength=1 β‡’ 4 bytes of AHS).
  3. so_recv() (isc_soc.c:387-389) sets pp->ahs_len = bhs->AHSLength*4 and reads the AHS bytes into the mbuf chain pq->mp β€” it never sets pp->ahs, which stays NULL (from pdu_alloc's memset).
  4. _nop_in() (isc_sm.c:195) takes the itt==-1 && ttt!=-1 branch, mutates the BHS into a NOP-OUT (isc_sm.c:217-227) and re-queues the same PDU via isc_qout() (isc_sm.c:228). Because pq->len is non-zero (set by so_recv), isc_qout does not call i_prepPDU (isc_sm.c:309), so the stale pp->ahs_len survives.
  5. proc_out() dequeues the NOP-OUT and calls isc_sendPDU() (isc_sm.c:530), whose active (mbuf) implementation does: c if(pp->ahs_len) { /* isc_soc.c:120 -- true */ bcopy(pp->ahs, ..., pp->ahs_len); /* isc_soc.c:125 -- pp->ahs == NULL */ bcopy(NULL, ...) faults reading address 0x0.

Evidence (panic signature, unpatched #0 kernel)

Fatal trap 12: page fault while in kernel mode
fault virtual address   = 0x0
fault code      = supervisor read data, page not present
instruction pointer = 0x8:0xffffffff80bca9b5
...
Stopped at      memmove+0xb5:   movl    (%rsi),%edx
db>

memmove (called by bcopy) with %rsi==0 is the bcopy(pp->ahs==NULL,...). Reproduced by mtarget nopin + idrv.

Threat model / privilege boundary

Identical to DF-2460: the attacker is the iSCSI target; the bug fires pre-login, no auth needed; the privileged ISCSISETSOC (root, /dev/iscsi is 0600 root:wheel) is the only thing the victim must do (normal iscontrol usage).

Why no escalation (Phase 6 hard blocker)

NULL-pointer read (bcopy from NULL). No attacker-controlled write destination, no content control, no object reuse β€” the read-only / no-write hard blocker applies; the only effect is a kernel panic (DoS).

PoC changes

  • mtarget.c, idrv.c: same harness as DF-2460; nopin mode sends the 52-byte NOP-IN (opcode=0x20, F=1, AHSLength=1, itt=0xffffffff, ttt=1 + 4 AHS bytes).
  • fix.diff (new): in _nop_in's re-queue branch, clear the stale AHS/data length fields (pp->ahs_len=0, pp->ds_len=0, pointers NULL) and force i_prepPDU to re-prepare the outgoing NOP-OUT (pq->len=0).

Fix (fix.diff)

Root cause: a received PDU is re-queued for sending with pp->ahs_len set from the wire but pp->ahs == NULL (AHS lives in pq->mp). The outgoing NOP-OUT keep-alive carries no AHS/data, so the fix drops the stale length fields and lets isc_qout→i_prepPDU recompute pq->len/BHS lengths for the NOP-OUT. Supersedes the finding markdown's proposal (which identified the bcopy(pp->ahs) line; this fixes the root cause at the re-queue site).

Fix validation (rebuilt single module)

  • Baseline (unpatched #0): PoC panics β€” Fatal trap 12 va=0x0 Stopped at memmove+0xb5 (panic.txt).
  • Patched module (sha256 f71cd799...): PoC runs clean β€” idrv reports "still alive after 6s (no panic)", guest up, no panic in serial log.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: mtarget(nopin)+idrv PANICS on unpatched #0 module ('Fatal trap 12 va=0x0 Stopped at memmove+0xb5') and does NOT panic on single-fix module (idrv reports 'still alive after 6s (no panic)', guest up).

baseline: Fatal trap 12 va=0x0 supervisor read data, Stopped at memmove+0xb5 (bcopy NULL). patched: 'idrv: still alive after 6s (no panic)' -- no panic, guest up.
↓ fix.diffkernel kern.version unchanged (#0); fix validated by rebuilding loadable iscsi_initiator.ko module and kldload-ing it; patched module sha256 f71cd7993f416862ef5839e618d840f0802147a9cc8503338f75aca3f0a90dd7

Confirmed kernel references

Detail

Exploit chain

none (NULL-pointer READ -- bcopy from NULL). Read-only NULL-deref, not write primitive, no escalation chain derivable (Phase 6 read-only hard blocker). Realistic impact ceiling: remote kernel panic / DoS from malicious iSCSI target.

Evidence (decisive lines)

Fatal trap 12: page fault while in kernel mode | fault virtual address = 0x0 | fault code = supervisor read data, page not present | Stopped at memmove+0xb5: movl (%rsi),%edx [bcopy(pp->ahs==NULL,...) in isc_sendPDU]

PoC changes

Wrote mtarget.c (nopin mode: 52-byte NOP-IN itt=0xffffffff ttt=1 AHSLength=1 + 4 AHS bytes) and idrv.c (minimal initiator: open /dev/iscsi, ISCSISETSES, socket+connect, ISCSISETSOC -- starts kernel receiver PRE-login so injected NOP-IN processed). Plus fix.diff (clear stale ahs_len/ds_len in _nop_in re-queue).

Verified recommended fix

In _nop_in's re-queue branch (isc_sm.c:225-228), zero pp->ahs_len/pp->ahs/pp->ds_len/pp->ds and set pq->len=0 so isc_qout re-runs i_prepPDU for outgoing NOP-OUT keep-alive, preventing isc_sendPDU's bcopy(pp->ahs==NULL,...). Supersedes finding proposal (fixes root cause at re-queue site). Full git-apply-able diff in findings/poc/DF-2459/fix.diff.

Verdict

REPRODUCED. _nop_in (isc_sm.c:212-228) re-queues a received NOP-IN (itt=0xffffffff, ttt!=0xffffffff) as a NOP_OUT via isc_qout WITHOUT clearing pp->ahs_len, which so_recv set from wire AHSLength while pp->ahs stays NULL (received PDUs carry AHS in pq->mp, never pp->ahs). isc_sendPDU (isc_soc.c:120-125, mbuf build) then does bcopy(pp->ahs==NULL, ..., pp->ahs_len) -> page fault at 0x0. Confirmed by 'Stopped at memmove+0xb5: movl (%rsi),%edx' with fault virtual address 0x0 (NULL read in bcopy/memmove).