]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
stream: add more liberal timestamp behavior in 3WHS
authorVictor Julien <vjulien@oisf.net>
Mon, 1 Sep 2025 12:51:56 +0000 (14:51 +0200)
committerVictor Julien <victor@inliniac.net>
Mon, 8 Sep 2025 16:47:16 +0000 (18:47 +0200)
RFC 7323 forbids a server to respond with a timestamp option in the
SYN/ACK when the SYN didn't have a timestamp option:

   A TCP MAY send the TSopt in an initial <SYN> segment (i.e., segment
   containing a SYN bit and no ACK bit), and MAY send a TSopt in
   <SYN,ACK> only if it received a TSopt in the initial <SYN> segment
   for the connection.

   Once TSopt has been successfully negotiated, that is both <SYN> and
   <SYN,ACK> contain TSopt, the TSopt MUST be sent in every non-<RST>
   segment for the duration of the connection, and SHOULD be sent in an
   <RST> segment (see Section 5.2 for details).

However, in the real world this pattern happens on benign traffic. This
would lead to missing logs and detection, and in IPS mode such sessions
would be blocked.

This patch allows this pattern when the `stream.liberal-timestamps` is
enabled (enabled by default).

Bug #4702.

src/stream-tcp.c

index 0c25119ad1ae2f0ea400944fdf4a302c229a7c25..cc49cb3fcc76533fce2e644af09cdbcc2edef4c0 100644 (file)
@@ -1890,9 +1890,14 @@ static void TcpStateQueueInitFromPktSynAck(const Packet *p, TcpStateQueue *q)
 
 /** \internal
  *  \brief Find the Queued SYN that is the same as this SYN/ACK
- *  \retval q or NULL */
+ *  \param[in] ignore_ts if true, ignore the timestamp
+ *  \retval q or NULL
+ *
+ *  \note When `ignore_ts`, the following is accepted: SYN w/o TS, SYN/ACK with TS.
+ *  \note When `ignore_ts` is set, `s` corresponds to the SYN/ACK packet and the
+ *  queue holds the stored SYN packets. */
 static const TcpStateQueue *StreamTcp3whsFindSyn(
-        const TcpSession *ssn, TcpStateQueue *s, TcpStateQueue **ret_tail)
+        const TcpSession *ssn, TcpStateQueue *s, TcpStateQueue **ret_tail, const bool ignore_ts)
 {
     SCLogDebug("ssn %p: search state:%p, isn:%u/win:%u/has_ts:%s/tsval:%u", ssn, s, s->seq, s->win,
             BOOL2STR(s->flags & STREAMTCP_QUEUE_FLAG_TS), s->ts);
@@ -1902,9 +1907,15 @@ static const TcpStateQueue *StreamTcp3whsFindSyn(
         SCLogDebug("ssn %p: queue state:%p, isn:%u/win:%u/has_ts:%s/tsval:%u (last:%s)", ssn, q,
                 q->seq, q->win, BOOL2STR(q->flags & STREAMTCP_QUEUE_FLAG_TS), q->ts,
                 BOOL2STR(q->next == NULL));
-        if ((s->flags & STREAMTCP_QUEUE_FLAG_TS) == (q->flags & STREAMTCP_QUEUE_FLAG_TS) &&
-                s->ts == q->ts && s->seq == q->seq) {
-            return q;
+
+        if (s->flags & STREAMTCP_QUEUE_FLAG_TS) {
+            if ((q->flags & STREAMTCP_QUEUE_FLAG_TS) && s->ts == q->ts && s->seq == q->seq) {
+                return q;
+            }
+        } else if (ignore_ts) {
+            if (s->seq == q->seq) {
+                return q;
+            }
         }
         last = q;
     }
@@ -1937,7 +1948,7 @@ static int StreamTcp3whsStoreSyn(TcpSession *ssn, Packet *p)
     TcpStateQueue *tail = NULL;
 
     /* first see if this is already in our list */
-    if (ssn->queue != NULL && StreamTcp3whsFindSyn(ssn, &search, &tail) != NULL)
+    if (ssn->queue != NULL && StreamTcp3whsFindSyn(ssn, &search, &tail, false) != NULL)
         return 0;
 
     if (ssn->queue_len == stream_config.max_syn_queued) {
@@ -2025,7 +2036,8 @@ static inline bool StateSynSentCheckSynAck3Whs(TcpSession *ssn, Packet *p, const
     TcpStateQueueInitFromPktSynAck(p, &search);
     SCLogDebug("%" PRIu64 ": ssn %p: SYN/ACK looking for SEQ %u", p->pcap_cnt, ssn, search.seq);
 
-    const TcpStateQueue *q = StreamTcp3whsFindSyn(ssn, &search, NULL);
+    const TcpStateQueue *q =
+            StreamTcp3whsFindSyn(ssn, &search, NULL, stream_config.liberal_timestamps);
     if (q == NULL) {
         SCLogDebug("not found: mismatch");
         goto failure;
@@ -2074,7 +2086,8 @@ static inline bool StateSynSentCheckSynAckTFO(TcpSession *ssn, Packet *p, const
         TcpStateQueueInitFromPktSynAck(p, &search);
         SCLogDebug("%" PRIu64 ": ssn %p: SYN/ACK looking for SEQ %u", p->pcap_cnt, ssn, search.seq);
 
-        const TcpStateQueue *q = StreamTcp3whsFindSyn(ssn, &search, NULL);
+        const TcpStateQueue *q =
+                StreamTcp3whsFindSyn(ssn, &search, NULL, stream_config.liberal_timestamps);
         if (q == NULL) {
             SCLogDebug("not found: mismatch");
             goto failure;
@@ -2117,7 +2130,11 @@ static int StreamTcpPacketStateSynSent(
     if ((tcph->th_flags & (TH_SYN | TH_ACK)) == (TH_SYN | TH_ACK) && PKT_IS_TOCLIENT(p)) {
         SCLogDebug("%" PRIu64 ": ssn %p: SYN/ACK on SYN_SENT state for packet %" PRIu64,
                 p->pcap_cnt, ssn, p->pcap_cnt);
-        const bool ts_mismatch = !StateSynSentValidateTimestamp(ssn, p);
+        /* if timestamps are liberal, allow a SYN/ACK with TS even if the SYN
+         * had none (violates RFC 7323, see bug #4702). */
+        const bool ts_mismatch =
+                !(stream_config.liberal_timestamps || StateSynSentValidateTimestamp(ssn, p));
+        SCLogDebug("ts_mismatch %s", BOOL2STR(ts_mismatch));
 
         if (!(TCP_HAS_TFO(p) || (ssn->flags & STREAMTCP_FLAG_TCP_FAST_OPEN))) {
             if (StateSynSentCheckSynAck3Whs(ssn, p, ts_mismatch)) {