netfilter: conntrack: allow sctp hearbeat after connection re-use
authorFlorian Westphal <fw@strlen.de>
Tue, 18 Aug 2020 14:15:58 +0000 (16:15 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Thu, 20 Aug 2020 12:13:49 +0000 (14:13 +0200)
If an sctp connection gets re-used, heartbeats are flagged as invalid
because their vtag doesn't match.

Handle this in a similar way as TCP conntrack when it suspects that the
endpoints and conntrack are out-of-sync.

When a HEARTBEAT request fails its vtag validation, flag this in the
conntrack state and accept the packet.

When a HEARTBEAT_ACK is received with an invalid vtag in the reverse
direction after we allowed such a HEARTBEAT through, assume we are
out-of-sync and re-set the vtag info.

v2: remove left-over snippet from an older incarnation that moved
    new_state/old_state assignments, thats not needed so keep that
    as-is.

Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/linux/netfilter/nf_conntrack_sctp.h
net/netfilter/nf_conntrack_proto_sctp.c

index 9a33f17..625f491 100644 (file)
@@ -9,6 +9,8 @@ struct ip_ct_sctp {
        enum sctp_conntrack state;
 
        __be32 vtag[IP_CT_DIR_MAX];
+       u8 last_dir;
+       u8 flags;
 };
 
 #endif /* _NF_CONNTRACK_SCTP_H */
index 4f897b1..810cca2 100644 (file)
@@ -62,6 +62,8 @@ static const unsigned int sctp_timeouts[SCTP_CONNTRACK_MAX] = {
        [SCTP_CONNTRACK_HEARTBEAT_ACKED]        = 210 SECS,
 };
 
+#define        SCTP_FLAG_HEARTBEAT_VTAG_FAILED 1
+
 #define sNO SCTP_CONNTRACK_NONE
 #define        sCL SCTP_CONNTRACK_CLOSED
 #define        sCW SCTP_CONNTRACK_COOKIE_WAIT
@@ -369,6 +371,7 @@ int nf_conntrack_sctp_packet(struct nf_conn *ct,
        u_int32_t offset, count;
        unsigned int *timeouts;
        unsigned long map[256 / sizeof(unsigned long)] = { 0 };
+       bool ignore = false;
 
        if (sctp_error(skb, dataoff, state))
                return -NF_ACCEPT;
@@ -427,15 +430,39 @@ int nf_conntrack_sctp_packet(struct nf_conn *ct,
                        /* Sec 8.5.1 (D) */
                        if (sh->vtag != ct->proto.sctp.vtag[dir])
                                goto out_unlock;
-               } else if (sch->type == SCTP_CID_HEARTBEAT ||
-                          sch->type == SCTP_CID_HEARTBEAT_ACK) {
+               } else if (sch->type == SCTP_CID_HEARTBEAT) {
+                       if (ct->proto.sctp.vtag[dir] == 0) {
+                               pr_debug("Setting %d vtag %x for dir %d\n", sch->type, sh->vtag, dir);
+                               ct->proto.sctp.vtag[dir] = sh->vtag;
+                       } else if (sh->vtag != ct->proto.sctp.vtag[dir]) {
+                               if (test_bit(SCTP_CID_DATA, map) || ignore)
+                                       goto out_unlock;
+
+                               ct->proto.sctp.flags |= SCTP_FLAG_HEARTBEAT_VTAG_FAILED;
+                               ct->proto.sctp.last_dir = dir;
+                               ignore = true;
+                               continue;
+                       } else if (ct->proto.sctp.flags & SCTP_FLAG_HEARTBEAT_VTAG_FAILED) {
+                               ct->proto.sctp.flags &= ~SCTP_FLAG_HEARTBEAT_VTAG_FAILED;
+                       }
+               } else if (sch->type == SCTP_CID_HEARTBEAT_ACK) {
                        if (ct->proto.sctp.vtag[dir] == 0) {
                                pr_debug("Setting vtag %x for dir %d\n",
                                         sh->vtag, dir);
                                ct->proto.sctp.vtag[dir] = sh->vtag;
                        } else if (sh->vtag != ct->proto.sctp.vtag[dir]) {
-                               pr_debug("Verification tag check failed\n");
-                               goto out_unlock;
+                               if (test_bit(SCTP_CID_DATA, map) || ignore)
+                                       goto out_unlock;
+
+                               if ((ct->proto.sctp.flags & SCTP_FLAG_HEARTBEAT_VTAG_FAILED) == 0 ||
+                                   ct->proto.sctp.last_dir == dir)
+                                       goto out_unlock;
+
+                               ct->proto.sctp.flags &= ~SCTP_FLAG_HEARTBEAT_VTAG_FAILED;
+                               ct->proto.sctp.vtag[dir] = sh->vtag;
+                               ct->proto.sctp.vtag[!dir] = 0;
+                       } else if (ct->proto.sctp.flags & SCTP_FLAG_HEARTBEAT_VTAG_FAILED) {
+                               ct->proto.sctp.flags &= ~SCTP_FLAG_HEARTBEAT_VTAG_FAILED;
                        }
                }
 
@@ -470,6 +497,10 @@ int nf_conntrack_sctp_packet(struct nf_conn *ct,
        }
        spin_unlock_bh(&ct->lock);
 
+       /* allow but do not refresh timeout */
+       if (ignore)
+               return NF_ACCEPT;
+
        timeouts = nf_ct_timeout_lookup(ct);
        if (!timeouts)
                timeouts = nf_sctp_pernet(nf_ct_net(ct))->timeouts;