⬢ DragonFlyBSD Kernel Audit
DF-4378 / chk_drm.c
← back to finding ↓ download raw
/*
 * DF-4378 reachability probe — drm_drv.c minor/devnode teardown leak.
 *
 * The bug is entirely kernel-side: drm_sysfs_minor_alloc() (drm_sysfs.c:97)
 * make_dev()s "dri/cardN" and throws the cdev away; drm_minor_free()
 * (drm_drv.c:176) kfree()s the drm_minor without destroy_dev()ing the node
 * and without freeing minor->kdev (put_device is #if 0).  Nothing in
 * sys/dev/drm ever calls destroy_dev.  Each driver attach/detach (or module
 * load/unload) cycle therefore leaks one cdev + one struct device per minor
 * and leaves a stale /dev/dri/cardN whose si_drv1 dangles at the freed
 * drm_device.
 *
 * This probe documents the userland-observable symptom set:
 *   1. presence/absence of /dev/dri on this machine,
 *   2. whether a DRM driver is even loaded (kldstat),
 *   3. (on a machine with a GPU) whether /dev/dri/cardN entries persist
 *      after `kldunload` of the display driver — the stale-node symptom.
 *
 * Expected on the audit QEMU guest: no /dev/dri, no drm module -> exit 2.
 */
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int
main(void)
{
	DIR *d;
	struct dirent *de;
	int fd, found = 0;

	d = opendir("/dev/dri");
	if (d == NULL) {
		printf("DF-4378: /dev/dri absent: no DRM driver attached on "
		    "this machine\n");
		return (2);
	}
	while ((de = readdir(d)) != NULL) {
		if (de->d_name[0] == '.')
			continue;
		printf("DF-4378: /dev/dri/%s present\n", de->d_name);
		found++;
	}
	closedir(d);
	if (!found)
		return (2);
	fd = open("/dev/dri/card0", O_RDWR);
	if (fd < 0)
		printf("DF-4378: card0 open: %s\n", strerror(errno));
	else {
		printf("DF-4378: card0 opened; leak check = kldunload/"
		    "kldload cycle then compare `vmstat -m` M_DRM lines and "
		    "ls /dev/dri\n");
		close(fd);
	}
	return (0);
}