]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
tcp: secure_seq: add back ports to TS offset
authorEric Dumazet <edumazet@google.com>
Mon, 2 Mar 2026 20:55:27 +0000 (20:55 +0000)
committerJakub Kicinski <kuba@kernel.org>
Thu, 5 Mar 2026 01:44:35 +0000 (17:44 -0800)
This reverts 28ee1b746f49 ("secure_seq: downgrade to per-host timestamp offsets")

tcp_tw_recycle went away in 2017.

Zhouyan Deng reported off-path TCP source port leakage via
SYN cookie side-channel that can be fixed in multiple ways.

One of them is to bring back TCP ports in TS offset randomization.

As a bonus, we perform a single siphash() computation
to provide both an ISN and a TS offset.

Fixes: 28ee1b746f49 ("secure_seq: downgrade to per-host timestamp offsets")
Reported-by: Zhouyan Deng <dengzhouyan_nwpu@163.com>
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Acked-by: Florian Westphal <fw@strlen.de>
Link: https://patch.msgid.link/20260302205527.1982836-1-edumazet@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
include/net/secure_seq.h
include/net/tcp.h
net/core/secure_seq.c
net/ipv4/syncookies.c
net/ipv4/tcp_input.c
net/ipv4/tcp_ipv4.c
net/ipv6/syncookies.c
net/ipv6/tcp_ipv6.c

index cddebafb9f779ebd5d9c02e8ff26c13b5697c7d1..6f996229167b3c3f7861b2d5693ef81b5eed0d74 100644 (file)
@@ -5,16 +5,47 @@
 #include <linux/types.h>
 
 struct net;
+extern struct net init_net;
+
+union tcp_seq_and_ts_off {
+       struct {
+               u32 seq;
+               u32 ts_off;
+       };
+       u64 hash64;
+};
 
 u64 secure_ipv4_port_ephemeral(__be32 saddr, __be32 daddr, __be16 dport);
 u64 secure_ipv6_port_ephemeral(const __be32 *saddr, const __be32 *daddr,
                               __be16 dport);
-u32 secure_tcp_seq(__be32 saddr, __be32 daddr,
-                  __be16 sport, __be16 dport);
-u32 secure_tcp_ts_off(const struct net *net, __be32 saddr, __be32 daddr);
-u32 secure_tcpv6_seq(const __be32 *saddr, const __be32 *daddr,
-                    __be16 sport, __be16 dport);
-u32 secure_tcpv6_ts_off(const struct net *net,
-                       const __be32 *saddr, const __be32 *daddr);
+union tcp_seq_and_ts_off
+secure_tcp_seq_and_ts_off(const struct net *net, __be32 saddr, __be32 daddr,
+                         __be16 sport, __be16 dport);
+
+static inline u32 secure_tcp_seq(__be32 saddr, __be32 daddr,
+                                __be16 sport, __be16 dport)
+{
+       union tcp_seq_and_ts_off ts;
+
+       ts = secure_tcp_seq_and_ts_off(&init_net, saddr, daddr,
+                                      sport, dport);
+
+       return ts.seq;
+}
+
+union tcp_seq_and_ts_off
+secure_tcpv6_seq_and_ts_off(const struct net *net, const __be32 *saddr,
+                           const __be32 *daddr,
+                           __be16 sport, __be16 dport);
+
+static inline u32 secure_tcpv6_seq(const __be32 *saddr, const __be32 *daddr,
+                                  __be16 sport, __be16 dport)
+{
+       union tcp_seq_and_ts_off ts;
+
+       ts = secure_tcpv6_seq_and_ts_off(&init_net, saddr, daddr,
+                                        sport, dport);
 
+       return ts.seq;
+}
 #endif /* _NET_SECURE_SEQ */
index eb8bf63fdafc3243469f293fd06aef0ce086c5a4..978eea2d5df04f378dceb251025bee3101120f69 100644 (file)
@@ -43,6 +43,7 @@
 #include <net/dst.h>
 #include <net/mptcp.h>
 #include <net/xfrm.h>
