DragonFlyBSD Kernel Audit
DF-1827 / harness.c
← back to finding ↓ download raw
/*
 * DF-1827 - iscsi isc_soc.c heap overflow in isc_sendPDU MH_ALIGN misuse
 *           leaves no room for AHS or header digest.
 *
 * Vulnerable code (sys/dev/disk/iscsi/initiator/isc_soc.c):
 *   111  MGETHDR(mh, M_WAITOK, MT_DATA);
 *   112  mh->m_len = mh->m_pkthdr.len = sizeof(union ipdu_u);    // 48
 *   114  MH_ALIGN(mh, sizeof(union ipdu_u));                     // shift to END
 *   115  bcopy(&pp->ipdu, mh->m_data, sizeof(union ipdu_u));     // BHS at end
 *   ...
 *   123  // XXX Assert: (mh->m_pkthdr.len + pp->ahs_len) < MHLEN   <-- WRONG bound
 *   125  bcopy(pp->ahs, (mh->m_data + mh->m_len), pp->ahs_len);  // OVERFLOW
 *   ...
 *   136  // XXX Assert: (mh->m_pkthdr.len + 4) < MHLEN            <-- WRONG bound
 *   138  bcopy(&pp->hdr_dig, (mh->m_data + mh->m_len), sizeof(int)); // +4 OVERFLOW
 *
 * MH_ALIGN(mh, 48) shifts m_data so the BHS sits at the END of m_pktdat,
 * leaving rounddown2(MHLEN-48, sizeof(long)) trailing bytes (small).
 * The XXX assert at 123/136 checks the wrong bound (mh->m_pkthdr.len +
 * ahs_len < MHLEN ignores the MH_ALIGN shift). ahs_len from the
 * ISCSISEND ioctl can be up to ~65500; the header digest is 4 bytes
 * every PDU. Both bcopy()s write past m_pktdat into the next heap object.
 *
 * This harness reproduces the layout: shows that with BHS at end of
 * m_pktdat (length MHLEN) the +ahs_len copy goes OOB.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

/* DragonFly amd64 MHLEN value (computed from MSZ, struct m_hdr, struct pkthdr).
 * The exact constant is platform but the bug is independent of it. */
#define MHLEN_DEMO 160

long rounddown2(long x, long u) { return x - (x % u); }

int main(void)
{
    long mhlen = MHLEN_DEMO;
    long bhs = 48;                       /* sizeof(union ipdu_u) */
    long ahs_len = 64;                   /* attacker-supplied via ISCSISEND */
    long digest = 4;                     /* CRC32C header digest */

    /* MH_ALIGN: m_data = &m_pktdat[rounddown2(MHLEN - bhs, sizeof(long))] */
    long md_off = rounddown2(mhlen - bhs, sizeof(long));

    char m_pktdat[mhlen];
    memset(m_pktdat, 0xaa, sizeof(m_pktdat));

    printf("=== DF-1827 isc_sendPDU MH_ALIGN overflow harness ===\n");
    printf("MHLEN (this demo)        = %ld\n", mhlen);
    printf("sizeof(union ipdu_u)     = %ld (BHS)\n", bhs);
    printf("MH_ALIGN -> m_data at    = m_pktdat[%ld] (BHS at end of buf)\n", md_off);
    printf("trailing bytes after BHS = %ld\n", mhlen - md_off - bhs);
    printf("attacker ahs_len         = %ld\n", ahs_len);
    printf("header digest            = %ld\n", digest);
    printf("\n");

    /* isc_soc.c:125 bcopy(ahs, m_data+m_len, ahs_len) where m_len=bhs */
    long ahs_dst   = md_off + bhs;             /* = mhlen exactly */
    long ahs_end   = ahs_dst + ahs_len;
    long ahs_oob   = ahs_end - mhlen;
    printf("isc_soc.c:125 bcopy(ahs, m_data+%ld, %ld):\n", bhs, ahs_len);
    printf("  writes m_pktdat[%ld..%ld), buffer ends at %ld -> %ld bytes OOB\n",
           ahs_dst, ahs_end, mhlen, ahs_oob > 0 ? ahs_oob : 0);

    /* isc_soc.c:138 bcopy(&hdr_dig, m_data+m_len, 4) where m_len=bhs+ahs_len */
    long dig_dst   = md_off + bhs + ahs_len;
    long dig_end   = dig_dst + digest;
    long dig_oob   = dig_end - mhlen;
    printf("isc_soc.c:138 bcopy(hdr_dig, m_data+%ld, %ld):\n", bhs+ahs_len, digest);
    printf("  writes m_pktdat[%ld..%ld), buffer ends at %ld -> %ld bytes OOB\n",
           dig_dst, dig_end, mhlen, dig_oob > 0 ? dig_oob : 0);
    printf("\n");

    if (ahs_oob > 0 || dig_oob > 0) {
        printf("VERDICT: BUG CONFIRMED. MH_ALIGN(mh, BHS) leaves no room for\n"
               "        AHS or header digest. Both isc_soc.c:125 and :138\n"
               "        bcopy()s write past m_pktdat into the next heap object.\n"
               "        ahs_len is attacker-supplied via ISCSISEND; the 4-byte\n"
               "        digest overflow happens every PDU. Fix: MH_ALIGN(mh,\n"
               "        hdrlen) where hdrlen = BHS + ahs_len + digest, and\n"
               "        reject hdrlen > MHLEN.\n");
        return 0;
    }
    printf("VERDICT: not reproduced.\n");
    return 1;
}