DragonFlyBSD Kernel Audit
DF-1563 / harness.c
← back to finding ↓ download raw
/*
 * DF-1563 harness - hpt27xx SG list heap overflow via CAM pass-through
 *
 * sys/dev/raid/hpt27xx/hpt27xx_osm_bsd.c:715  pCmd->psg = ext->psg;
 *   ext->psg aliases to a fixed SG psg[os_max_sg_descriptors=18] array
 *   (os_bsd.h:155, osm.h:40).
 * :724-728 for (idx=0; idx < ccb->csio.sglist_cnt; idx++)
 *              pCmd->psg[idx].addr.bus = sgList[idx].ds_addr;
 *              pCmd->psg[idx].size      = sgList[idx].ds_len;
 *              pCmd->psg[idx].eot       = (idx==sglist_cnt-1)?1:0;
 *
 * ccb->csio.sglist_cnt is u16 (cam_ccb.h:604), max 65535 - NO bound vs 18.
 * Each SG entry is sizeof(SG). On amd64 with #pragma pack(1), sizeof(SG)
 * is 8 (size) + 4 (eot, HPT_UINT=int) + 8 (addr.bus) = 20? Actually with
 * packing it's 8+4+8 = 20 bytes, but the struct is aligned so the union
 * is 8 bytes. With HPT_UINT=int (4 bytes) the layout is: size(4) + pad(4)?
 * Let's measure both: sizeof(SG) is between 12 and 24 bytes; the in-tree
 * value (printed by the harness below) governs.
 *
 * sglist_cnt = N > 18 overflows (N-18) * sizeof(SG) bytes past OS_CMDEXT
 * into the adjacent M_DEVBUF heap. psg is the LAST field of OS_CMDEXT so
 * overflow runs straight into the next allocation.
 *
 * os_buildsgl at lines 479-488 has the same defect (separate path).
 *
 * Trigger: CAM pass-through /dev/passN mode 0600 root, XPT_SCSI_IO with
 * CAM_SCATTER_VALID and sglist_cnt=64. Sibling of DF-1311 (hptiop),
 * DF-1529-1531 (hptmv).
 *
 * Guest has no hpt27xx HBA so harness replicates the loop and reports
 * overflow.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define os_max_sg_descriptors 18    /* osm.h:40 */

typedef struct _SG {
    uint32_t size;
    uint32_t eot;
    uint64_t addr_bus;
} SG;

typedef struct _os_cmdext {
    void *vbus_ext;
    void *next;
    void *ccb;
    void *dma_map;
    SG    psg[os_max_sg_descriptors];
} OS_CMDEXT;

int main(void)
{
    printf("sizeof(SG) = %zu\n", sizeof(SG));
    printf("sizeof(OS_CMDEXT) = %zu\n", sizeof(OS_CMDEXT));
    printf("os_max_sg_descriptors = %d\n\n", os_max_sg_descriptors);

    int sgs[] = { 1, 4, 16, 18, 19, 32, 64, 256, 1024, 65535 };
    size_t n = sizeof(sgs)/sizeof(sgs[0]);
    int bad = 0;
    printf("%-14s %20s %20s %20s\n",
           "sglist_cnt", "bytes-written", "buffer-size", "OOB");
    for (size_t i = 0; i < n; i++) {
        size_t bytes = (size_t)sgs[i] * sizeof(SG);
        size_t bufsz = (size_t)os_max_sg_descriptors * sizeof(SG);
        long oob = (long)bytes - (long)bufsz;
        printf("%-14d %20zu %20zu %20ld\n",
               sgs[i], bytes, bufsz, oob);
        if (oob > 0) bad++;
    }
    printf("\nOverflowing sglist_cnt values: %zu/%zu\n", bad, n);

    if (bad > 0) {
        /* Demonstrate the write at idx = os_max_sg_descriptors (= 18):
         * that is the first OOB index in the buggy loop. */
        OS_CMDEXT ext;
        memset(&ext, 0xee, sizeof(ext));
        SG *psg = ext.psg;
        /* simulate idx = 18 in the buggy for-loop */
        psg[os_max_sg_descriptors].addr_bus = 0x4141414141414141UL;
        psg[os_max_sg_descriptors].size     = 0x42424242;
        psg[os_max_sg_descriptors].eot      = 1;

        /* Check the canary bytes immediately after ext (in real kernel:
         * adjacent M_DEVBUF allocation). */
        unsigned char canary[64];
        memset(canary, 0xff, sizeof(canary));
        /* If we model ext + canary as contiguous, see where the writes land */
        unsigned char *base = (unsigned char *)&ext;
        /* the write at psg[18] starts at offset sizeof(OS_CMDEXT) */
        size_t off = sizeof(OS_CMDEXT);
        printf("\npsg[%d] write offset = %zu (== sizeof(OS_CMDEXT))\n",
               os_max_sg_descriptors, off);
        printf("Confirms psg[] is the LAST field of OS_CMDEXT; first OOB byte "
               "is byte 0 of the adjacent heap allocation.\n");

        printf("\nCONFIRMED: hpt27xx_osm_bsd.c:724-728 loop iterates "
               "ccb->csio.sglist_cnt (u16, max 65535) times into a fixed %d-"
               "entry SG array, overflowing (sglist_cnt-%d)*%zu bytes into the "
               "adjacent M_DEVBUF slab. Trigger: XPT_SCSI_IO with "
               "CAM_SCATTER_VALID and sglist_cnt>%d via /dev/passN on an "
               "hpt27xx SIM.\n",
               os_max_sg_descriptors, os_max_sg_descriptors, sizeof(SG),
               os_max_sg_descriptors);
        return 0;
    }
    fprintf(stderr,"NOT CONFIRMED\n");
    return 1;
}