+#include <net/secure_seq.h>
 
 #include <linux/seq_file.h>
 #include <linux/memcontrol.h>
@@ -2464,8 +2465,9 @@ struct tcp_request_sock_ops {
                                       struct flowi *fl,
                                       struct request_sock *req,
                                       u32 tw_isn);
-       u32 (*init_seq)(const struct sk_buff *skb);
-       u32 (*init_ts_off)(const struct net *net, const struct sk_buff *skb);
+       union tcp_seq_and_ts_off (*init_seq_and_ts_off)(
+                                       const struct net *net,
+                                       const struct sk_buff *skb);
        int (*send_synack)(const struct sock *sk, struct dst_entry *dst,
                           struct flowi *fl, struct request_sock *req,
                           struct tcp_fastopen_cookie *foc,
index 9a39656804513dcef0888d280d8289913ef27eea..6a6f2cda5aaef82074718439920c75a75592e967 100644 (file)
@@ -20,7 +20,6 @@
 #include <net/tcp.h>
 
 static siphash_aligned_key_t net_secret;
-static siphash_aligned_key_t ts_secret;
 
 #define EPHEMERAL_PORT_SHUFFLE_PERIOD (10 * HZ)
 
@@ -28,11 +27,6 @@ static __always_inline void net_secret_init(void)
 {
        net_get_random_once(&net_secret, sizeof(net_secret));
 }
-
-static __always_inline void ts_secret_init(void)
-{
-       net_get_random_once(&ts_secret, sizeof(ts_secret));
-}
 #endif
 
 #ifdef CONFIG_INET
@@ -53,28 +47,9 @@ static u32 seq_scale(u32 seq)
 #endif
 
 #if IS_ENABLED(CONFIG_IPV6)
-u32 secure_tcpv6_ts_off(const struct net *net,
-                       const __be32 *saddr, const __be32 *daddr)
-{
-       const struct {
-               struct in6_addr saddr;
-               struct in6_addr daddr;
-       } __aligned(SIPHASH_ALIGNMENT) combined = {
-               .saddr = *(struct in6_addr *)saddr,
-               .daddr = *(struct in6_addr *)daddr,
-       };
-
-       if (READ_ONCE(net->ipv4.sysctl_tcp_timestamps) != 1)
-               return 0;
-
-       ts_secret_init();
-       return siphash(&combined, offsetofend(typeof(combined), daddr),
-                      &ts_secret);
-}
-EXPORT_IPV6_MOD(secure_tcpv6_ts_off);
-
-u32 secure_tcpv6_seq(const __be32 *saddr, const __be32 *daddr,
-                    __be16 sport, __be16 dport)
+union tcp_seq_and_ts_off
+secure_tcpv6_seq_and_ts_off(const struct net *net, const __be32 *saddr,
+                           const __be32 *daddr, __be16 sport, __be16 dport)
 {
        const struct {
                struct in6_addr saddr;
@@ -87,14 +62,20 @@ u32 secure_tcpv6_seq(const __be32 *saddr, const __be32 *daddr,
                .sport = sport,
                .dport = dport
        };
-       u32 hash;
+       union tcp_seq_and_ts_off st;
 
        net_secret_init();
-       hash = siphash(&combined, offsetofend(typeof(combined), dport),
-                      &net_secret);
-       return seq_scale(hash);
+
+       st.hash64 = siphash(&combined, offsetofend(typeof(combined), dport),
+                           &net_secret);
+
+       if (READ_ONCE(net->ipv4.sysctl_tcp_timestamps) != 1)
+               st.ts_off = 0;
+
+       st.seq = seq_scale(st.seq);
+       return st;
 }
-EXPORT_SYMBOL(secure_tcpv6_seq);
+EXPORT_SYMBOL(secure_tcpv6_seq_and_ts_off);
 
 u64 secure_ipv6_port_ephemeral(const __be32 *saddr, const __be32 *daddr,
                               __be16 dport)
@@ -118,33 +99,30 @@ EXPORT_SYMBOL(secure_ipv6_port_ephemeral);
 #endif
 
 #ifdef CONFIG_INET
-u32 secure_tcp_ts_off(const struct net *net, __be32 saddr, __be32 daddr)
-{
-       if (READ_ONCE(net->ipv4.sysctl_tcp_timestamps) != 1)
-               return 0;
-
-       ts_secret_init();
-       return siphash_2u32((__force u32)saddr, (__force u32)daddr,
-                           &ts_secret);
-}
-
 /* secure_tcp_seq_and_tsoff(a, b, 0, d) == secure_ipv4_port_ephemeral(a, b, d),
  * but fortunately, `sport' cannot be 0 in any circumstances. If this changes,
  * it would be easy enough to have the former function use siphash_4u32, passing
  * the arguments as separate u32.
  */
-u32 secure_tcp_seq(__be32 saddr, __be32 daddr,
-                  __be16 sport, __be16 dport)
+union tcp_seq_and_ts_off
+secure_tcp_seq_and_ts_off(const struct net *net, __be32 saddr, __be32 daddr,
+                         __be16 sport, __be16 dport)
 {
-       u32 hash;
+       u32 ports = (__force u32)sport << 16 | (__force u32)dport;
+       union tcp_seq_and_ts_off st;
 
        net_secret_init();
-       hash = siphash_3u32((__force u32)saddr, (__force u32)daddr,
-                           (__force u32)sport << 16 | (__force u32)dport,
-                           &net_secret);
-       return seq_scale(hash);
+
+       st.hash64 = siphash_3u32((__force u32)saddr, (__force u32)daddr,
+                                ports, &net_secret);
+
+       if (READ_ONCE(net->ipv4.sysctl_tcp_timestamps) != 1)
+               st.ts_off = 0;
+
+       st.seq = seq_scale(st.seq);
+       return st;
 }
-EXPORT_SYMBOL_GPL(secure_tcp_seq);
+EXPORT_SYMBOL_GPL(secure_tcp_seq_and_ts_off);
 
 u64 secure_ipv4_port_ephemeral(__be32 saddr, __be32 daddr, __be16 dport)
 {
index 061751aabc8e16c5d536a19f7b920d1bca2b0f4f..fc3affd9c8014b1d4e9f161421a7753717cdcd73 100644 (file)
@@ -378,9 +378,14 @@ static struct request_sock *cookie_tcp_check(struct net *net, struct sock *sk,
        tcp_parse_options(net, skb, &tcp_opt, 0, NULL);
 
        if (tcp_opt.saw_tstamp && tcp_opt.rcv_tsecr) {
-               tsoff = secure_tcp_ts_off(net,
-                                         ip_hdr(skb)->daddr,
-                                         ip_hdr(skb)->saddr);
+               union tcp_seq_and_ts_off st;
+
+               st = secure_tcp_seq_and_ts_off(net,
+                                              ip_hdr(skb)->daddr,
+                                              ip_hdr(skb)->saddr,
+                                              tcp_hdr(skb)->dest,
+                                              tcp_hdr(skb)->source);
+               tsoff = st.ts_off;
                tcp_opt.rcv_tsecr -= tsoff;
        }
 
index 7b03f2460751f366dd6cf15505e49ae26cd6466e..cba89733d1216bc2663758b4bda21984835e6055 100644 (file)
@@ -7646,6 +7646,7 @@ int tcp_conn_request(struct request_sock_ops *rsk_ops,
        const struct tcp_sock *tp = tcp_sk(sk);
        struct net *net = sock_net(sk);
        struct sock *fastopen_sk = NULL;
+       union tcp_seq_and_ts_off st;
        struct request_sock *req;
        bool want_cookie = false;
        struct dst_entry *dst;
@@ -7715,9 +7716,12 @@ int tcp_conn_request(struct request_sock_ops *rsk_ops,
        if (!dst)
                goto drop_and_free;
 
+       if (tmp_opt.tstamp_ok || (!want_cookie && !isn))
+               st = af_ops->init_seq_and_ts_off(net, skb);
+
        if (tmp_opt.tstamp_ok) {
                tcp_rsk(req)->req_usec_ts = dst_tcp_usec_ts(dst);
-               tcp_rsk(req)->ts_off = af_ops->init_ts_off(net, skb);
+               tcp_rsk(req)->ts_off = st.ts_off;
        }
        if (!want_cookie && !isn) {
                int max_syn_backlog = READ_ONCE(net->ipv4.sysctl_max_syn_backlog);
@@ -7739,7 +7743,7 @@ int tcp_conn_request(struct request_sock_ops *rsk_ops,
                        goto drop_and_release;
                }
 
-               isn = af_ops->init_seq(skb);
+               isn = st.seq;
        }
 
        tcp_ecn_create_request(req, skb, sk, dst);
index 910c25cb24e1099c5480de276d6a3f27c33397ec..c7b2463c2e25445e9ceb902c7a769e03a41981dd 100644 (file)
@@ -105,17 +105,14 @@ static DEFINE_PER_CPU(struct sock_bh_locked, ipv4_tcp_sk) = {
 
 static DEFINE_MUTEX(tcp_exit_batch_mutex);
 
-static u32 tcp_v4_init_seq(const struct sk_buff *skb)
+static union tcp_seq_and_ts_off
+tcp_v4_init_seq_and_ts_off(const struct net *net, const struct sk_buff *skb)
 {
-       return secure_tcp_seq(ip_hdr(skb)->daddr,
-                             ip_hdr(skb)->saddr,
-                             tcp_hdr(skb)->dest,
-                             tcp_hdr(skb)->source);
-}
-
-static u32 tcp_v4_init_ts_off(const struct net *net, const struct sk_buff *skb)
-{
-       return secure_tcp_ts_off(net, ip_hdr(skb)->daddr, ip_hdr(skb)->saddr);
+       return secure_tcp_seq_and_ts_off(net,
+                                        ip_hdr(skb)->daddr,
+                                        ip_hdr(skb)->saddr,
+                                        tcp_hdr(skb)->dest,
+                                        tcp_hdr(skb)->source);
 }
 
 int tcp_twsk_unique(struct sock *sk, struct sock *sktw, void *twp)
@@ -327,15 +324,16 @@ int tcp_v4_connect(struct sock *sk, struct sockaddr_unsized *uaddr, int addr_len
        rt = NULL;
 
        if (likely(!tp->repair)) {
+               union tcp_seq_and_ts_off st;
+
+               st = secure_tcp_seq_and_ts_off(net,
+                                              inet->inet_saddr,
+                                              inet->inet_daddr,
+                                              inet->inet_sport,
+                                              usin->sin_port);
                if (!tp->write_seq)
-                       WRITE_ONCE(tp->write_seq,
-                                  secure_tcp_seq(inet->inet_saddr,
-                                                 inet->inet_daddr,
-                                                 inet->inet_sport,
-                                                 usin->sin_port));
-               WRITE_ONCE(tp->tsoffset,
-                          secure_tcp_ts_off(net, inet->inet_saddr,
-                                            inet->inet_daddr));
+                       WRITE_ONCE(tp->write_seq, st.seq);
+               WRITE_ONCE(tp->tsoffset, st.ts_off);
        }
 
        atomic_set(&inet->inet_id, get_random_u16());
@@ -1677,8 +1675,7 @@ const struct tcp_request_sock_ops tcp_request_sock_ipv4_ops = {
        .cookie_init_seq =      cookie_v4_init_sequence,
 #endif
        .route_req      =       tcp_v4_route_req,
-       .init_seq       =       tcp_v4_init_seq,
-       .init_ts_off    =       tcp_v4_init_ts_off,
+       .init_seq_and_ts_off    =       tcp_v4_init_seq_and_ts_off,
        .send_synack    =       tcp_v4_send_synack,
 };
 
index 7e007f013ec827c99bcab4ceb85eb35e9242b439..4f6f0d751d6c533231ca0397319935dc90ba4dba 100644 (file)
@@ -151,9 +151,14 @@ static struct request_sock *cookie_tcp_check(struct net *net, struct sock *sk,
        tcp_parse_options(net, skb, &tcp_opt, 0, NULL);
 
        if (tcp_opt.saw_tstamp && tcp_opt.rcv_tsecr) {
-               tsoff = secure_tcpv6_ts_off(net,
-                                           ipv6_hdr(skb)->daddr.s6_addr32,
-                                           ipv6_hdr(skb)->saddr.s6_addr32);
+               union tcp_seq_and_ts_off st;
+
+               st = secure_tcpv6_seq_and_ts_off(net,
+                                                ipv6_hdr(skb)->daddr.s6_addr32,
+                                                ipv6_hdr(skb)->saddr.s6_addr32,
+                                                tcp_hdr(skb)->dest,
+                                                tcp_hdr(skb)->source);
+               tsoff = st.ts_off;
                tcp_opt.rcv_tsecr -= tsoff;
        }
 
index 5195a46b951ea3b51fa7ae5a7f2a1b6c49f578c8..bb09d5ccf5990dc8c86829e4b54fbe896cfe2505 100644 (file)
@@ -105,18 +105,14 @@ static void inet6_sk_rx_dst_set(struct sock *sk, const struct sk_buff *skb)
        }
 }
 
-static u32 tcp_v6_init_seq(const struct sk_buff *skb)
+static union tcp_seq_and_ts_off
+tcp_v6_init_seq_and_ts_off(const struct net *net, const struct sk_buff *skb)
 {
-       return secure_tcpv6_seq(ipv6_hdr(skb)->daddr.s6_addr32,
-                               ipv6_hdr(skb)->saddr.s6_addr32,
-                               tcp_hdr(skb)->dest,
-                               tcp_hdr(skb)->source);
-}
-
-static u32 tcp_v6_init_ts_off(const struct net *net, const struct sk_buff *skb)
-{
-       return secure_tcpv6_ts_off(net, ipv6_hdr(skb)->daddr.s6_addr32,
-                                  ipv6_hdr(skb)->saddr.s6_addr32);
+       return secure_tcpv6_seq_and_ts_off(net,
+                                          ipv6_hdr(skb)->daddr.s6_addr32,
+                                          ipv6_hdr(skb)->saddr.s6_addr32,
+                                          tcp_hdr(skb)->dest,
+                                          tcp_hdr(skb)->source);
 }
 
 static int tcp_v6_pre_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
@@ -320,14 +316,16 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr,
        sk_set_txhash(sk);
 
        if (likely(!tp->repair)) {
+               union tcp_seq_and_ts_off st;
+
+               st = secure_tcpv6_seq_and_ts_off(net,
+                                                np->saddr.s6_addr32,
+                                                sk->sk_v6_daddr.s6_addr32,
+                                                inet->inet_sport,
+                                                inet->inet_dport);
                if (!tp->write_seq)
-                       WRITE_ONCE(tp->write_seq,
-                                  secure_tcpv6_seq(np->saddr.s6_addr32,
-                                                   sk->sk_v6_daddr.s6_addr32,
-                                                   inet->inet_sport,
-                                                   inet->inet_dport));
-               tp->tsoffset = secure_tcpv6_ts_off(net, np->saddr.s6_addr32,
-                                                  sk->sk_v6_daddr.s6_addr32);
+                       WRITE_ONCE(tp->write_seq, st.seq);
+               tp->tsoffset = st.ts_off;
        }
 
        if (tcp_fastopen_defer_connect(sk, &err))
@@ -817,8 +815,7 @@ const struct tcp_request_sock_ops tcp_request_sock_ipv6_ops = {
        .cookie_init_seq =      cookie_v6_init_sequence,
 #endif
        .route_req      =       tcp_v6_route_req,
-       .init_seq       =       tcp_v6_init_seq,
-       .init_ts_off    =       tcp_v6_init_ts_off,
+       .init_seq_and_ts_off    = tcp_v6_init_seq_and_ts_off,
        .send_synack    =       tcp_v6_send_synack,
 };