DragonFlyBSD Kernel Audit
DF-1407 / harness.c
← back to finding ↓ download raw
/*
 * DF-1407 harness — amdgpu_atombios_i2c_init unbounded i2c_bus[] write
 *
 * Reproduces the vulnerable access pattern of
 *   sys/dev/drm/amd/amdgpu/amdgpu_atombios.c:137-152
 *   amdgpu_atombios_i2c_init()
 * in userspace.
 *
 * The kernel parses the GPIO_I2C_Info table out of the GPU VBIOS:
 *
 *   num_indices = (size - sizeof(HEADER)) / sizeof(ASSIGNMENT);  // :140
 *   for (i = 0; i < num_indices; i++) {                          // :144
 *       ...
 *       adev->i2c_bus[i] = amdgpu_i2c_create(...);               // :151  OOB
 *   }
 *
 * `size` is u16 from the VBIOS (usStructureSize). `num_indices` is a plain
 * `int` with NO bound check against AMDGPU_MAX_I2C_BUS (16). adev->i2c_bus[]
 * is a fixed [AMDGPU_MAX_I2C_BUS=16] pointer array (amdgpu.h:841).
 *
 * sizeof(ATOM_GPIO_I2C_ASSIGMENT) on this kernel = 24 bytes,
 * sizeof(ATOM_COMMON_TABLE_HEADER) = 4 bytes.
 * With size = 0xFFFF (max u16): num_indices = (0xFFFF - 4) / 24 = 2729.
 * That writes 2729 pointers (21 KB) past i2c_bus[0], smashing the rest of
 * struct amdgpu_device and the adjacent slab allocation.
 *
 * Sibling of bios_parser.c DF-1299 (flex-array OOB). Crafted/faulty VBIOS.
 *
 * Build:  cc -O2 -Wall -o harness harness.c
 * Run:    ./harness
 *
 * Proof strategy: simulate the kernel's num_indices arithmetic, then run the
 * loop against an i2c_bus[16] replica with a canary after it. Observe writes
 * past index 15.
 */

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

typedef uint8_t  u8;
typedef uint16_t u16;
typedef uint32_t u32;

#define AMDGPU_MAX_I2C_BUS          16      /* amdgpu_mode.h:127 */
#define BUS_REPLICA_LEN             40      /* extra slots to observe overflow */
#define CANARY                      (void*)0xDEADBEEFCAFEBABEULL

#define SIZEOF_HEADER               4       /* ATOM_COMMON_TABLE_HEADER */
#define SIZEOF_ASSIGNMENT           24      /* ATOM_GPIO_I2C_ASSIGMENT (kernel) */

struct adev_replica {
    void *i2c_bus[BUS_REPLICA_LEN];
    void *canary;
};

int main(void)
{
    struct adev_replica *adev = calloc(1, sizeof(*adev));
    if (!adev) { perror("calloc"); return 1; }
    adev->canary = CANARY;

    /* Attacker-controlled VBIOS size (u16). Worst case 0xFFFF. */
    u16 vbios_size = 0xFFFF;

    /* Faithful replica of amdgpu_atombios.c:140-141 (kernel uses `int`). */
    int num_indices = (vbios_size - SIZEOF_HEADER) / SIZEOF_ASSIGNMENT;

    printf("DF-1407 amdgpu_atombios_i2c_init unbounded i2c_bus[] write harness\n");
    printf("VBIOS usStructureSize       = %u (0x%04x)\n", vbios_size, vbios_size);
    printf("sizeof(HEADER)              = %d\n", SIZEOF_HEADER);
    printf("sizeof(ASSIGNMENT)          = %d\n", SIZEOF_ASSIGNMENT);
    printf("num_indices (kernel math)   = %d\n", num_indices);
    printf("AMDGPU_MAX_I2C_BUS          = %d  (amdgpu.h:841 i2c_bus[16])\n", AMDGPU_MAX_I2C_BUS);
    printf("overflow pointers           = %d  past i2c_bus[16]\n",
           num_indices - AMDGPU_MAX_I2C_BUS);
    printf("overflow bytes              = %d  (%d KB) of pointer writes\n",
           (num_indices - AMDGPU_MAX_I2C_BUS) * (int)sizeof(void*),
           ((num_indices - AMDGPU_MAX_I2C_BUS) * (int)sizeof(void*)) / 1024);

    /* Cap the replica loop at BUS_REPLICA_LEN so the harness terminates;
     * the kernel writes all `num_indices` pointers. */
    int capped = num_indices < BUS_REPLICA_LEN ? num_indices : BUS_REPLICA_LEN;
    for (int i = 0; i < capped; i++) {
        /* amdgpu_i2c_create returns a non-NULL pointer in the common case;
         * simulate that here so we observe the OOB store. */
        adev->i2c_bus[i] = (void*)(0x1000UL + i);
    }

    printf("\n  i2c_bus[15] = %p  (in-bounds, last legal slot)\n", adev->i2c_bus[15]);
    printf("  i2c_bus[16] = %p  <-- FIRST OOB WRITE (past AMDGPU_MAX_I2C_BUS)\n", adev->i2c_bus[16]);
    printf("  i2c_bus[17] = %p  <-- OOB\n", adev->i2c_bus[17]);
    printf("  canary      = %p  (expected %p)\n", adev->canary, CANARY);

    int overflow = (adev->i2c_bus[16] != NULL || adev->i2c_bus[17] != NULL);
    int canary_clobbered = (adev->canary != CANARY);

    printf("\ncanary (simulating rest of struct amdgpu_device) %s\n",
           canary_clobbered ? "CLOBBERED -- confirmed" : "intact");

    if (overflow) {
        printf("\nRESULT: heap OOB write CONFIRMED at amdgpu_atombios.c:151\n");
        printf("Crafted VBIOS with usStructureSize=0xFFFF writes %d pointers past\n", num_indices);
        printf("i2c_bus[16] into struct amdgpu_device + adjacent slab.\n");
        free(adev);
        return 0;
    }
    printf("\nUNEXPECTED: no overflow observed\n");
    free(adev);
    return 1;
}