]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
fbdev: udlfb: add vm_ops to dlfb_ops_mmap to prevent use-after-free
authorRajat Gupta <rajgupt@qti.qualcomm.com>
Mon, 4 May 2026 03:51:10 +0000 (20:51 -0700)
committerHelge Deller <deller@gmx.de>
Mon, 4 May 2026 08:35:55 +0000 (10:35 +0200)
dlfb_ops_mmap() uses remap_pfn_range() to map vmalloc framebuffer pages
to userspace but sets no vm_ops on the VMA. This means the kernel cannot
track active mmaps. When dlfb_realloc_framebuffer() replaces the backing
buffer via FBIOPUT_VSCREENINFO, existing mmap PTEs are not invalidated.
On USB disconnect, dlfb_ops_destroy() calls vfree() on the old pages
while userspace PTEs still reference them, resulting in a use-after-free:
the process retains read/write access to freed kernel pages.

Add vm_operations_struct with open/close callbacks that maintain an
atomic mmap_count on struct dlfb_data. In dlfb_realloc_framebuffer(),
check mmap_count and return -EBUSY if the buffer is currently mapped,
preventing buffer replacement while userspace holds stale PTEs.

Tested with PoC using dummy_hcd + raw_gadget USB device emulation.

Signed-off-by: Rajat Gupta <rajgupt@qti.qualcomm.com>
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: stable@vger.kernel.org
Signed-off-by: Helge Deller <deller@gmx.de>
drivers/video/fbdev/udlfb.c
include/video/udlfb.h

index c341d76bc5646b380d9d6df401cbe9802eca0c70..fdbb8671a810c7d2e6c93a55e50135de8578a4b8 100644 (file)
@@ -321,12 +321,32 @@ static int dlfb_set_video_mode(struct dlfb_data *dlfb,
        return retval;
 }
 
+static void dlfb_vm_open(struct vm_area_struct *vma)
+{
+       struct dlfb_data *dlfb = vma->vm_private_data;
+
+       atomic_inc(&dlfb->mmap_count);
+}
+
+static void dlfb_vm_close(struct vm_area_struct *vma)
+{
+       struct dlfb_data *dlfb = vma->vm_private_data;
+
+       atomic_dec(&dlfb->mmap_count);
+}
+
+static const struct vm_operations_struct dlfb_vm_ops = {
+       .open  = dlfb_vm_open,
+       .close = dlfb_vm_close,
+};
+
 static int dlfb_ops_mmap(struct fb_info *info, struct vm_area_struct *vma)
 {
        unsigned long start = vma->vm_start;
        unsigned long size = vma->vm_end - vma->vm_start;
        unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
        unsigned long page, pos;
+       struct dlfb_data *dlfb = info->par;
 
        if (info->fbdefio)
                return fb_deferred_io_mmap(info, vma);
@@ -358,6 +378,9 @@ static int dlfb_ops_mmap(struct fb_info *info, struct vm_area_struct *vma)
                        size = 0;
        }
 
+       vma->vm_ops = &dlfb_vm_ops;
+       vma->vm_private_data = dlfb;
+       atomic_inc(&dlfb->mmap_count);
        return 0;
 }
 
@@ -1176,7 +1199,6 @@ static void dlfb_deferred_vfree(struct dlfb_data *dlfb, void *mem)
 
 /*
  * Assumes &info->lock held by caller
- * Assumes no active clients have framebuffer open
  */
 static int dlfb_realloc_framebuffer(struct dlfb_data *dlfb, struct fb_info *info, u32 new_len)
 {
@@ -1188,6 +1210,13 @@ static int dlfb_realloc_framebuffer(struct dlfb_data *dlfb, struct fb_info *info
        new_len = PAGE_ALIGN(new_len);
 
        if (new_len > old_len) {
+               if (atomic_read(&dlfb->mmap_count) > 0) {
+                       dev_warn(info->dev,
+                               "refusing realloc: %d active mmaps\n",
+                               atomic_read(&dlfb->mmap_count));
+                       return -EBUSY;
+               }
+
                /*
                 * Alloc system memory for virtual framebuffer
                 */
index 58fb5732831a4325f31ba7e66ef15aeb4a026516..ab34790d57ecd6a951fb9cbe10c096ef66caa824 100644 (file)
@@ -56,6 +56,7 @@ struct dlfb_data {
        spinlock_t damage_lock;
        struct work_struct damage_work;
        struct fb_ops ops;
+       atomic_t mmap_count;
        /* blit-only rendering path metrics, exposed through sysfs */
        atomic_t bytes_rendered; /* raw pixel-bytes driver asked to render */
        atomic_t bytes_identical; /* saved effort with backbuffer comparison */