Detailed Technical Analysis
1. Crash Manifestation and Context
The kernel crashes with an invalid opcode Oops (0f 0b) at net/core/skbuff.c:2903 inside __pskb_pull_tail():
BUG_ON(skb_copy_bits(skb, skb_headlen(skb),
skb_tail_pointer(skb), delta));
In the crash report, register RBX (and R10) holds 0xffffff2 (-14 = -EFAULT), indicating that skb_copy_bits() returned -EFAULT. Because __pskb_pull_tail() asserts that copying the tail bits into the newly allocated/expanded head buffer must succeed, this assertion failure triggers BUG().
The stack trace reveals two coupled paths during a local SCTP transmission:
-
Transmit / Egress Path (<TASK>):
- User space issues a
sendmmsg() syscall on an SCTP socket.
- SCTP constructs an outgoing packet via
sctp_packet_pack() (net/sctp/output.c), which configures SCTP GSO (GSO_BY_FRAGS) where individual data chunks are placed in chained sk_buff structures linked in skb_shinfo(head)->frag_list.
- The packet is transmitted via loopback/veth (
dev_queue_xmit()), which queues the packet into the per-CPU backlog queue and returns.
- On exit from
dev_queue_xmit(), rcu_read_unlock_bh() / local_bh_enable() triggers pending softirqs.
-
Receive / Ingress Path (<IRQ>):
- Softirq processing (
net_rx_action() -> process_backlog()) passes the packet to IPv4 receive (ip_rcv() -> ip_local_deliver_finish()).
- The IPv4 layer pulls the IP header via
__skb_pull(skb, skb_network_header_len(skb)) and invokes sctp_rcv() (net/sctp/input.c:120).
2. Root Cause Analysis
In sctp_rcv() (net/sctp/input.c:120):
bool is_gso = skb_is_gso(skb) && skb_is_gso_sctp(skb);
...
if (((!is_gso || skb_cloned(skb)) && skb_linearize(skb)) ||
!pskb_may_pull(skb, sizeof(struct sctphdr)))
goto discard_it;
- If the received
skb is an SCTP GSO packet (which stores its chunks in skb_shinfo(skb)->frag_list), but happens to be cloned (for example, by a packet sniffer/tap via dev_queue_xmit_nit or skb_share_check), or if GSO flags were cleared or not set, sctp_rcv() attempts to linearize the entire buffer using skb_linearize(skb).
skb_linearize() invokes __skb_linearize(), which in turn calls __pskb_pull_tail(skb, skb->data_len).
- In
__pskb_pull_tail(skb, delta):
pskb_expand_head(skb, ...) is called to reallocate head room because skb_cloned(skb) is true. During this expansion, skb_clone_fraglist(skb) is called to reference the fragments.
skb_copy_bits(skb, skb_headlen(skb), skb_tail_pointer(skb), delta) is executed to pull all data_len bytes into the newly contiguous head buffer.
skb_copy_bits() traverses skb_shinfo(skb)->frag_list. If there is a length mismatch or fragmentation geometry inconsistency (e.g. cumulative lengths in frag_list being shorter than skb->data_len, or recursive fragment bounds checks failing), skb_copy_bits() does not complete all len bytes and jumps to fault: return -EFAULT;.
__pskb_pull_tail hits BUG_ON(...) on receiving -EFAULT.
3. Verification & Inconclusive Points
- Verified:
__pskb_pull_tail() executes BUG_ON(skb_copy_bits(...)) because skb_copy_bits() returned -EFAULT.
- The calling context is
sctp_rcv() calling skb_linearize(skb).
- In a standard production kernel without debug sanitizers,
BUG_ON() triggers an invalid opcode trap (ud2), halting the system.
- Inconclusive / Caveats:
- The precise event that introduced the geometry discrepancy between
skb->data_len and the frag_list lengths could not be definitively isolated (candidate triggers include interaction between network header stripping and linearization, packet trimming via pskb_trim_rcsum in the IP layer, or cloned frag_list state handling during pskb_expand_head).
4. Classification & Impact Assessment
- RootCauseSummary: When receiving a nonlinear SCTP packet (such as an SCTP GSO packet using
frag_list), sctp_rcv() attempts to linearize the packet with skb_linearize() if it is cloned or not recognized as GSO. In __pskb_pull_tail(), skb_copy_bits() fails with -EFAULT due to a mismatch between data_len and the fragment list, triggering a BUG_ON() panic.
- RootCauseFiles:
net/sctp/input.c, net/core/skbuff.c
- MemoryCorruption:
false (the check fails cleanly before any illegal memory write; the panic is due to an explicit BUG_ON() assertion).
- InfoLeak:
"none" (no kernel memory is disclosed).
- RawDenialOfService:
true (BUG_ON() unconditionally panics the kernel on production builds).
- IsNetCandidate:
true (networking stack, SCTP, and core skbuff).
- IsKVMCandidate:
false (no relation to KVM/hypervisors).