[PATCH RFC] drm/cirrus-qemu: Use actual VRAM size to prevent out-of-bounds write
"syzbot" <[email protected]>
| Newsgroups | dev.linux.lists.syzbot |
|---|---|
| Message-ID | <[email protected]> |
The `cirrus-qemu` driver previously relied on a hardcoded constant
`CIRRUS_VRAM_SIZE` (4 MB) to validate framebuffer sizes. However, during
device probe, the driver maps the VRAM using the actual size of the PCI
device's BAR0.
If a privileged user unbinds a random PCI device with a BAR0 smaller than 4
MB and binds the `cirrus-qemu` driver to it, the mapped VRAM will be
smaller than 4 MB. Because the validation checks still used the hardcoded 4
MB size, the driver would allow the creation of a framebuffer larger than
the actually mapped VRAM.
When the DRM device is closed, `drm_release()` triggers a full atomic
commit to restore the fbdev mode. This calls
`cirrus_primary_plane_helper_atomic_update()`, which uses `drm_fb_memcpy()`
to copy the framebuffer into the mapped VRAM. Since the mapped VRAM is
smaller than the framebuffer, `memcpy_toio()` writes past the end of the
mapped I/O memory, resulting in a supervisor write page fault:
BUG: unable to handle page fault for address: ffffc900033dd000
#PF: supervisor write access in kernel mode
#PF: error_code(0x0002) - not-present page
...
RIP: 0010:rep_movs arch/x86/lib/iomem.c:13 [inline]
RIP: 0010:string_memcpy_toio arch/x86/lib/iomem.c:64 [inline]
RIP: 0010:memcpy_toio+0x7c/0xe0 arch/x86/lib/iomem.c:110
...
Call Trace:
<TASK>
iosys_map_memcpy_to include/linux/iosys-map.h:285 [inline]
drm_fb_memcpy+0x325/0x5d0 drivers/gpu/drm/drm_format_helper.c:442
cirrus_primary_plane_helper_atomic_update+0x98a/0xb00
drivers/gpu/drm/tiny/cirrus-qemu.c:358
drm_atomic_helper_commit_planes+0x626/0xea0
drivers/gpu/drm/drm_atomic_helper.c:3038
drm_atomic_helper_commit_tail+0x60/0x510
drivers/gpu/drm/drm_atomic_helper.c:1989
commit_tail+0x2b1/0x3c0 drivers/gpu/drm/drm_atomic_helper.c:2074
drm_atomic_helper_commit+0xa77/0xb10
drivers/gpu/drm/drm_atomic_helper.c:2312
...
To fix this, remove the hardcoded `CIRRUS_VRAM_SIZE` and dynamically track
the actual VRAM size in `struct cirrus_device`. Initialize this size from
the PCI resource length during probe, ensuring it is non-zero. Update the
validation hooks `cirrus_primary_plane_helper_atomic_check()` and
`cirrus_mode_config_mode_valid()` to use the dynamically tracked VRAM size,
ensuring that framebuffers strictly fit within the mapped memory.
Fixes: ab3e023b1b4c ("drm/cirrus: rewrite and modernize driver.")
Assisted-by: Gemini:gemini-3.6-flash Gemini:gemini-3.1-pro-preview syzbot
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=2442951a6abb004df963
Link: https://syzkaller.appspot.com/ai_job?id=0995d2c9-3eea-4945-84e7-ab3d2acd4f21
To: "David Airlie" <[email protected]>
To: "Dave Airlie" <[email protected]>
To: <[email protected]>
To: "Gerd Hoffmann" <[email protected]>
To: "Maarten Lankhorst" <[email protected]>
To: "Maxime Ripard" <[email protected]>
To: "Simona Vetter" <[email protected]>
To: "Thomas Zimmermann" <[email protected]>
To: <[email protected]>
Cc: "Jani Nikula" <[email protected]>
Cc: <[email protected]>
Cc: "Luca Ceresoli" <[email protected]>
---
diff --git a/drivers/gpu/drm/tiny/cirrus-qemu.c b/drivers/gpu/drm/tiny/cirrus-qemu.c
index 075221b43..7f8cb3264 100644
--- a/drivers/gpu/drm/tiny/cirrus-qemu.c
+++ b/drivers/gpu/drm/tiny/cirrus-qemu.c
@@ -55,7 +55,6 @@
#define DRIVER_MINOR 0
#define CIRRUS_MAX_PITCH (0x1FF << 3) /* (4096 - 1) & ~111b bytes */
-#define CIRRUS_VRAM_SIZE (4 * 1024 * 1024) /* 4 MB */
struct cirrus_device {
struct drm_device dev;
@@ -68,6 +67,7 @@ struct cirrus_device {
/* HW resources */
void __iomem *vram;
+ size_t vram_size;
void __iomem *mmio;
};
@@ -298,6 +298,7 @@ static const uint64_t cirrus_primary_plane_format_modifiers[] = {
static int cirrus_primary_plane_helper_atomic_check(struct drm_plane *plane,
struct drm_atomic_commit *state)
{
+ struct cirrus_device *cirrus = to_cirrus(plane->dev);
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane);
struct drm_framebuffer *fb = new_plane_state->fb;
struct drm_crtc *new_crtc = new_plane_state->crtc;
@@ -319,7 +320,7 @@ static int cirrus_primary_plane_helper_atomic_check(struct drm_plane *plane,
/* validate size constraints */
if (fb->pitches[0] > CIRRUS_MAX_PITCH)
return -EINVAL;
- else if (fb->pitches[0] > CIRRUS_VRAM_SIZE / fb->height)
+ else if (fb->pitches[0] > cirrus->vram_size / fb->height)
return -EINVAL;
return 0;
@@ -514,6 +515,7 @@ static int cirrus_pipe_init(struct cirrus_device *cirrus)
static enum drm_mode_status cirrus_mode_config_mode_valid(struct drm_device *dev,
const struct drm_display_mode *mode)
{
+ struct cirrus_device *cirrus = to_cirrus(dev);
const struct drm_format_info *format = drm_format_info(DRM_FORMAT_XRGB8888);
u64 pitch;
@@ -525,7 +527,7 @@ static enum drm_mode_status cirrus_mode_config_mode_valid(struct drm_device *dev
return MODE_BAD_WIDTH;
if (pitch > CIRRUS_MAX_PITCH)
return MODE_BAD_WIDTH; /* maximum programmable pitch */
- if (pitch > CIRRUS_VRAM_SIZE / mode->vdisplay)
+ if (pitch > cirrus->vram_size / mode->vdisplay)
return MODE_MEM;
return MODE_OK;
@@ -602,8 +604,12 @@ static int cirrus_pci_probe(struct pci_dev *pdev,
dev = &cirrus->dev;
+ if (pci_resource_len(pdev, 0) == 0)
+ return -ENODEV;
+
+ cirrus->vram_size = pci_resource_len(pdev, 0);
cirrus->vram = devm_ioremap(&pdev->dev, pci_resource_start(pdev, 0),
- pci_resource_len(pdev, 0));
+ cirrus->vram_size);
if (cirrus->vram == NULL)
return -ENOMEM;
base-commit: db2ddb87143519e20a95aa36c60b36107b736a58
--
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to Sign-off the patch as a human author
and send it to the upstream kernel mailing lists.
Reply with '#syz reject' to reject it ('#syz unreject' to undo).
See https://goo.gle/syzbot-ai-patches for information about AI-generated patches.
You can comment on the patch as usual, syzbot will try to address
the comments and send a new version of the patch if necessary.
syzbot engineers can be reached at [email protected].