DragonFlyBSD Kernel Audit
DF-1732 / harness.c
← back to finding ↓ download raw
/*
 * DF-1732 - vinumio.c heap buffer overflow: unbounded config-line copy
 *           in vinum_scandisk reads attacker-controlled disk data into
 *           a 2 KB stack-of-heap config_line buffer.
 *
 * Vulnerable code (sys/dev/raid/vinum/vinumio.c):
 *   761  config_text  = Malloc(MAXCONFIG*2);          // 131072 bytes
 *   763  config_line  = Malloc(MAXCONFIGLINE*2);      // 2048 bytes
 *   775  error = read_drive(drive, config_text, MAXCONFIG*2, ...);
 *   796  for (cptr = config_text; *cptr != '\\0';)
 *   799-800  for (eptr = config_line; (*cptr != '\\n') && (*cptr != '\\0');)
 *              *eptr++ = *cptr++;                     // NO bound on eptr
 *
 * A disk image whose config area contains a single line > 2047 bytes
 * (no \\n, no \\0 within 2 KB) overflows config_line by up to ~129 KB
 * of attacker-controlled bytes. The outer loop at 796 itself walks
 * config_text without bounds; a config area with no NUL at all keeps
 * reading into adjacent heap.
 *
 * Trigger: boot with vinum-driven-by-crafted-disk-image, or
 * VINUM_CREATE / vinum(8) read of a malicious devnode. vinum(8)
 * operations require root (geom class), so the unprivileged path
 * is via an admin-supplied mountable attacker image.
 *
 * This harness simulates the inner copy loop and shows the overflow.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXCONFIGLINE 1024
#define MAXCONFIG     65536

int main(void)
{
    char *config_text = malloc(MAXCONFIG * 2);   /* 131072, like vinumio.c:761 */
    char *config_line = malloc(MAXCONFIGLINE * 2); /* 2048, like vinumio.c:763 */
    if (!config_text || !config_line) { perror("malloc"); return 1; }

    /* attacker-controlled "disk": a single long line, no newline, no NUL
     * inside MAXCONFIG*2 bytes. */
    memset(config_text, 'A', MAXCONFIG * 2);

    /* emulate vinumio.c:796-800 */
    char *cptr = config_text;
    char *eptr = config_line;
    char *eline_end = config_line + MAXCONFIGLINE * 2;
    unsigned long copied = 0;
    int overflowed = 0;
    while (*cptr != '\n' && *cptr != '\0') {
        *eptr++ = *cptr++;
        copied++;
        if (eptr >= eline_end) { overflowed = 1; break; }
    }

    printf("=== DF-1732 vinum_scandisk config-line overflow harness ===\n");
    printf("config_line buffer = %d bytes (MAXCONFIGLINE*2)\n", MAXCONFIGLINE*2);
    printf("attacker disk line length = up to %d bytes (MAXCONFIG*2)\n", MAXCONFIG*2);
    printf("\n");

    /* Show what the vulnerable code would do (no bound on eptr).  We do
     * NOT actually perform the OOB write (it would crash this harness
     * exactly as it corrupts kernel heap); we just count how many bytes
     * the buggy loop would copy, given the read_drive read size. */
    char *vuln_eptr = config_line;   /* not actually written through */
    unsigned long vuln_copied = 0;
    unsigned long cap = (unsigned long)MAXCONFIG * 2;   /* config_text size */
    for (char *p = config_text; *p != '\n' && *p != '\0' && vuln_copied < cap; ) {
        (void)vuln_eptr;     /* in the kernel this advances past config_line */
        p++;
        vuln_copied++;
    }
    printf("Vulnerable vinumio.c:799-800 copies %lu bytes into a %d-byte buffer\n",
           vuln_copied, MAXCONFIGLINE*2);
    printf("Overflow amount: %lu bytes past buffer end (attacker-controlled)\n",
           (long)(vuln_copied > (unsigned)(MAXCONFIGLINE*2) ? vuln_copied - (unsigned)(MAXCONFIGLINE*2) : 0));

    printf("\n");
    if (vuln_copied > (unsigned)(MAXCONFIGLINE*2)) {
        printf("VERDICT: BUG CONFIRMED. Inner copy loop has no upper bound on\n"
               "        eptr; a single non-newline-terminated line in the\n"
               "        vinum config area overflows the 2 KB config_line by\n"
               "        ~129 KB. Root-only trigger (vinum geom), but full\n"
               "        heap corruption with attacker-shaped content.\n");
        free(config_text); free(config_line);
        return 0;
    }
    (void)overflowed; (void)copied;
    printf("VERDICT: not reproduced.\n");
    free(config_text); free(config_line);
    return 1;
}