• Jakub Sitnicki's avatar
    net, gro: Set inner transport header offset in tcp/udp GRO hook · d51c5907
    Jakub Sitnicki authored
    GSO expects inner transport header offset to be valid when
    skb->encapsulation flag is set. GSO uses this value to calculate the length
    of an individual segment of a GSO packet in skb_gso_transport_seglen().
    
    However, tcp/udp gro_complete callbacks don't update the
    skb->inner_transport_header when processing an encapsulated TCP/UDP
    segment. As a result a GRO skb has ->inner_transport_header set to a value
    carried over from earlier skb processing.
    
    This can have mild to tragic consequences. From miscalculating the GSO
    segment length to triggering a page fault [1], when trying to read TCP/UDP
    header at an address past the skb->data page.
    
    The latter scenario leads to an oops report like so:
    
      BUG: unable to handle page fault for address: ffff9fa7ec00d008
      #PF: supervisor read access in kernel mode
      #PF: error_code(0x0000) - not-present page
      PGD 123f201067 P4D 123f201067 PUD 123f209067 PMD 0
      Oops: 0000 [#1] SMP NOPTI
      CPU: 44 PID: 0 Comm: swapper/44 Not tainted 5.4.53-cloudflare-2020.7.21 #1
      Hardware name: HYVE EDGE-METAL-GEN10/HS-1811DLite1, BIOS V2.15 02/21/2020
      RIP: 0010:skb_gso_transport_seglen+0x44/0xa0
      Code: c0 41 83 e0 11 f6 87 81 00 00 00 20 74 30 0f b7 87 aa 00 00 00 0f [...]
      RSP: 0018:ffffad8640bacbb8 EFLAGS: 00010202
      RAX: 000000000000feda RBX: ffff9fcc8d31bc00 RCX: ffff9fa7ec00cffc
      RDX: ffff9fa7ebffdec0 RSI: 000000000000feda RDI: 0000000000000122
      RBP: 00000000000005c4 R08: 0000000000000001 R09: 0000000000000000
      R10: ffff9fe588ae3800 R11: ffff9fe011fc92f0 R12: ffff9fcc8d31bc00
      R13: ffff9fe0119d4300 R14: 00000000000005c4 R15: ffff9fba57d70900
      FS:  0000000000000000(0000) GS:ffff9fe68df00000(0000) knlGS:0000000000000000
      CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
      CR2: ffff9fa7ec00d008 CR3: 0000003e99b1c000 CR4: 0000000000340ee0
      Call Trace:
       <IRQ>
       skb_gso_validate_network_len+0x11/0x70
       __ip_finish_output+0x109/0x1c0
       ip_sublist_rcv_finish+0x57/0x70
       ip_sublist_rcv+0x2aa/0x2d0
       ? ip_rcv_finish_core.constprop.0+0x390/0x390
       ip_list_rcv+0x12b/0x14f
       __netif_receive_skb_list_core+0x2a9/0x2d0
       netif_receive_skb_list_internal+0x1b5/0x2e0
       napi_complete_done+0x93/0x140
       veth_poll+0xc0/0x19f [veth]
       ? mlx5e_napi_poll+0x221/0x610 [mlx5_core]
       net_rx_action+0x1f8/0x790
       __do_softirq+0xe1/0x2bf
       irq_exit+0x8e/0xc0
       do_IRQ+0x58/0xe0
       common_interrupt+0xf/0xf
       </IRQ>
    
    The bug can be observed in a simple setup where we send IP/GRE/IP/TCP
    packets into a netns over a veth pair. Inside the netns, packets are
    forwarded to dummy device:
    
      trafgen -> [veth A]--[veth B] -forward-> [dummy]
    
    For veth B to GRO aggregate packets on receive, it needs to have an XDP
    program attached (for example, a trivial XDP_PASS). Additionally, for UDP,
    we need to enable GSO_UDP_L4 feature on the device:
    
      ip netns exec A ethtool -K AB rx-udp-gro-forwarding on
    
    The last component is an artificial delay to increase the chances of GRO
    batching happening:
    
      ip netns exec A tc qdisc add dev AB root \
         netem delay 200us slot 5ms 10ms packets 2 bytes 64k
    
    With such a setup in place, the bug can be observed by tracing the skb
    outer and inner offsets when GSO skb is transmitted from the dummy device:
    
    tcp:
    
    FUNC              DEV   SKB_LEN  NH  TH ENC INH ITH GSO_SIZE GSO_TYPE
    ip_finish_output  dumB     2830 270 290   1 294 254     1383 (tcpv4,gre,)
                                                    ^^^
    udp:
    
    FUNC              DEV   SKB_LEN  NH  TH ENC INH ITH GSO_SIZE GSO_TYPE
    ip_finish_output  dumB     2818 270 290   1 294 254     1383 (gre,udp_l4,)
                                                    ^^^
    
    Fix it by updating the inner transport header offset in tcp/udp
    gro_complete callbacks, similar to how {inet,ipv6}_gro_complete callbacks
    update the inner network header offset, when skb->encapsulation flag is
    set.
    
    [1] https://lore.kernel.org/netdev/CAKxSbF01cLpZem2GFaUaifh0S-5WYViZemTicAg7FCHOnh6kug@mail.gmail.com/
    
    Fixes: bf296b12 ("tcp: Add GRO support")
    Fixes: f993bc25 ("net: core: handle encapsulation offloads when computing segment lengths")
    Fixes: e20cf8d3 ("udp: implement GRO for plain UDP sockets.")
    Reported-by: default avatarAlex Forster <aforster@cloudflare.com>
    Signed-off-by: default avatarJakub Sitnicki <jakub@cloudflare.com>
    Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
    d51c5907
tcp_offload.c 8.21 KB