โฌข DragonFlyBSD Kernel Audit
DF-1250 / harness.c
โ† back to finding โ†“ download raw
/*
 * DF-1250 harness โ€” radeon_combios_check_hardcoded_edid() BIOS over-read.
 *
 * sys/dev/drm/radeon/radeon_combios.c:364-388:
 *   raw  = rdev->bios + edid_info;                 (line 373)
 *   size = EDID_LENGTH * (raw[0x7e] + 1);          (line 374)  -> up to 128*256 = 32768
 *   edid = kmalloc(size, ...);                     (line 375)
 *   memcpy(edid, raw, size);                       (line 379)
 *   if (!drm_edid_is_valid(edid)) ...              (line 381)  -- checked AFTER memcpy
 *
 * `rdev->bios` can be a small allocation: radeon_read_platform_bios() reads
 * bios[2]*512 bytes; a malicious ROM can declare a 512-byte BIOS image while
 * the hardcoded-EDID table's raw[0x7e] byte == 0xff, making size = 32768.
 * The memcpy then reads 32768 bytes from a 512-byte allocation: ~32 KB of
 * adjacent kernel heap is leaked into `edid`, which the caller may later copy
 * to userspace. drm_edid_is_valid is checked only AFTER the over-read, so the
 * leak happens unconditionally.
 *
 * Kernel trigger requires an AMD/ATI radeon GPU whose (malicious) Video BIOS
 * contains a crafted COMBIOS hardcoded-EDID table โ€” absent from this guest.
 * This harness replicates the size computation + memcpy and proves the
 * over-read math against a 512-byte BIOS slab.
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

#define EDID_LENGTH 128

int main(void) {
    /* Model rdev->bios as a 512-byte allocation (platform BIOS path). */
    size_t bios_alloc = 512;
    uint8_t *bios = calloc(1, bios_alloc);
    /* The hardcoded-EDID table lives at some offset inside the BIOS; model
     * edid_info near the start so raw[0x7e] is still inside the alloc. */
    int edid_info = 0x40;
    uint8_t *raw = bios + edid_info;

    /* Malicious ROM sets the extension-count byte raw[0x7e] = 0xff. */
    raw[0x7e] = 0xff;

    /* radeon_combios.c:374 โ€” size = EDID_LENGTH * (raw[0x7e] + 1) */
    int size = EDID_LENGTH * (raw[0x7e] + 1);
    printf("bios allocation = %zu bytes\n", bios_alloc);
    printf("edid_info = 0x%x, raw = bios+%d\n", edid_info, edid_info);
    printf("raw[0x7e] = 0x%02x\n", raw[0x7e]);
    printf("computed size (radeon_combios.c:374) = %d bytes\n", size);

    /* How far past the BIOS allocation would the memcpy read? */
    size_t copy_end = (size_t)edid_info + (size_t)size;
    long overread = (long)copy_end - (long)bios_alloc;
    printf("memcpy reads raw[0 .. %d) = bios[%d .. %zu)\n", size, edid_info, copy_end);
    printf("over-read past BIOS allocation: %ld bytes of adjacent kernel heap\n", overread);

    if (overread > 0) {
        printf("\nPRIMITIVE CONFIRMED: memcpy(edid, raw, %d) reads %ld bytes past a %zu-byte BIOS slab.\n",
               size, overread, bios_alloc);
        printf("drm_edid_is_valid is checked only AFTER (radeon_combios.c:381) -> leak is unconditional. Bug is REAL.\n");
        free(bios);
        return 0;
    }
    printf("\nUNEXPECTED: no over-read\n");
    free(bios);
    return 1;
}