Commit f69fa76482e654f7d94e4aa40ea0ebf04363396a
Exists in
master
and in
4 other branches
Merge branch 'fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/ieee1394/linux1394-2.6
* 'fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/ieee1394/linux1394-2.6: firewire: ohci: fix race when reading count in AR descriptor firewire: ohci: avoid reallocation of AR buffers firewire: ohci: fix race in AR split packet handling firewire: ohci: fix buffer overflow in AR split packet handling
Showing 1 changed file Side-by-side Diff
drivers/firewire/ohci.c
... | ... | @@ -577,17 +577,11 @@ |
577 | 577 | return ret; |
578 | 578 | } |
579 | 579 | |
580 | -static int ar_context_add_page(struct ar_context *ctx) | |
580 | +static void ar_context_link_page(struct ar_context *ctx, | |
581 | + struct ar_buffer *ab, dma_addr_t ab_bus) | |
581 | 582 | { |
582 | - struct device *dev = ctx->ohci->card.device; | |
583 | - struct ar_buffer *ab; | |
584 | - dma_addr_t uninitialized_var(ab_bus); | |
585 | 583 | size_t offset; |
586 | 584 | |
587 | - ab = dma_alloc_coherent(dev, PAGE_SIZE, &ab_bus, GFP_ATOMIC); | |
588 | - if (ab == NULL) | |
589 | - return -ENOMEM; | |
590 | - | |
591 | 585 | ab->next = NULL; |
592 | 586 | memset(&ab->descriptor, 0, sizeof(ab->descriptor)); |
593 | 587 | ab->descriptor.control = cpu_to_le16(DESCRIPTOR_INPUT_MORE | |
594 | 588 | |
... | ... | @@ -606,7 +600,20 @@ |
606 | 600 | |
607 | 601 | reg_write(ctx->ohci, CONTROL_SET(ctx->regs), CONTEXT_WAKE); |
608 | 602 | flush_writes(ctx->ohci); |
603 | +} | |
609 | 604 | |
605 | +static int ar_context_add_page(struct ar_context *ctx) | |
606 | +{ | |
607 | + struct device *dev = ctx->ohci->card.device; | |
608 | + struct ar_buffer *ab; | |
609 | + dma_addr_t uninitialized_var(ab_bus); | |
610 | + | |
611 | + ab = dma_alloc_coherent(dev, PAGE_SIZE, &ab_bus, GFP_ATOMIC); | |
612 | + if (ab == NULL) | |
613 | + return -ENOMEM; | |
614 | + | |
615 | + ar_context_link_page(ctx, ab, ab_bus); | |
616 | + | |
610 | 617 | return 0; |
611 | 618 | } |
612 | 619 | |
613 | 620 | |
614 | 621 | |
... | ... | @@ -730,16 +737,17 @@ |
730 | 737 | static void ar_context_tasklet(unsigned long data) |
731 | 738 | { |
732 | 739 | struct ar_context *ctx = (struct ar_context *)data; |
733 | - struct fw_ohci *ohci = ctx->ohci; | |
734 | 740 | struct ar_buffer *ab; |
735 | 741 | struct descriptor *d; |
736 | 742 | void *buffer, *end; |
743 | + __le16 res_count; | |
737 | 744 | |
738 | 745 | ab = ctx->current_buffer; |
739 | 746 | d = &ab->descriptor; |
740 | 747 | |
741 | - if (d->res_count == 0) { | |
742 | - size_t size, rest, offset; | |
748 | + res_count = ACCESS_ONCE(d->res_count); | |
749 | + if (res_count == 0) { | |
750 | + size_t size, size2, rest, pktsize, size3, offset; | |
743 | 751 | dma_addr_t start_bus; |
744 | 752 | void *start; |
745 | 753 | |
746 | 754 | |
747 | 755 | |
748 | 756 | |
749 | 757 | |
750 | 758 | |
751 | 759 | |
752 | 760 | |
... | ... | @@ -750,29 +758,63 @@ |
750 | 758 | */ |
751 | 759 | |
752 | 760 | offset = offsetof(struct ar_buffer, data); |
753 | - start = buffer = ab; | |
761 | + start = ab; | |
754 | 762 | start_bus = le32_to_cpu(ab->descriptor.data_address) - offset; |
763 | + buffer = ab->data; | |
755 | 764 | |
756 | 765 | ab = ab->next; |
757 | 766 | d = &ab->descriptor; |
758 | - size = buffer + PAGE_SIZE - ctx->pointer; | |
767 | + size = start + PAGE_SIZE - ctx->pointer; | |
768 | + /* valid buffer data in the next page */ | |
759 | 769 | rest = le16_to_cpu(d->req_count) - le16_to_cpu(d->res_count); |
770 | + /* what actually fits in this page */ | |
771 | + size2 = min(rest, (size_t)PAGE_SIZE - offset - size); | |
760 | 772 | memmove(buffer, ctx->pointer, size); |
761 | - memcpy(buffer + size, ab->data, rest); | |
762 | - ctx->current_buffer = ab; | |
763 | - ctx->pointer = (void *) ab->data + rest; | |
764 | - end = buffer + size + rest; | |
773 | + memcpy(buffer + size, ab->data, size2); | |
765 | 774 | |
766 | - while (buffer < end) | |
767 | - buffer = handle_ar_packet(ctx, buffer); | |
775 | + while (size > 0) { | |
776 | + void *next = handle_ar_packet(ctx, buffer); | |
777 | + pktsize = next - buffer; | |
778 | + if (pktsize >= size) { | |
779 | + /* | |
780 | + * We have handled all the data that was | |
781 | + * originally in this page, so we can now | |
782 | + * continue in the next page. | |
783 | + */ | |
784 | + buffer = next; | |
785 | + break; | |
786 | + } | |
787 | + /* move the next packet to the start of the buffer */ | |
788 | + memmove(buffer, next, size + size2 - pktsize); | |
789 | + size -= pktsize; | |
790 | + /* fill up this page again */ | |
791 | + size3 = min(rest - size2, | |
792 | + (size_t)PAGE_SIZE - offset - size - size2); | |
793 | + memcpy(buffer + size + size2, | |
794 | + (void *) ab->data + size2, size3); | |
795 | + size2 += size3; | |
796 | + } | |
768 | 797 | |
769 | - dma_free_coherent(ohci->card.device, PAGE_SIZE, | |
770 | - start, start_bus); | |
771 | - ar_context_add_page(ctx); | |
798 | + if (rest > 0) { | |
799 | + /* handle the packets that are fully in the next page */ | |
800 | + buffer = (void *) ab->data + | |
801 | + (buffer - (start + offset + size)); | |
802 | + end = (void *) ab->data + rest; | |
803 | + | |
804 | + while (buffer < end) | |
805 | + buffer = handle_ar_packet(ctx, buffer); | |
806 | + | |
807 | + ctx->current_buffer = ab; | |
808 | + ctx->pointer = end; | |
809 | + | |
810 | + ar_context_link_page(ctx, start, start_bus); | |
811 | + } else { | |
812 | + ctx->pointer = start + PAGE_SIZE; | |
813 | + } | |
772 | 814 | } else { |
773 | 815 | buffer = ctx->pointer; |
774 | 816 | ctx->pointer = end = |
775 | - (void *) ab + PAGE_SIZE - le16_to_cpu(d->res_count); | |
817 | + (void *) ab + PAGE_SIZE - le16_to_cpu(res_count); | |
776 | 818 | |
777 | 819 | while (buffer < end) |
778 | 820 | buffer = handle_ar_packet(ctx, buffer); |