net/tcp: Add TCP-AO SNE support
authorDmitry Safonov <dima@arista.com>
Mon, 23 Oct 2023 19:22:06 +0000 (20:22 +0100)
committerDavid S. Miller <davem@davemloft.net>
Fri, 27 Oct 2023 09:35:45 +0000 (10:35 +0100)
Add Sequence Number Extension (SNE) for TCP-AO.
This is needed to protect long-living TCP-AO connections from replaying
attacks after sequence number roll-over, see RFC5925 (6.2).

Co-developed-by: Francesco Ruggeri <fruggeri@arista.com>
Signed-off-by: Francesco Ruggeri <fruggeri@arista.com>
Co-developed-by: Salam Noureddine <noureddine@arista.com>
Signed-off-by: Salam Noureddine <noureddine@arista.com>
Signed-off-by: Dmitry Safonov <dima@arista.com>
Acked-by: David Ahern <dsahern@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/tcp_ao.h
net/ipv4/tcp_ao.c
net/ipv4/tcp_input.c
net/ipv4/tcp_ipv4.c
net/ipv4/tcp_minisocks.c
net/ipv6/tcp_ipv6.c

index cfb55bd..0c3516d 100644 (file)
@@ -95,6 +95,25 @@ struct tcp_ao_info {
                                __unused        :31;
        __be32                  lisn;
        __be32                  risn;
+       /* Sequence Number Extension (SNE) are upper 4 bytes for SEQ,
+        * that protect TCP-AO connection from replayed old TCP segments.
+        * See RFC5925 (6.2).
+        * In order to get correct SNE, there's a helper tcp_ao_compute_sne().
+        * It needs SEQ basis to understand whereabouts are lower SEQ numbers.
+        * According to that basis vector, it can provide incremented SNE
+        * when SEQ rolls over or provide decremented SNE when there's
+        * a retransmitted segment from before-rolling over.
+        * - for request sockets such basis is rcv_isn/snt_isn, which seems
+        *   good enough as it's unexpected to receive 4 Gbytes on reqsk.
+        * - for full sockets the basis is rcv_nxt/snd_una. snd_una is
+        *   taken instead of snd_nxt as currently it's easier to track
+        *   in tcp_snd_una_update(), rather than updating SNE in all
+        *   WRITE_ONCE(tp->snd_nxt, ...)
+        * - for time-wait sockets the basis is tw_rcv_nxt/tw_snd_nxt.
+        *   tw_snd_nxt is not expected to change, while tw_rcv_nxt may.
+        */
+       u32                     snd_sne;
+       u32                     rcv_sne;
        refcount_t              refcnt;         /* Protects twsk destruction */
        struct rcu_head         rcu;
 };
@@ -147,6 +166,7 @@ enum skb_drop_reason tcp_inbound_ao_hash(struct sock *sk,
                        const struct sk_buff *skb, unsigned short int family,
                        const struct request_sock *req,
                        const struct tcp_ao_hdr *aoh);
+u32 tcp_ao_compute_sne(u32 next_sne, u32 next_seq, u32 seq);
 struct tcp_ao_key *tcp_ao_do_lookup(const struct sock *sk,
                                    const union tcp_ao_addr *addr,
                                    int family, int sndid, int rcvid);
@@ -156,7 +176,7 @@ int tcp_ao_hash_hdr(unsigned short family, char *ao_hash,
                    const union tcp_ao_addr *saddr,
                    const struct tcphdr *th, u32 sne);
 int tcp_ao_prepare_reset(const struct sock *sk, struct sk_buff *skb,
-                        const struct tcp_ao_hdr *aoh, int l3index,
+                        const struct tcp_ao_hdr *aoh, int l3index, u32 seq,
                         struct tcp_ao_key **key, char **traffic_key,
                         bool *allocated_traffic_key, u8 *keyid, u32 *sne);
 
index 1097e99..7e14bcd 100644 (file)
@@ -401,6 +401,21 @@ static int tcp_ao_hash_pseudoheader(unsigned short int family,
        return -EAFNOSUPPORT;
 }
 
