DF-1827 / harness.c
/* * 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; } |