LVS
lvs-devel
Google
 
Web LinuxVirtualServer.org

[PATCH nf] ipvs: skip IPv6 extension headers in TCP state lookup

To: netdev@xxxxxxxxxxxxxxx
Subject: [PATCH nf] ipvs: skip IPv6 extension headers in TCP state lookup
Cc: Yizhou Zhao <zhaoyz24@xxxxxxxxxxxxxxxxxxxxx>, Simon Horman <horms@xxxxxxxxxxxx>, Julian Anastasov <ja@xxxxxx>, Pablo Neira Ayuso <pablo@xxxxxxxxxxxxx>, Florian Westphal <fw@xxxxxxxxx>, Phil Sutter <phil@xxxxxx>, "David S. Miller" <davem@xxxxxxxxxxxxx>, Eric Dumazet <edumazet@xxxxxxxxxx>, Jakub Kicinski <kuba@xxxxxxxxxx>, Paolo Abeni <pabeni@xxxxxxxxxx>, lvs-devel@xxxxxxxxxxxxxxx, netfilter-devel@xxxxxxxxxxxxxxx, coreteam@xxxxxxxxxxxxx, linux-kernel@xxxxxxxxxxxxxxx, Yuxiang Yang <yangyx22@xxxxxxxxxxxxxxxxxxxxx>, Ao Wang <wangao@xxxxxxxxxx>, Xuewei Feng <fengxw06@xxxxxxx>, Qi Li <qli01@xxxxxxxxxxxxxxx>, Ke Xu <xuke@xxxxxxxxxxxxxxx>, stable@xxxxxxxxxxxxxxx
From: Yizhou Zhao <zhaoyz24@xxxxxxxxxxxxxxxxxxxxx>
Date: Sun, 5 Jul 2026 20:56:57 +0800
tcp_state_transition() reads the TCP header again in order to drive the
IPVS TCP state table.  For IPv6 it computes the offset with
sizeof(struct ipv6hdr), while the surrounding IPVS code uses iph->len from
ip_vs_fill_iph_skb(), where ipv6_find_hdr() has already skipped extension
headers and found the real transport header.

This makes the state machine read from the wrong offset for IPv6 TCP
packets that carry extension headers.  For example, a SYN packet with an
8-byte destination options header can be scheduled correctly by
tcp_conn_schedule(), but tcp_state_transition() reads a byte from the real
TCP sequence number as the TCP flags.  An attacker can therefore make an
uncompleted SYN move from NONE to ESTABLISHED or CLOSE, changing the
connection timeout and active/inactive destination counters.  In the
ESTABLISHED case, the half-open connection gets the 15 minute established
timeout instead of the shorter SYN_RECV timeout, which can be used for a
remote denial of service against an IPv6 IPVS TCP service.

Use ip_vs_fill_iph_skb() in tcp_state_transition() and read the TCP header
from iph.len, matching tcp_conn_schedule() and the TCP NAT handlers.  For
IPv4 and IPv6 packets without extension headers this preserves the existing
offset.

Fixes: 0bbdd42b7efa ("IPVS: Extend protocol DNAT/SNAT and state handlers")
Cc: stable@xxxxxxxxxxxxxxx
Reported-by: Yizhou Zhao <zhaoyz24@xxxxxxxxxxxxxxxxxxxxx>
Reported-by: Yuxiang Yang <yangyx22@xxxxxxxxxxxxxxxxxxxxx>
Reported-by: Ao Wang <wangao@xxxxxxxxxx>
Reported-by: Xuewei Feng <fengxw06@xxxxxxx>
Reported-by: Qi Li <qli01@xxxxxxxxxxxxxxx>
Reported-by: Ke Xu <xuke@xxxxxxxxxxxxxxx>
Assisted-by: Claude-Code:GLM-5.2
Signed-off-by: Yizhou Zhao <zhaoyz24@xxxxxxxxxxxxxxxxxxxxx>
---
 net/netfilter/ipvs/ip_vs_proto_tcp.c | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/net/netfilter/ipvs/ip_vs_proto_tcp.c 
b/net/netfilter/ipvs/ip_vs_proto_tcp.c
index 8cc0a8ce6241..6fde2165a8d6 100644
--- a/net/netfilter/ipvs/ip_vs_proto_tcp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_tcp.c
@@ -581,15 +581,13 @@ tcp_state_transition(struct ip_vs_conn *cp, int direction,
                     const struct sk_buff *skb,
                     struct ip_vs_proto_data *pd)
 {
        struct tcphdr _tcph, *th;
+       struct ip_vs_iphdr iph;
 
-#ifdef CONFIG_IP_VS_IPV6
-       int ihl = cp->af == AF_INET ? ip_hdrlen(skb) : sizeof(struct ipv6hdr);
-#else
-       int ihl = ip_hdrlen(skb);
-#endif
+       if (!ip_vs_fill_iph_skb(cp->af, skb, false, &iph))
+               return;
 
-       th = skb_header_pointer(skb, ihl, sizeof(_tcph), &_tcph);
+       th = skb_header_pointer(skb, iph.len, sizeof(_tcph), &_tcph);
        if (th == NULL)
                return;
 
-- 
2.47.3



<Prev in Thread] Current Thread [Next in Thread>
  • [PATCH nf] ipvs: skip IPv6 extension headers in TCP state lookup, Yizhou Zhao <=