Skip to content

Commit

Permalink
firewire: ohci: fix buffer overflow in AR split packet handling
Browse files Browse the repository at this point in the history
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>
Cc: 2.6.22-2.6.36 <stable@kernel.org>
Tested-by: Maxim Levitsky <maximlevitsky@gmail.com>
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de> (cast PAGE_SIZE to size_t)
  • Loading branch information
Clemens Ladisch authored and Stefan Richter committed Oct 30, 2010
1 parent f6f94e2 commit 85f7ffd
Showing 1 changed file with 32 additions and 3 deletions.
35 changes: 32 additions & 3 deletions drivers/firewire/ohci.c
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,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;

Expand All @@ -756,12 +756,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);
Expand Down

0 comments on commit 85f7ffd

Please sign in to comment.