freenode
Kernel & Low-Level

Kernel closes virtio GSO bypass that skipped flow dissection

Untrusted GSO frames without NEEDS_CSUM could evade checks, mis-place transport headers, and trigger out-of-bounds reads, especially above 64 KB.

Eric Dumazet has landed fixes in the Linux networking tree for a bypass in untrusted Generic Segmentation Offload (GSO) validation on virtio net headers, plus a related truncation bug in flow dissection that mattered once packets routinely exceeded 64 KB.

A earlier hardening step was supposed to force flow dissection of GSO packets that lacked the NEEDS_CSUM flag. It gated that path on the socket buffer's network-header offset being non-zero. Several entry points (TAP writes, virtio-net receive, and related paths) called the conversion helper before initializing that offset and the device pointer. Fresh buffers zero the network offset, and zero is also a valid offset when there is no headroom, so the check always failed open. Those frames skipped dissection and took a shallow fallback that pulled a fixed minimum header length without validating L3/L4 protocols or setting the transport header.

The fallout was concrete. Malformed network headers were not rejected. IPv4 options or IPv6 extension headers could leave the TCP header outside linear data. Later GSO length logic then read out of bounds; KASAN reported slab-out-of-bounds access while computing transport segment length under qdisc enqueue. Protocol matching also ran too early against the outer Ethernet type, so VLAN-tagged (802.1Q) GSO without NEEDS_CSUM could be rejected before dissection ever saw the real L3 protocol.

The fix initializes device and network-header state in the affected callers, always dissects GSO packets in the virtio header path, and matches the GSO type against the dissected L3 protocol rather than the outer L2 type. The unvalidated shallow fallback is gone.

A companion change repairs transport-offset calculation in the core flow dissector. Comparing lengths with a 16-bit minimum silently truncated skb length, so a successful dissection of a frame whose length modulo 65536 was smaller than the true offset (for example length 65540 with network offset 34) stored a bogus small transport offset, pointing into the Ethernet header. Large GSO skbs were already possible via AF_PACKET with virtio headers; BIG TCP made them common. Offsets that no longer fit in 16 bits now fail dissection instead of returning a wrong value.

New TAP selftests accept VLAN-tagged TCPv4 GSO without NEEDS_CSUM (including DATA_VALID), reject mismatched GSO types and truncated TCP headers, and accept a 65540-byte frame dissected before the Ethernet header is pulled. Michael S. Tsirkin supplied a reproducer for the VLAN case. Willem de Bruijn reviewed the patches. Dumazet noted a similar truncation still exists on the BPF flow-dissector early path and plans a separate follow-up.