tcp: add sanity tests in tcp_add_backlog() [Linux 5.1]

tcp: add sanity tests in tcp_add_backlog() [Linux 5.1]

This Linux kernel change "tcp: add sanity tests in tcp_add_backlog()" is included in the Linux 5.1 release. This change is authored by Eric Dumazet <edumazet [at] google.com> on Fri Apr 26 10:10:05 2019 -0700. The commit for this change in Linux stable tree is ca2fe29 (patch).

tcp: add sanity tests in tcp_add_backlog()

Richard and Bruno both reported that my commit added a bug,
and Bruno was able to determine the problem came when a segment
wih a FIN packet was coalesced to a prior one in tcp backlog queue.

It turns out the header prediction in tcp_rcv_established()
looks back to TCP headers in the packet, not in the metadata
(aka TCP_SKB_CB(skb)->tcp_flags)

The fast path in tcp_rcv_established() is not supposed to
handle a FIN flag (it does not call tcp_fin())

Therefore we need to make sure to propagate the FIN flag,
so that the coalesced packet does not go through the fast path,
the same than a GRO packet carrying a FIN flag.

While we are at it, make sure we do not coalesce packets with
RST or SYN, or if they do not have ACK set.

Many thanks to Richard and Bruno for pinpointing the bad commit,
and to Richard for providing a first version of the fix.

Fixes: 4f693b55c3d2 ("tcp: implement coalescing on backlog queue")
Signed-off-by: Eric Dumazet <[email protected]>
Reported-by: Richard Purdie <[email protected]>
Reported-by: Bruno PrĂ©mont <[email protected]>
Signed-off-by: David S. Miller <[email protected]>

There are 13 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 net/ipv4/tcp_ipv4.c | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 2f8039a..a289694 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -1673,7 +1673,9 @@ bool tcp_add_backlog(struct sock *sk, struct sk_buff *skb)
    if (TCP_SKB_CB(tail)->end_seq != TCP_SKB_CB(skb)->seq ||
        TCP_SKB_CB(tail)->ip_dsfield != TCP_SKB_CB(skb)->ip_dsfield ||
        ((TCP_SKB_CB(tail)->tcp_flags |
-         TCP_SKB_CB(skb)->tcp_flags) & TCPHDR_URG) ||
+         TCP_SKB_CB(skb)->tcp_flags) & (TCPHDR_SYN | TCPHDR_RST | TCPHDR_URG)) ||
+       !((TCP_SKB_CB(tail)->tcp_flags &
+         TCP_SKB_CB(skb)->tcp_flags) & TCPHDR_ACK) ||
        ((TCP_SKB_CB(tail)->tcp_flags ^
          TCP_SKB_CB(skb)->tcp_flags) & (TCPHDR_ECE | TCPHDR_CWR)) ||
 #ifdef CONFIG_TLS_DEVICE
@@ -1692,6 +1694,15 @@ bool tcp_add_backlog(struct sock *sk, struct sk_buff *skb)
        if (after(TCP_SKB_CB(skb)->ack_seq, TCP_SKB_CB(tail)->ack_seq))
            TCP_SKB_CB(tail)->ack_seq = TCP_SKB_CB(skb)->ack_seq;

+       /* We have to update both TCP_SKB_CB(tail)->tcp_flags and
+        * thtail->fin, so that the fast path in tcp_rcv_established()
+        * is not entered if we append a packet with a FIN.
+        * SYN, RST, URG are not present.
+        * ACK is set on both packets.
+        * PSH : we do not really care in TCP stack,
+        *       at least for 'GRO' packets.
+        */
+       thtail->fin |= th->fin;
        TCP_SKB_CB(tail)->tcp_flags |= TCP_SKB_CB(skb)->tcp_flags;

        if (TCP_SKB_CB(skb)->has_rxtstamp) {

Leave a Reply

Your email address will not be published. Required fields are marked *