staging: octeon-usb: prevent memory corruption
authorAaro Koskinen <aaro.koskinen@iki.fi>
Wed, 19 Mar 2014 22:52:08 +0000 (00:52 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 20 Mar 2014 01:51:12 +0000 (01:51 +0000)
octeon-hcd will crash the kernel when SLOB is used. This usually happens
after the 18-byte control transfer when a device descriptor is read.
The DMA engine is always transfering full 32-bit words and if the
transfer is shorter, some random garbage appears after the buffer.
The problem is not visible with SLUB since it rounds up the allocations
to word boundary, and the extra bytes will go undetected.

Fix by providing quirk functions for DMA map/unmap that allocate a bigger
temporary buffer when necessary. Tested by booting EdgeRouter Lite
to USB stick root file system with SLAB, SLOB and SLUB kernels.

Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=72121
Reported-by: Sergey Popov <pinkbyte@gentoo.org>
Signed-off-by: Aaro Koskinen <aaro.koskinen@iki.fi>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/staging/octeon-usb/octeon-hcd.c

index 55380b3..8b8ce72 100644 (file)
@@ -464,6 +464,112 @@ struct octeon_hcd {
 /* Returns the IO address to push/pop stuff data from the FIFOs */
 #define USB_FIFO_ADDRESS(channel, usb_index) (CVMX_USBCX_GOTGCTL(usb_index) + ((channel)+1)*0x1000)
 
+/**
+ * struct octeon_temp_buffer - a bounce buffer for USB transfers
+ * @temp_buffer: the newly allocated temporary buffer (including meta-data)
+ * @orig_buffer: the original buffer passed by the USB stack
+ * @data:       the newly allocated temporary buffer (excluding meta-data)
+ *
+ * Both the DMA engine and FIFO mode will always transfer full 32-bit words. If
+ * the buffer is too short, we need to allocate a temporary one, and this struct
+ * represents it.
+ */
+struct octeon_temp_buffer {
+       void *temp_buffer;
+       void *orig_buffer;
+       u8 data[0];
+};
+
+/**
+ * octeon_alloc_temp_buffer - allocate a temporary buffer for USB transfer
+ *                            (if needed)
+ * @urb:       URB.
+ * @mem_flags: Memory allocation flags.
+ *
+ * This function allocates a temporary bounce buffer whenever it's needed
+ * due to HW limitations.
+ */
+static int octeon_alloc_temp_buffer(struct urb *urb, gfp_t mem_flags)
+{
+       struct octeon_temp_buffer *temp;
+
+       if (urb->num_sgs || urb->sg ||
+           (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) ||
+           !(urb->transfer_buffer_length % sizeof(u32)))
+               return 0;
+
+       temp = kmalloc(ALIGN(urb->transfer_buffer_length, sizeof(u32)) +
+                      sizeof(*temp), mem_flags);
+       if (!temp)
+               return -ENOMEM;
+
+       temp->temp_buffer = temp;
+       temp->orig_buffer = urb->transfer_buffer;
+       if (usb_urb_dir_out(urb))
+               memcpy(temp->data, urb->transfer_buffer,
+                      urb->transfer_buffer_length);
+       urb->transfer_buffer = temp->data;
+       urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER;
+
+       return 0;
+}
+
+/**
+ * octeon_free_temp_buffer - free a temporary buffer used by USB transfers.
+ * @urb: URB.
+ *
+ * Frees a buffer allocated by octeon_alloc_temp_buffer().
+ */
+static void octeon_free_temp_buffer(struct urb *urb)
+{
+       struct octeon_temp_buffer *temp;
+
+       if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER))
+               return;
+
+       temp = container_of(urb->transfer_buffer, struct octeon_temp_buffer,
+                           data);
+       if (usb_urb_dir_in(urb))
+               memcpy(temp->orig_buffer, urb->transfer_buffer,
+                      urb->actual_length);
+       urb->transfer_buffer = temp->orig_buffer;
+       urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER;
+       kfree(temp->temp_buffer);
+}
+
+/**
+ * octeon_map_urb_for_dma - Octeon-specific map_urb_for_dma().
+ * @hcd:       USB HCD structure.
+ * @urb:       URB.
+ * @mem_flags: Memory allocation flags.
+ */
+static int octeon_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
+                                 gfp_t mem_flags)
+{
+       int ret;
+
+       ret = octeon_alloc_temp_buffer(urb, mem_flags);
+       if (ret)
+               return ret;
+
+       ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags);
+       if (ret)
+               octeon_free_temp_buffer(urb);
+
+       return ret;
+}
+
+/**
+ * octeon_unmap_urb_for_dma - Octeon-specific unmap_urb_for_dma()
+ * @hcd:       USB HCD structure.
+ * @urb:       URB.
+ */
+static void octeon_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb)
+{
+       usb_hcd_unmap_urb_for_dma(hcd, urb);
+       octeon_free_temp_buffer(urb);
+}
+
 /**
  * Read a USB 32bit CSR. It performs the necessary address swizzle
  * for 32bit CSRs and logs the value in a readable format if
@@ -3601,6 +3707,8 @@ static const struct hc_driver octeon_hc_driver = {
        .get_frame_number       = octeon_usb_get_frame_number,
        .hub_status_data        = octeon_usb_hub_status_data,
        .hub_control            = octeon_usb_hub_control,
+       .map_urb_for_dma        = octeon_map_urb_for_dma,
+       .unmap_urb_for_dma      = octeon_unmap_urb_for_dma,
 };
 
 static int octeon_usb_probe(struct platform_device *pdev)