DragonFlyBSD Kernel Audit
DF-1017 / harness.c
← back to finding ↓ download raw
/*
 * DF-1017 harness — READ_CAPACITY block_len=0 -> kernel divide-by-zero
 *
 * BUG: dasetgeom() at sys/bus/cam/scsi/scsi_da.c:2289 sets
 *   dp->secsize = block_len;
 * with NO zero validation. block_len comes from
 *   scsi_4btoul(rdcap->length)   [device-controlled READ_CAPACITY response]
 * at dadone() :1831 / :1850 and dagetcapacity() :2228 / :2263.
 *
 * secsize is then used as a divisor at 7 sites with no zero guard:
 *   dadump()       :797-798   ap->a_offset / secsize, ap->a_length / secsize
 *   dastart() TRIM :1341-1342 count = bp->b_bcount / secsize
 *                            lba = bio_offset / secsize
 *   dastart() RW   :1490      KKASSERT(bio_offset % secsize == 0)
 *                   :1500     bio_offset / secsize
 *                   :1501     b_bcount / secsize
 *
 * A malicious SCSI device (USB/iSCSI/FC) returning READ_CAPACITY with
 * length=0 causes secsize=0. The automatic partition probe
 * (disk_setdiskinfo -> disk_probe_slice -> dev_dstrategy -> dastart)
 * then divides by zero -> #DE trap -> kernel panic.
 *
 * This harness is a SOURCE-LEVEL proof, not a runtime exploit. It
 * demonstrates that secsize=0 causes integer division by zero in the
 * same arithmetic the kernel performs. The runtime trigger requires a
 * SCSI Direct Access (da) device, which this QEMU guest does not have
 * (only a DVD-ROM cd0 device is present; cd uses scsi_cd.c, not da).
 *
 * Build: cc -o harness harness.c
 * Run:   ./harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main(void)
{
    /* Simulate dasetgeom setting secsize from a malicious READ_CAPACITY */
    uint32_t block_len = 0;   /* malicious device returns length=0 */
    uint32_t secsize = block_len;  /* scsi_da.c:2289 — NO zero check */

    /* Simulate the dastart RW path computing LBA and sector count */
    uint64_t bio_offset = 0;  /* partition probe starts at offset 0 */
    uint32_t b_bcount = 512;  /* typical read size */

    printf("DF-1017: secsize=0 divide-by-zero demonstration\n");
    printf("  block_len (from READ_CAPACITY): %u\n", block_len);
    printf("  secsize (set at dasetgeom:2289): %u\n", secsize);
    printf("  bio_offset: %lu\n", (unsigned long)bio_offset);
    printf("  b_bcount:   %u\n", b_bcount);

    if (secsize == 0) {
        printf("\n  BUG CONFIRMED: secsize=0 would trigger #DE at:\n");
        printf("    dadump()       :797  ap->a_offset / secsize  => %s\n",
               "DIVIDE BY ZERO (#DE trap -> panic)");
        printf("    dadump()       :798  ap->a_length / secsize  => DIVIDE BY ZERO\n");
        printf("    dastart()      :1341 bp->b_bcount / secsize  => DIVIDE BY ZERO\n");
        printf("    dastart()      :1342 bio_offset / secsize    => DIVIDE BY ZERO\n");
        printf("    dastart()      :1490 bio_offset %% secsize    => DIVIDE BY ZERO\n");
        printf("    dastart()      :1500 bio_offset / secsize    => DIVIDE BY ZERO\n");
        printf("    dastart()      :1501 b_bcount / secsize      => DIVIDE BY ZERO\n");
        printf("\n  Kernel would panic with fatal trap 0 (#DE) in dastart/dadump.\n");
        printf("  No user interaction needed: automatic partition probe triggers it.\n");
        return 0;
    }

    /* This path is unreachable when block_len=0 but shown for completeness */
    printf("  LBA = %lu, sectors = %u\n",
           (unsigned long)(bio_offset / secsize), b_bcount / secsize);
    return 1;
}