tailscale/tailscale
Infinite loop in getGSOSizeFromControl when UDP_GRO control message is not first
Summary
Context: The
getGSOSizeFromControlfunction innet/batching/conn_linux.goparses socket control messages during UDP packet reception to extract the GSO (Generic Segmentation Offload) size when UDP_GRO is enabled.Bug: The function parses from the original
controlslice instead of the updatedrem(remainder) slice in the loop, causing it to repeatedly parse the first control message indefinitely.Actual vs. expected: When multiple control messages are present and UDP_GRO is not first, the function enters an infinite loop instead of iterating through all messages to find the UDP_GRO message.
Impact: The affected goroutine hangs indefinitely with 100% CPU usage, blocking packet reception and causing connection failure without producing any error message or panic.
Code with bug
The loop condition checks len(rem) but the parsing operation uses control, causing the same message to be parsed repeatedly. The rem variable is updated to the same value each iteration, and the loop never progresses to subsequent messages.
Example
A test program demonstrates the bug by constructing a control message buffer with two messages:
A non-UDP_GRO message (SCM_TIMESTAMP)
A UDP_GRO message with GSO size 1400
Buggy version output:
The function repeatedly parses the same message (Level=1, Type=29) with RemLen staying constant at 24 bytes, never progressing to the second message.
Fixed version output:
The fixed version successfully parses both messages, finding the UDP_GRO message (Level=17/SOL_UDP, Type=104/UDP_GRO) on the second iteration.
Recommended fix
This ensures each iteration parses the next control message from the remainder buffer, allowing the function to progress through all messages until it finds the UDP_GRO message or exhausts the buffer.
The bug has likely gone undetected because the kernel typically sends UDP_GRO as the first or only control message, which the function handles correctly. The bug only manifests when multiple control messages are present and UDP_GRO is not first—a rare condition in typical UDP receive operations.