DF-3739 / harness_i2c.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | /* * DF-3739 harness: radeon_atombios_i2c_init() OOB write past rdev->i2c_bus[16] * * Replicates VERBATIM the loop arithmetic of sys/dev/drm/radeon/radeon_atombios.c:169-188 * num_indices = (size - sizeof(ATOM_COMMON_TABLE_HEADER)) / sizeof(ATOM_GPIO_I2C_ASSIGMENT); * for (i = 0; i < num_indices; i++) { ... if (i2c.valid) rdev->i2c_bus[i] = radeon_i2c_create(...); } * against the real fixed kernel array rdev->i2c_bus[RADEON_MAX_I2C_BUS] (radeon.h:2440, * RADEON_MAX_I2C_BUS == 16, radeon_mode.h:96). * * size comes straight from the VBIOS GPIO_I2C_Info data table via atom_parse_data_header() * (atom.c:1366-1385) which performs NO bounds validation of the u16 size field. * * The destination is modelled as i2c_bus[16] embedded in a struct immediately followed by a * canary region (in the kernel: radeon_device.debugfs[32], .debugfs_count, .vm_manager, * .gpu_clock_mutex, ... — radeon.h:2441+). i2c.valid is modelled as BIOS-controlled * (usClkMaskRegisterIndex != 0 -> valid, radeon_atombios.c:113-116). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <stddef.h> typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; /* --- ATOM struct replicas (atombios.h:206, :3203-3222) --- */ typedef struct { u16 usStructureSize; u8 ucTableFormatRevision, ucTableContentRevision; } ATOM_COMMON_TABLE_HEADER; typedef union { u16 bfAccess; struct { u16 x:8; }; u8 ucAccess; } ATOM_I2C_ID_CONFIG_ACCESS; typedef struct { u16 usClkMaskRegisterIndex, usClkEnRegisterIndex, usClkY_RegisterIndex, usClkA_RegisterIndex; u16 usDataMaskRegisterIndex, usDataEnRegisterIndex, usDataY_RegisterIndex, usDataA_RegisterIndex; ATOM_I2C_ID_CONFIG_ACCESS sucI2cId; u8 ucClkMaskShift, ucClkEnShift, ucClkY_Shift, ucClkA_Shift; u8 ucDataMaskShift, ucDataEnShift, ucDataY_Shift, ucDataA_Shift; u8 ucReserved1, ucReserved2; } ATOM_GPIO_I2C_ASSIGMENT; #define RADEON_MAX_I2C_BUS 16 /* radeon_mode.h:96 */ /* model of struct radeon_device tail: radeon.h:2440-2442 */ struct radeon_device_model { void *pad; /* ...hyperz_filp etc. */ struct { void *p; } i2c_bus[RADEON_MAX_I2C_BUS]; /* radeon.h:2440 */ u64 canary[16]; /* models debugfs[] etc. that follow */ }; static unsigned long oob_writes, oob_bytes; #define GUARD 0xDEADBEEFCAFEF00DUL /* radeon_i2c_create() stand-in: returns non-NULL heap-ish pointer */ static void *fake_i2c_create(unsigned idx) { return (void *)(0x1000UL + idx * 0x40UL); } int main(void) { printf("sizeof(ATOM_GPIO_I2C_ASSIGMENT) = %zu (kernel expects 28)\n", sizeof(ATOM_GPIO_I2C_ASSIGMENT)); if (sizeof(ATOM_GPIO_I2C_ASSIGMENT) != 28 || sizeof(ATOM_COMMON_TABLE_HEADER) != 4) { fprintf(stderr, "layout replica mismatch\n"); return 2; } /* crafted VBIOS GPIO_I2C_Info table: self-declared size = 0x0FFC (4092) bytes. * atom_parse_data_header() returns it verbatim: no validation against image length. */ u16 bios_size_field = 4092; int num_indices = (bios_size_field - sizeof(ATOM_COMMON_TABLE_HEADER)) / sizeof(ATOM_GPIO_I2C_ASSIGMENT); /* == radeon_atombios.c:172-173 */ printf("BIOS size field = %u -> num_indices = %d (i2c_bus[%d])\n", bios_size_field, num_indices, RADEON_MAX_I2C_BUS); struct radeon_device_model rdev; memset(&rdev, 0, sizeof(rdev)); for (int i = 0; i < 16; i++) rdev.canary[i] = GUARD; /* verbatim loop body, radeon_atombios.c:176-187 */ for (int i = 0; i < num_indices; i++) { /* radeon_lookup_i2c_gpio_quirks(): no-op for this model. * i2c = radeon_get_bus_rec_for_i2c_gpio(gpio): * i2c.valid = (gpio->usClkMaskRegisterIndex != 0) [:113-116] * BIOS attacker sets it nonzero -> valid -> store performed. */ int valid = 1; /* fully BIOS-controlled */ if (valid) { /* rdev->i2c_bus[i] = radeon_i2c_create(...) [:183] */ if (i >= RADEON_MAX_I2C_BUS) { oob_writes++; oob_bytes += sizeof(void *); } if (i < RADEON_MAX_I2C_BUS + 16) /* stop before smashing our own canary base */ ((void **)&rdev.i2c_bus[0])[i] = fake_i2c_create(i); } } unsigned long smashed = 0; for (int i = 0; i < 16; i++) if (rdev.canary[i] != GUARD) smashed++; printf("kernel-store rdev->i2c_bus[i] executed for i=0..%d\n", num_indices - 1); printf("OOB pointer writes past i2c_bus[%d] : %lu (%lu bytes) into subsequent radeon_device fields\n", RADEON_MAX_I2C_BUS, oob_writes, oob_bytes); printf("canary words corrupted in this model : %lu\n", smashed); /* worst case: size = 0xFFFF */ u16 worst = 0xFFFF; int worst_n = (worst - 4) / 28; printf("worst case size=0xFFFF -> num_indices = %d -> %d OOB pointer writes (%d bytes)\n", worst_n, worst_n > 16 ? worst_n - 16 : 0, (worst_n - 16) * (int)sizeof(void *)); if (oob_writes == 0) { printf("FAIL: no OOB demonstrated\n"); return 1; } printf("REPRODUCED (harness): BIOS-count-driven write past i2c_bus[16] in radeon_device\n"); return 0; } |