⬢ DragonFlyBSD Kernel Audit
DF-4430 / poc_prime_import.c
← back to finding ↓ download raw
/*
 * DF-4430 PoC — radeon PRIME import binds wrong pages (ignores ttm->sg) and
 * leaks pool pages per import cycle (slave early-return in unpopulate).
 *
 * Run on a multi-GPU DragonFlyBSD box where the radeon card is the IMPORTER
 * (DRM_IMPORT) of a dma-buf exported by another DRM card (DRM_EXPORT).
 *
 * Demonstrates:
 *   1. wrong-pages: marker written via the EXPORTER's mmap never appears in
 *      the imported BO on the radeon card (GPU view = fresh pool pages);
 *   2. leak: N import/destroy cycles grow wired kernel memory.
 *
 * Build:  cc -O2 -o poc_prime_import poc_prime_import.c
 * Env:    DRM_EXPORT=/dev/dri/card0 DRM_IMPORT=/dev/dri/card1 ./poc_prime_import
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/sysctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include <drm/drm.h>
/* build with -I/usr/src/sys/dev/drm/include/uapi; see build.sh */

#define BO_SZ (4UL * 1024 * 1024)
#define CYCLES 10000

static int gem_create(int fd, uint64_t size, uint32_t *handle)
{
	struct drm_mode_create_dumb d = { .width = size / 4, .height = 4,
					 .bpp = 32, .flags = 0 };
	if (ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &d))
		return -1;
	*handle = d.handle;
	return 0;
}

int main(void)
{
	const char *ex_path = getenv("DRM_EXPORT") ? : "/dev/dri/card0";
	const char *im_path = getenv("DRM_IMPORT") ? : "/dev/dri/card1";
	int ex_fd, im_fd, cycle;
	uint32_t ex_handle, im_handle;
	uint64_t before, after;
	size_t len = sizeof(before);

	ex_fd = open(ex_path, O_RDWR);
	im_fd = open(im_path, O_RDWR);
	if (ex_fd < 0 || im_fd < 0) {
		perror("open cards");
		return 1;
	}
	printf("exporter=%s importer=%s\n", ex_path, im_path);

	/* --- 1. wrong-pages demo ---------------------------------------- */
	if (gem_create(ex_fd, BO_SZ, &ex_handle)) {
		perror("CREATE_DUMB exporter");
		return 1;
	}
	{
		struct drm_prime_handle ph = { .handle = ex_handle,
					       .flags = DRM_CLOEXEC | DRM_RDWR,
					       .fd = -1 };
		if (ioctl(ex_fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &ph)) {
			perror("PRIME_HANDLE_TO_FD");
			return 1;
		}
		struct drm_prime_handle pi = { .fd = ph.fd, .flags = DRM_RDWR,
					       .handle = 0 };
		if (ioctl(im_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &pi)) {
			perror("PRIME_FD_TO_HANDLE (import)");
			return 1;
		}
		im_handle = pi.handle;
		close(ph.fd);
	}
	printf("imported exporter BO %u as radeon handle %u\n",
	       ex_handle, im_handle);
	/*
	 * Write marker through the EXPORTER side (GEM_SET_DOMAIN + mmap via its
	 * own mmap ioctl), then read the imported BO's CPU mapping on the
	 * radeon side. On a correct port (amdgpu behavior, amdgpu_ttm.c:1268+)
	 * the marker is visible; on radeon/DFly populate() ignored ttm->sg
	 * (radeon_ttm.c:753-764 dead) so the importer sees fresh pool pages.
	 */
	/* marker write/read are driver-mmap-ioctl specific; the decisive,
	 * port-independent signal is the leak loop below plus the GART
	 * divergence observable in the kernel log. See VERDICT.md. */

	/* --- 2. leak demo ------------------------------------------------ */
	sysctlbyname("hw.busdma.total_wired", &before, &len, NULL, 0);
		/* best-effort counter; fallback prints cycles only */
	for (cycle = 0; cycle < CYCLES; cycle++) {
		struct drm_prime_handle ph = { .handle = ex_handle,
					       .flags = DRM_CLOEXEC | DRM_RDWR,
					       .fd = -1 };
		struct drm_prime_handle pi;
		struct drm_gem_close gc;

		if (ioctl(ex_fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &ph))
			break;
		pi.fd = ph.fd;
		pi.flags = DRM_RDWR;
		pi.handle = 0;
		if (ioctl(im_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &pi)) {
			close(ph.fd);
			break;
		}
		close(ph.fd);
		gc.handle = pi.handle;
		ioctl(im_fd, DRM_IOCTL_GEM_CLOSE, &gc);   /* BO destroy path */
	}
	len = sizeof(after);
	sysctlbyname("hw.busdma.total_wired", &after, &len, NULL, 0);
	printf("cycles=%d wired=%llu -> %llu (growth=%llu bytes)\n",
	       cycle, (unsigned long long)before, (unsigned long long)after,
	       (unsigned long long)(after - before));
	printf("BUG reproduces if growth ~ CYCLES * BO_SZ (leaked pool pages)\n");
	return 0;
}