]> git.itanic.dy.fi Git - linux-stable/commitdiff
usb: usbfs: Enforce page requirements for mmap
authorRuihan Li <lrh2000@pku.edu.cn>
Mon, 15 May 2023 13:09:55 +0000 (21:09 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 14 Jun 2023 08:59:58 +0000 (10:59 +0200)
commit 0143d148d1e882fb1538dc9974c94d63961719b9 upstream.

The current implementation of usbdev_mmap uses usb_alloc_coherent to
allocate memory pages that will later be mapped into the user space.
Meanwhile, usb_alloc_coherent employs three different methods to
allocate memory, as outlined below:
 * If hcd->localmem_pool is non-null, it uses gen_pool_dma_alloc to
   allocate memory;
 * If DMA is not available, it uses kmalloc to allocate memory;
 * Otherwise, it uses dma_alloc_coherent.

However, it should be noted that gen_pool_dma_alloc does not guarantee
that the resulting memory will be page-aligned. Furthermore, trying to
map slab pages (i.e., memory allocated by kmalloc) into the user space
is not resonable and can lead to problems, such as a type confusion bug
when PAGE_TABLE_CHECK=y [1].

To address these issues, this patch introduces hcd_alloc_coherent_pages,
which addresses the above two problems. Specifically,
hcd_alloc_coherent_pages uses gen_pool_dma_alloc_align instead of
gen_pool_dma_alloc to ensure that the memory is page-aligned. To replace
kmalloc, hcd_alloc_coherent_pages directly allocates pages by calling
__get_free_pages.

Reported-by: syzbot+fcf1a817ceb50935ce99@syzkaller.appspotmail.comm
Closes: https://lore.kernel.org/lkml/000000000000258e5e05fae79fc1@google.com/ [1]
Fixes: f7d34b445abc ("USB: Add support for usbfs zerocopy.")
Fixes: ff2437befd8f ("usb: host: Fix excessive alignment restriction for local memory allocations")
Cc: stable@vger.kernel.org
Signed-off-by: Ruihan Li <lrh2000@pku.edu.cn>
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Link: https://lore.kernel.org/r/20230515130958.32471-2-lrh2000@pku.edu.cn
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/core/buffer.c
drivers/usb/core/devio.c
include/linux/usb/hcd.h

index 6cf22c27f2d24e3eb74f631cd2adb07e8c685ce6..be8738750948e1c314a11a30c5114ff7adfe7c3a 100644 (file)
@@ -170,3 +170,44 @@ void hcd_buffer_free(
        }
        dma_free_coherent(hcd->self.sysdev, size, addr, dma);
 }
+
+void *hcd_buffer_alloc_pages(struct usb_hcd *hcd,
+               size_t size, gfp_t mem_flags, dma_addr_t *dma)
+{
+       if (size == 0)
+               return NULL;
+
+       if (hcd->localmem_pool)
+               return gen_pool_dma_alloc_align(hcd->localmem_pool,
+                               size, dma, PAGE_SIZE);
+
+       /* some USB hosts just use PIO */
+       if (!hcd_uses_dma(hcd)) {
+               *dma = DMA_MAPPING_ERROR;
+               return (void *)__get_free_pages(mem_flags,
+                               get_order(size));
+       }
+
+       return dma_alloc_coherent(hcd->self.sysdev,
+                       size, dma, mem_flags);
+}
+
+void hcd_buffer_free_pages(struct usb_hcd *hcd,
+               size_t size, void *addr, dma_addr_t dma)
+{
+       if (!addr)
+               return;
+
+       if (hcd->localmem_pool) {
+               gen_pool_free(hcd->localmem_pool,
+                               (unsigned long)addr, size);
+               return;
+       }
+
+       if (!hcd_uses_dma(hcd)) {
+               free_pages((unsigned long)addr, get_order(size));
+               return;
+       }
+
+       dma_free_coherent(hcd->self.sysdev, size, addr, dma);
+}
index d037deb9588411a2c9caa685f9cc65a584014a23..e76694e32736540490e3dec0afcc6ed4608444b5 100644 (file)
@@ -173,6 +173,7 @@ static int connected(struct usb_dev_state *ps)
 static void dec_usb_memory_use_count(struct usb_memory *usbm, int *count)
 {
        struct usb_dev_state *ps = usbm->ps;
+       struct usb_hcd *hcd = bus_to_hcd(ps->dev->bus);
        unsigned long flags;
 
        spin_lock_irqsave(&ps->lock, flags);
@@ -181,8 +182,8 @@ static void dec_usb_memory_use_count(struct usb_memory *usbm, int *count)
                list_del(&usbm->memlist);
                spin_unlock_irqrestore(&ps->lock, flags);
 
-               usb_free_coherent(ps->dev, usbm->size, usbm->mem,
-                               usbm->dma_handle);
+               hcd_buffer_free_pages(hcd, usbm->size,
+                               usbm->mem, usbm->dma_handle);
                usbfs_decrease_memory_usage(
                        usbm->size + sizeof(struct usb_memory));
                kfree(usbm);
@@ -234,8 +235,8 @@ static int usbdev_mmap(struct file *file, struct vm_area_struct *vma)
                goto error_decrease_mem;
        }
 
-       mem = usb_alloc_coherent(ps->dev, size, GFP_USER | __GFP_NOWARN,
-                       &dma_handle);
+       mem = hcd_buffer_alloc_pages(hcd,
+                       size, GFP_USER | __GFP_NOWARN, &dma_handle);
        if (!mem) {
                ret = -ENOMEM;
                goto error_free_usbm;
index a400e86f621816788f3c71897219ea57048a8889..0cfd540e7d063c89069c0f14dac4f747062fa268 100644 (file)
@@ -503,6 +503,11 @@ void *hcd_buffer_alloc(struct usb_bus *bus, size_t size,
 void hcd_buffer_free(struct usb_bus *bus, size_t size,
        void *addr, dma_addr_t dma);
 
+void *hcd_buffer_alloc_pages(struct usb_hcd *hcd,
+               size_t size, gfp_t mem_flags, dma_addr_t *dma);
+void hcd_buffer_free_pages(struct usb_hcd *hcd,
+               size_t size, void *addr, dma_addr_t dma);
+
 /* generic bus glue, needed for host controllers that don't use PCI */
 extern irqreturn_t usb_hcd_irq(int irq, void *__hcd);