From: Victor Julien Date: Mon, 1 Sep 2025 12:51:56 +0000 (+0200) Subject: stream: add more liberal timestamp behavior in 3WHS X-Git-Tag: suricata-8.0.1~22 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=d352b75ac64bd2026d87fe2a95c7d178e66e2922;p=thirdparty%2Fsuricata.git stream: add more liberal timestamp behavior in 3WHS 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 segment (i.e., segment containing a SYN bit and no ACK bit), and MAY send a TSopt in only if it received a TSopt in the initial segment for the connection. Once TSopt has been successfully negotiated, that is both and contain TSopt, the TSopt MUST be sent in every non- segment for the duration of the connection, and SHOULD be sent in an 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. --- diff --git a/src/stream-tcp.c b/src/stream-tcp.c index 0c25119ad1..cc49cb3fcc 100644 --- a/src/stream-tcp.c +++ b/src/stream-tcp.c @@ -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)) {