| From 0e41a4f42fa43d85f2006a9788c354fb72699a4e Mon Sep 17 00:00:00 2001 |
| From: Clemens Ladisch <clemens@ladisch.de> |
| Date: Mon, 25 Oct 2010 11:41:53 +0200 |
| Subject: [PATCH] firewire: ohci: fix buffer overflow in AR split packet |
| handling |
| |
| commit 85f7ffd5d2b320f73912b15fe8cef34bae297daf upstream. |
| |
| When the controller had to split a received asynchronous packet into two |
| buffers, the driver tries to reassemble it by copying both parts into |
| the first page. However, if size + rest > PAGE_SIZE, i.e., if the yet |
| unhandled packets before the split packet, the split packet itself, and |
| any received packets after the split packet are together larger than one |
| page, then the memory after the first page would get overwritten. |
| |
| To fix this, do not try to copy the data of all unhandled packets at |
| once, but copy the possibly needed data every time when handling |
| a packet. |
| |
| This gets rid of most of the infamous crashes and data corruptions when |
| using firewire-net. |
| |
| Signed-off-by: Clemens Ladisch <clemens@ladisch.de> |
| Tested-by: Maxim Levitsky <maximlevitsky@gmail.com> |
| Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de> (cast PAGE_SIZE to size_t) |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c |
| index 94b16e0..b1b0281 100644 |
| --- a/drivers/firewire/ohci.c |
| +++ b/drivers/firewire/ohci.c |
| @@ -636,7 +636,7 @@ static void ar_context_tasklet(unsigned long data) |
| d = &ab->descriptor; |
| |
| if (d->res_count == 0) { |
| - size_t size, rest, offset; |
| + size_t size, size2, rest, pktsize, size3, offset; |
| dma_addr_t start_bus; |
| void *start; |
| |
| @@ -653,12 +653,41 @@ static void ar_context_tasklet(unsigned long data) |
| ab = ab->next; |
| d = &ab->descriptor; |
| size = buffer + PAGE_SIZE - ctx->pointer; |
| + /* valid buffer data in the next page */ |
| rest = le16_to_cpu(d->req_count) - le16_to_cpu(d->res_count); |
| + /* what actually fits in this page */ |
| + size2 = min(rest, (size_t)PAGE_SIZE - size); |
| memmove(buffer, ctx->pointer, size); |
| - memcpy(buffer + size, ab->data, rest); |
| + memcpy(buffer + size, ab->data, size2); |
| ctx->current_buffer = ab; |
| ctx->pointer = (void *) ab->data + rest; |
| - end = buffer + size + rest; |
| + |
| + while (size > 0) { |
| + void *next = handle_ar_packet(ctx, buffer); |
| + pktsize = next - buffer; |
| + if (pktsize >= size) { |
| + /* |
| + * We have handled all the data that was |
| + * originally in this page, so we can now |
| + * continue in the next page. |
| + */ |
| + buffer = next; |
| + break; |
| + } |
| + /* move the next packet to the start of the buffer */ |
| + memmove(buffer, next, size + size2 - pktsize); |
| + size -= pktsize; |
| + /* fill up this page again */ |
| + size3 = min(rest - size2, |
| + (size_t)PAGE_SIZE - size - size2); |
| + memcpy(buffer + size + size2, |
| + (void *) ab->data + size2, size3); |
| + size2 += size3; |
| + } |
| + |
| + /* handle the packets that are fully in the next page */ |
| + buffer = (void *) ab->data + (buffer - (start + size)); |
| + end = (void *) ab->data + rest; |
| |
| while (buffer < end) |
| buffer = handle_ar_packet(ctx, buffer); |
| -- |
| 1.7.4.4 |
| |