Commit 2fc11536cf5c0b8eb4eb7e01a2a672a189e9280f

Authored by Hans Verkuil
Committed by Mauro Carvalho Chehab
1 parent c10469c637

V4L/DVB: videobuf-dma-sg: set correct size in last sg element

This fixes a nasty memory corruption bug when using userptr I/O.
The function videobuf_pages_to_sg() sets up the scatter-gather list for the
DMA transfer to the userspace pages. The first transfer is setup correctly
(the size is set to PAGE_SIZE - offset), but all other transfers have size
PAGE_SIZE. This is wrong for the last transfer which may be less than PAGE_SIZE.

Most, if not all, drivers will program the boards DMA engine correctly, i.e.
even though the size in the last sg element is wrong, they will do their
own size calculations and make sure the right amount is DMA-ed, and so seemingly
prevent memory corruption.

However, behind the scenes the dynamic DMA mapping support (in lib/swiotlb.c)
may create bounce buffers if the memory pages are not in DMA-able memory.
This happens for example on a 64-bit linux with a board that only supports
32-bit DMA.

These bounce buffers DO use the information in the sg list to determine the
size. So while the DMA engine transfers the correct amount of data, when the
data is 'bounced' back too much is copied, causing buffer overwrites.

The fix is simple: calculate and set the correct size for the last sg list
element.

Signed-off-by: Hans Verkuil <hans.verkuil@tandberg.com>
Cc: stable@kernel.org
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>

Showing 2 changed files with 8 additions and 4 deletions Side-by-side Diff

drivers/media/video/videobuf-dma-sg.c
... ... @@ -94,7 +94,7 @@
94 94 * must free the memory.
95 95 */
96 96 static struct scatterlist *videobuf_pages_to_sg(struct page **pages,
97   - int nr_pages, int offset)
  97 + int nr_pages, int offset, size_t size)
98 98 {
99 99 struct scatterlist *sglist;
100 100 int i;
101 101  
... ... @@ -110,12 +110,14 @@
110 110 /* DMA to highmem pages might not work */
111 111 goto highmem;
112 112 sg_set_page(&sglist[0], pages[0], PAGE_SIZE - offset, offset);
  113 + size -= PAGE_SIZE - offset;
113 114 for (i = 1; i < nr_pages; i++) {
114 115 if (NULL == pages[i])
115 116 goto nopage;
116 117 if (PageHighMem(pages[i]))
117 118 goto highmem;
118   - sg_set_page(&sglist[i], pages[i], PAGE_SIZE, 0);
  119 + sg_set_page(&sglist[i], pages[i], min(PAGE_SIZE, size), 0);
  120 + size -= min(PAGE_SIZE, size);
119 121 }
120 122 return sglist;
121 123  
... ... @@ -170,7 +172,8 @@
170 172  
171 173 first = (data & PAGE_MASK) >> PAGE_SHIFT;
172 174 last = ((data+size-1) & PAGE_MASK) >> PAGE_SHIFT;
173   - dma->offset = data & ~PAGE_MASK;
  175 + dma->offset = data & ~PAGE_MASK;
  176 + dma->size = size;
174 177 dma->nr_pages = last-first+1;
175 178 dma->pages = kmalloc(dma->nr_pages * sizeof(struct page *), GFP_KERNEL);
176 179 if (NULL == dma->pages)
... ... @@ -252,7 +255,7 @@
252 255  
253 256 if (dma->pages) {
254 257 dma->sglist = videobuf_pages_to_sg(dma->pages, dma->nr_pages,
255   - dma->offset);
  258 + dma->offset, dma->size);
256 259 }
257 260 if (dma->vaddr) {
258 261 dma->sglist = videobuf_vmalloc_to_sg(dma->vaddr,
include/media/videobuf-dma-sg.h
... ... @@ -48,6 +48,7 @@
48 48  
49 49 /* for userland buffer */
50 50 int offset;
  51 + size_t size;
51 52 struct page **pages;
52 53  
53 54 /* for kernel buffers */