Commit 2fc11536 authored by Hans Verkuil's avatar Hans Verkuil Committed by Mauro Carvalho Chehab

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: default avatarHans Verkuil <hans.verkuil@tandberg.com>
Cc: stable@kernel.org
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@redhat.com>
parent c10469c6
...@@ -94,7 +94,7 @@ static struct scatterlist *videobuf_vmalloc_to_sg(unsigned char *virt, ...@@ -94,7 +94,7 @@ static struct scatterlist *videobuf_vmalloc_to_sg(unsigned char *virt,
* must free the memory. * must free the memory.
*/ */
static struct scatterlist *videobuf_pages_to_sg(struct page **pages, static struct scatterlist *videobuf_pages_to_sg(struct page **pages,
int nr_pages, int offset) int nr_pages, int offset, size_t size)
{ {
struct scatterlist *sglist; struct scatterlist *sglist;
int i; int i;
...@@ -110,12 +110,14 @@ static struct scatterlist *videobuf_pages_to_sg(struct page **pages, ...@@ -110,12 +110,14 @@ static struct scatterlist *videobuf_pages_to_sg(struct page **pages,
/* DMA to highmem pages might not work */ /* DMA to highmem pages might not work */
goto highmem; goto highmem;
sg_set_page(&sglist[0], pages[0], PAGE_SIZE - offset, offset); sg_set_page(&sglist[0], pages[0], PAGE_SIZE - offset, offset);
size -= PAGE_SIZE - offset;
for (i = 1; i < nr_pages; i++) { for (i = 1; i < nr_pages; i++) {
if (NULL == pages[i]) if (NULL == pages[i])
goto nopage; goto nopage;
if (PageHighMem(pages[i])) if (PageHighMem(pages[i]))
goto highmem; goto highmem;
sg_set_page(&sglist[i], pages[i], PAGE_SIZE, 0); sg_set_page(&sglist[i], pages[i], min(PAGE_SIZE, size), 0);
size -= min(PAGE_SIZE, size);
} }
return sglist; return sglist;
...@@ -170,7 +172,8 @@ static int videobuf_dma_init_user_locked(struct videobuf_dmabuf *dma, ...@@ -170,7 +172,8 @@ static int videobuf_dma_init_user_locked(struct videobuf_dmabuf *dma,
first = (data & PAGE_MASK) >> PAGE_SHIFT; first = (data & PAGE_MASK) >> PAGE_SHIFT;
last = ((data+size-1) & PAGE_MASK) >> PAGE_SHIFT; last = ((data+size-1) & PAGE_MASK) >> PAGE_SHIFT;
dma->offset = data & ~PAGE_MASK; dma->offset = data & ~PAGE_MASK;
dma->size = size;
dma->nr_pages = last-first+1; dma->nr_pages = last-first+1;
dma->pages = kmalloc(dma->nr_pages * sizeof(struct page *), GFP_KERNEL); dma->pages = kmalloc(dma->nr_pages * sizeof(struct page *), GFP_KERNEL);
if (NULL == dma->pages) if (NULL == dma->pages)
...@@ -252,7 +255,7 @@ int videobuf_dma_map(struct device *dev, struct videobuf_dmabuf *dma) ...@@ -252,7 +255,7 @@ int videobuf_dma_map(struct device *dev, struct videobuf_dmabuf *dma)
if (dma->pages) { if (dma->pages) {
dma->sglist = videobuf_pages_to_sg(dma->pages, dma->nr_pages, dma->sglist = videobuf_pages_to_sg(dma->pages, dma->nr_pages,
dma->offset); dma->offset, dma->size);
} }
if (dma->vaddr) { if (dma->vaddr) {
dma->sglist = videobuf_vmalloc_to_sg(dma->vaddr, dma->sglist = videobuf_vmalloc_to_sg(dma->vaddr,
......
...@@ -48,6 +48,7 @@ struct videobuf_dmabuf { ...@@ -48,6 +48,7 @@ struct videobuf_dmabuf {
/* for userland buffer */ /* for userland buffer */
int offset; int offset;
size_t size;
struct page **pages; struct page **pages;
/* for kernel buffers */ /* for kernel buffers */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment