Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

usb: xhci: Fix isochronous Ring Underrun/Overrun event handling

The TRB pointer of these events points at enqueue at the time of error
occurrence on xHCI 1.1+ HCs or it's NULL on older ones. By the time we
are handling the event, a new TD may be queued at this ring position.

I can trigger this race by rising interrupt moderation to increase IRQ
handling delay. Similar delay may occur naturally due to system load.

If this ever happens after a Missed Service Error, missed TDs will be
skipped and the new TD processed as if it matched the event. It could
be given back prematurely, risking data loss or buffer UAF by the xHC.

Don't complete TDs on xrun events and don't warn if queued TDs don't
match the event's TRB pointer, which can be NULL or a link/no-op TRB.
Don't warn if there are no queued TDs at all.

Now that it's safe, also handle xrun events if the skip flag is clear.
This ensures completion of any TD stuck in 'error mid TD' state right
before the xrun event, which could happen if a driver submits a finite
number of URBs to a buggy HC and then an error occurs on the last TD.

Signed-off-by: Michal Pecio <michal.pecio@gmail.com>
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://lore.kernel.org/r/20250306144954.3507700-6-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Michal Pecio and committed by
Greg Kroah-Hartman
906dec15 bfa84599

+14 -6
+14 -6
drivers/usb/host/xhci-ring.c
··· 2627 2627 int status = -EINPROGRESS; 2628 2628 struct xhci_ep_ctx *ep_ctx; 2629 2629 u32 trb_comp_code; 2630 + bool ring_xrun_event = false; 2630 2631 2631 2632 slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); 2632 2633 ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; ··· 2734 2733 * Underrun Event for OUT Isoch endpoint. 2735 2734 */ 2736 2735 xhci_dbg(xhci, "Underrun event on slot %u ep %u\n", slot_id, ep_index); 2737 - if (ep->skip) 2738 - break; 2739 - return 0; 2736 + ring_xrun_event = true; 2737 + break; 2740 2738 case COMP_RING_OVERRUN: 2741 2739 xhci_dbg(xhci, "Overrun event on slot %u ep %u\n", slot_id, ep_index); 2742 - if (ep->skip) 2743 - break; 2744 - return 0; 2740 + ring_xrun_event = true; 2741 + break; 2745 2742 case COMP_MISSED_SERVICE_ERROR: 2746 2743 /* 2747 2744 * When encounter missed service error, one or more isoc tds ··· 2812 2813 */ 2813 2814 if (trb_comp_code != COMP_STOPPED && 2814 2815 trb_comp_code != COMP_STOPPED_LENGTH_INVALID && 2816 + !ring_xrun_event && 2815 2817 !ep_ring->last_td_was_short) { 2816 2818 xhci_warn(xhci, "Event TRB for slot %u ep %u with no TDs queued\n", 2817 2819 slot_id, ep_index); ··· 2846 2846 td = NULL; 2847 2847 goto check_endpoint_halted; 2848 2848 } 2849 + 2850 + /* TD was queued after xrun, maybe xrun was on a link, don't panic yet */ 2851 + if (ring_xrun_event) 2852 + return 0; 2849 2853 2850 2854 /* 2851 2855 * Skip the Force Stopped Event. The 'ep_trb' of FSE is not in the current ··· 2896 2892 * the event. 2897 2893 */ 2898 2894 } while (ep->skip); 2895 + 2896 + /* Get out if a TD was queued at enqueue after the xrun occurred */ 2897 + if (ring_xrun_event) 2898 + return 0; 2899 2899 2900 2900 if (trb_comp_code == COMP_SHORT_PACKET) 2901 2901 ep_ring->last_td_was_short = true;