+u32 tcp_ao_compute_sne(u32 next_sne, u32 next_seq, u32 seq)
+{
+       u32 sne = next_sne;
+
+       if (before(seq, next_seq)) {
+               if (seq > next_seq)
+                       sne--;
+       } else {
+               if (seq < next_seq)
+                       sne++;
+       }
+
+       return sne;
+}
+
 /* tcp_ao_hash_sne(struct tcp_sigpool *hp)
  * @hp - used for hashing
  * @sne - sne value
@@ -611,7 +626,7 @@ struct tcp_ao_key *tcp_v4_ao_lookup(const struct sock *sk, struct sock *addr_sk,
 }
 
 int tcp_ao_prepare_reset(const struct sock *sk, struct sk_buff *skb,
-                        const struct tcp_ao_hdr *aoh, int l3index,
+                        const struct tcp_ao_hdr *aoh, int l3index, u32 seq,
                         struct tcp_ao_key **key, char **traffic_key,
                         bool *allocated_traffic_key, u8 *keyid, u32 *sne)
 {
@@ -639,7 +654,7 @@ int tcp_ao_prepare_reset(const struct sock *sk, struct sk_buff *skb,
 
                        sisn = htonl(tcp_rsk(req)->rcv_isn);
                        disn = htonl(tcp_rsk(req)->snt_isn);
-                       *sne = 0;
+                       *sne = tcp_ao_compute_sne(0, tcp_rsk(req)->snt_isn, seq);
                } else {
                        sisn = th->seq;
                        disn = 0;
@@ -670,11 +685,15 @@ int tcp_ao_prepare_reset(const struct sock *sk, struct sk_buff *skb,
                *keyid = (*key)->rcvid;
        } else {
                struct tcp_ao_key *rnext_key;
+               u32 snd_basis;
 
-               if (sk->sk_state == TCP_TIME_WAIT)
+               if (sk->sk_state == TCP_TIME_WAIT) {
                        ao_info = rcu_dereference(tcp_twsk(sk)->ao_info);
-               else
+                       snd_basis = tcp_twsk(sk)->tw_snd_nxt;
+               } else {
                        ao_info = rcu_dereference(tcp_sk(sk)->ao_info);
+                       snd_basis = tcp_sk(sk)->snd_una;
+               }
                if (!ao_info)
                        return -ENOENT;
 
@@ -684,7 +703,8 @@ int tcp_ao_prepare_reset(const struct sock *sk, struct sk_buff *skb,
                *traffic_key = snd_other_key(*key);
                rnext_key = READ_ONCE(ao_info->rnext_key);
                *keyid = rnext_key->rcvid;
-               *sne = 0;
+               *sne = tcp_ao_compute_sne(READ_ONCE(ao_info->snd_sne),
+                                         snd_basis, seq);
        }
        return 0;
 }
@@ -698,6 +718,7 @@ int tcp_ao_transmit_skb(struct sock *sk, struct sk_buff *skb,
        struct tcp_ao_info *ao;
        void *tkey_buf = NULL;
        u8 *traffic_key;
+       u32 sne;
 
        ao = rcu_dereference_protected(tcp_sk(sk)->ao_info,
                                       lockdep_sock_is_held(sk));
@@ -717,8 +738,10 @@ int tcp_ao_transmit_skb(struct sock *sk, struct sk_buff *skb,
                tp->af_specific->ao_calc_key_sk(key, traffic_key,
                                                sk, ao->lisn, disn, true);
        }
+       sne = tcp_ao_compute_sne(READ_ONCE(ao->snd_sne), READ_ONCE(tp->snd_una),
+                                ntohl(th->seq));
        tp->af_specific->calc_ao_hash(hash_location, key, sk, skb, traffic_key,
-                                     hash_location - (u8 *)th, 0);
+                                     hash_location - (u8 *)th, sne);
        kfree(tkey_buf);
        return 0;
 }
@@ -846,7 +869,8 @@ tcp_inbound_ao_hash(struct sock *sk, const struct sk_buff *skb,
                if (unlikely(th->syn && !th->ack))
                        goto verify_hash;
 
-               sne = 0;
+               sne = tcp_ao_compute_sne(info->rcv_sne, tcp_sk(sk)->rcv_nxt,
+                                        ntohl(th->seq));
                /* Established socket, traffic key are cached */
                traffic_key = rcv_other_key(key);
                err = tcp_ao_verify_hash(sk, skb, family, info, aoh, key,
@@ -881,14 +905,16 @@ tcp_inbound_ao_hash(struct sock *sk, const struct sk_buff *skb,
        if ((1 << sk->sk_state) & (TCPF_LISTEN | TCPF_NEW_SYN_RECV)) {
                /* Make the initial syn the likely case here */
                if (unlikely(req)) {
-                       sne = 0;
+                       sne = tcp_ao_compute_sne(0, tcp_rsk(req)->rcv_isn,
+                                                ntohl(th->seq));
                        sisn = htonl(tcp_rsk(req)->rcv_isn);
                        disn = htonl(tcp_rsk(req)->snt_isn);
                } else if (unlikely(th->ack && !th->syn)) {
                        /* Possible syncookie packet */
                        sisn = htonl(ntohl(th->seq) - 1);
                        disn = htonl(ntohl(th->ack_seq) - 1);
-                       sne = 0;
+                       sne = tcp_ao_compute_sne(0, ntohl(sisn),
+                                                ntohl(th->seq));
                } else if (unlikely(!th->syn)) {
                        /* no way to figure out initial sisn/disn - drop */
                        return SKB_DROP_REASON_TCP_FLAGS;
@@ -986,6 +1012,7 @@ void tcp_ao_connect_init(struct sock *sk)
                tp->tcp_header_len += tcp_ao_len(key);
 
                ao_info->lisn = htonl(tp->write_seq);
+               ao_info->snd_sne = 0;
        } else {
                /* Can't happen: tcp_connect() verifies that there's
                 * at least one tcp-ao key that matches the remote peer.
@@ -1021,6 +1048,7 @@ void tcp_ao_finish_connect(struct sock *sk, struct sk_buff *skb)
                return;
 
        WRITE_ONCE(ao->risn, tcp_hdr(skb)->seq);
+       ao->rcv_sne = 0;
 
        hlist_for_each_entry_rcu(key, &ao->head, node)
                tcp_ao_cache_traffic_keys(sk, ao, key);
index de64c0c..67ca721 100644 (file)
@@ -3576,9 +3576,18 @@ static inline bool tcp_may_update_window(const struct tcp_sock *tp,
 static void tcp_snd_una_update(struct tcp_sock *tp, u32 ack)
 {
        u32 delta = ack - tp->snd_una;
+#ifdef CONFIG_TCP_AO
+       struct tcp_ao_info *ao;
+#endif
 
        sock_owned_by_me((struct sock *)tp);
        tp->bytes_acked += delta;
+#ifdef CONFIG_TCP_AO
+       ao = rcu_dereference_protected(tp->ao_info,
+                                      lockdep_sock_is_held((struct sock *)tp));
+       if (ao && ack < tp->snd_una)
+               ao->snd_sne++;
+#endif
        tp->snd_una = ack;
 }
 
@@ -3586,9 +3595,18 @@ static void tcp_snd_una_update(struct tcp_sock *tp, u32 ack)
 static void tcp_rcv_nxt_update(struct tcp_sock *tp, u32 seq)
 {
        u32 delta = seq - tp->rcv_nxt;
+#ifdef CONFIG_TCP_AO
+       struct tcp_ao_info *ao;
+#endif
 
        sock_owned_by_me((struct sock *)tp);
        tp->bytes_received += delta;
+#ifdef CONFIG_TCP_AO
+       ao = rcu_dereference_protected(tp->ao_info,
+                                      lockdep_sock_is_held((struct sock *)tp));
+       if (ao && seq < tp->rcv_nxt)
+               ao->rcv_sne++;
+#endif
        WRITE_ONCE(tp->rcv_nxt, seq);
 }
 
@@ -6456,6 +6474,16 @@ consume:
                 * simultaneous connect with crossed SYNs.
                 * Particularly, it can be connect to self.
                 */
+#ifdef CONFIG_TCP_AO
+               struct tcp_ao_info *ao;
+
+               ao = rcu_dereference_protected(tp->ao_info,
+                                              lockdep_sock_is_held(sk));
+               if (ao) {
+                       WRITE_ONCE(ao->risn, th->seq);
+                       ao->rcv_sne = 0;
+               }
+#endif
                tcp_set_state(sk, TCP_SYN_RECV);
 
                if (tp->rx_opt.saw_tstamp) {
index ece95d5..bdec997 100644 (file)
@@ -676,7 +676,7 @@ static bool tcp_v4_ao_sign_reset(const struct sock *sk, struct sk_buff *skb,
        u8 keyid;
 
        rcu_read_lock();
-       if (tcp_ao_prepare_reset(sk, skb, aoh, l3index,
+       if (tcp_ao_prepare_reset(sk, skb, aoh, l3index, ntohl(reply->seq),
                                 &key, &traffic_key, &allocated_traffic_key,
                                 &keyid, &ao_sne))
                goto out;
@@ -1034,6 +1034,7 @@ static void tcp_v4_timewait_ack(struct sock *sk, struct sk_buff *skb)
                struct tcp_ao_key *rnext_key;
 
                key.traffic_key = snd_other_key(key.ao_key);
+               key.sne = READ_ONCE(ao_info->snd_sne);
                rnext_key = READ_ONCE(ao_info->rnext_key);
                key.rcv_next = rnext_key->rcvid;
                key.type = TCP_KEY_AO;
index 8d941a6..a9807ee 100644 (file)
@@ -51,6 +51,18 @@ tcp_timewait_check_oow_rate_limit(struct inet_timewait_sock *tw,
        return TCP_TW_SUCCESS;
 }
 
+static void twsk_rcv_nxt_update(struct tcp_timewait_sock *tcptw, u32 seq)
+{
+#ifdef CONFIG_TCP_AO
+       struct tcp_ao_info *ao;
+
+       ao = rcu_dereference(tcptw->ao_info);
+       if (unlikely(ao && seq < tcptw->tw_rcv_nxt))
+               WRITE_ONCE(ao->rcv_sne, ao->rcv_sne + 1);
+#endif
+       tcptw->tw_rcv_nxt = seq;
+}
+
 /*
  * * Main purpose of TIME-WAIT state is to close connection gracefully,
  *   when one of ends sits in LAST-ACK or CLOSING retransmitting FIN
@@ -136,7 +148,8 @@ tcp_timewait_state_process(struct inet_timewait_sock *tw, struct sk_buff *skb,
 
                /* FIN arrived, enter true time-wait state. */
                tw->tw_substate   = TCP_TIME_WAIT;
-               tcptw->tw_rcv_nxt = TCP_SKB_CB(skb)->end_seq;
+               twsk_rcv_nxt_update(tcptw, TCP_SKB_CB(skb)->end_seq);
+
                if (tmp_opt.saw_tstamp) {
                        tcptw->tw_ts_recent_stamp = ktime_get_seconds();
                        tcptw->tw_ts_recent       = tmp_opt.rcv_tsval;
index cc899ca..4dcbc13 100644 (file)
@@ -1090,7 +1090,7 @@ static void tcp_v6_send_reset(const struct sock *sk, struct sk_buff *skb)
                int l3index;
 
                l3index = tcp_v6_sdif(skb) ? tcp_v6_iif_l3_slave(skb) : 0;
-               if (tcp_ao_prepare_reset(sk, skb, aoh, l3index,
+               if (tcp_ao_prepare_reset(sk, skb, aoh, l3index, seq,
                                         &key.ao_key, &key.traffic_key,
                                         &allocated_traffic_key,
                                         &key.rcv_next, &key.sne))
@@ -1167,6 +1167,7 @@ static void tcp_v6_timewait_ack(struct sock *sk, struct sk_buff *skb)
                /* rcv_next switches to our rcv_next */
                rnext_key = READ_ONCE(ao_info->rnext_key);
                key.rcv_next = rnext_key->rcvid;
+               key.sne = READ_ONCE(ao_info->snd_sne);
                key.type = TCP_KEY_AO;
 #else
        if (0) {