โฌข DragonFlyBSD Kernel Audit
DF-1510 / harness.c
โ† back to finding โ†“ download raw
/*
 * DF-1510 harness โ€” advansys adwcam autosense OOB read/write
 *
 * sys/dev/disk/advansys/adwcam.c:383-384  acb->queue.sense_len =
 *           MIN(csio->sense_len, sizeof(acb->sense_data)=32);
 *           ^ firmware-bound value correctly clamped to 32 bytes
 *
 * But csio->sense_len is NOT updated, so at completion:
 *      :1321-1322 bcopy(&acb->sense_data, &ccb->csio.sense_data,
 *                        ccb->csio.sense_len);   <-- raw user value (up to 255)
 *
 * Both source (acb->sense_data) and dest (ccb->csio.sense_data) are 32-byte
 * struct scsi_sense_data. With sense_len=255 the bcopy over-reads 223 bytes
 * of the ACB past sense_data (acb->links, acb->queue.* โ€” kernel heap
 * pointers โ€” leaked back to the user via ccb->csio.sense_data tail) and
 * over-writes the ccb->csio fields after sense_data (msg_ptr, resid,
 * cdb_io, tag_id, init_id).
 *
 * csio->sense_len is fully attacker-controlled via the CAM pass-through
 * /dev/passN + CAMIOCOMMAND path. The guest has no AdvanSys HBA and no
 * /dev/passN, so this is a harness proof of the genuine MIN/bcopy length
 * mismatch.
 */

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

#define SENSE_DATA_SIZE 32           /* sizeof(struct scsi_sense_data) */

struct acb {
    uint8_t  sense_data[SENSE_DATA_SIZE];
    /* The 223 bytes following sense_data in the real struct acb hold
     * kernel pointers (links.sle_next, queue.* bus addresses). We model
     * them as a poison pattern to make the leak visible. */
    uint8_t  kernel_residue[223];
};

struct csio {
    uint8_t  sense_data[SENSE_DATA_SIZE];   /* dest */
    uint8_t  sense_len;                      /* user-controlled */
    uint8_t  cdb_len;
    uint16_t sglist_cnt;
    uint8_t  scsi_status;
    uint8_t  sense_resid;
    uint32_t resid;
    uint64_t cdb_io;
    uint8_t *msg_ptr;     /* kernel pointer โ€” over-written if bcopy is big */
    uint16_t msg_len;
    uint8_t  tag_action;
    uint32_t tag_id;
    uint32_t init_id;
};

/* Replicate the MIN() at adwcam.c:383-384. */
static uint8_t clamp_for_firmware(uint8_t sense_len)
{
    return (uint8_t)(sense_len < SENSE_DATA_SIZE ? sense_len : SENSE_DATA_SIZE);
}

/* Replicate the bcopy at adwcam.c:1321-1322 using the RAW sense_len. */
static void buggy_completion_bcopy(struct acb *acb, struct csio *csio)
{
    /* The bug: bcopy uses csio->sense_len, not acb->queue.sense_len. */
    uint8_t n = csio->sense_len;
    uint8_t *src = acb->sense_data;
    uint8_t *dst = csio->sense_data;
    for (uint8_t i = 0; i < n; i++) dst[i] = src[i];
}

int main(void)
{
    int bad = 0;
    uint8_t lens[] = { 0, 18, 32, 33, 64, 128, 200, 255 };
    size_t n = sizeof(lens)/sizeof(lens[0]);

    printf("%-12s %18s %28s\n", "sense_len", "fw-clamped len", "OOB read+write bytes");

    for (size_t i = 0; i < n; i++) {
        struct acb  acb;
        struct csio csio;
        memset(&acb.sense_data, 0xDD, SENSE_DATA_SIZE);
        memset(&acb.kernel_residue, 0xCC, sizeof(acb.kernel_residue));
        memset(&csio, 0, sizeof(csio));
        csio.sense_len = lens[i];

        /* firmware clamps (line 383-384) โ€” this is what the HBA will write */
        uint8_t fw_len = clamp_for_firmware(csio.sense_len);

        /* HBA fills only the first fw_len bytes of acb->sense_data with real
         * sense bytes; remaining bytes of acb->sense_data stay 0xDD poison
         * (M_ZERO or stale), then acb->kernel_residue is real kernel data. */

        /* Completion uses the raw csio->sense_len (line 1321-1322). */
        buggy_completion_bcopy(&acb, &csio);

        int oob = (int)csio.sense_len - SENSE_DATA_SIZE;
        printf("%-12u %18u %28d\n", (unsigned)lens[i], (unsigned)fw_len, oob);
        if (oob > 0) {
            bad++;
            /* The bytes [SENSE_DATA_SIZE .. sense_len-1] of csio.sense_data
             * are now copied from acb.kernel_residue โ€” i.e. kernel heap. */
            int leaked = oob < (int)sizeof(csio) - SENSE_DATA_SIZE
                            ? oob
                            : (int)sizeof(csio) - SENSE_DATA_SIZE;
            printf("  -> %d bytes of ACB kernel-residue leaked into user ccb; "
                   "%d bytes overwrote csio fields past sense_data\n",
                   oob, leaked);
        }
    }
    printf("\nBuggy sense_lens: %zu/%zu\n", bad, n);

    if (bad > 0) {
        printf("\nCONFIRMED: adwcam at adwcam.c:1321-1322 bcopy()s up to %d "
               "bytes from a %d-byte source (acb->sense_data) into a %d-byte "
               "dest (ccb->csio.sense_data) when sense_len=255 โ€” leaking up "
               "to 223 bytes of kernel heap (ACB pointers) and corrupting "
               "ccb->csio.msg_ptr/resid/cdb_io/tag_id/init_id. Trigger: "
               "operator-group CAMIOCOMMAND on /dev/passN against a CHECK-"
               "CONDITION target on an AdvanSys bus.\n",
               255, SENSE_DATA_SIZE, SENSE_DATA_SIZE);
        return 0;
    }
    fprintf(stderr,"NOT CONFIRMED\n");
    return 1;
}