]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4252: stream_tcp: support for asymmetric normalization
authorJuweria Ali Imran (jaliimra) <jaliimra@cisco.com>
Tue, 30 Apr 2024 12:45:22 +0000 (12:45 +0000)
committerSteven Baigal (sbaigal) <sbaigal@cisco.com>
Tue, 30 Apr 2024 12:45:22 +0000 (12:45 +0000)
Merge in SNORT/snort3 from ~JALIIMRA/snort3:asymmetric_normalization to master

Squashed commit of the following:

commit 4c5c502b823a2f6d832a5fd39ca60bb33189234b
Author: Juweria Ali Imran <jaliimra@cisco.com>
Date:   Wed Apr 17 21:18:47 2024 +0000

    stream_tcp: support for asymmetric normalization

src/stream/stream.h
src/stream/tcp/tcp_defs.h
src/stream/tcp/tcp_module.cc
src/stream/tcp/tcp_module.h
src/stream/tcp/tcp_normalizer.cc
src/stream/tcp/tcp_normalizer.h
src/stream/tcp/tcp_normalizers.cc
src/stream/tcp/tcp_session.cc
src/stream/tcp/tcp_session.h
src/stream/tcp/tcp_state_listen.cc
src/stream/tcp/tcp_state_none.cc

index 2c03e87620ad9bdd33d93bb54aec669f61fbd41d..28e2577096d10efa45e37f2fe2f07c43b972bf8b 100644 (file)
@@ -60,7 +60,7 @@ class StreamSplitter;
 // sequence must match enum StreamPolicy defines in tcp_defs.h
 #define TCP_POLICIES \
     "first | last | linux | old_linux | bsd | macos | solaris | irix | " \
-    "hpux11 | hpux10 | windows | win_2003 | vista | proxy"
+    "hpux11 | hpux10 | windows | win_2003 | vista | proxy | asymmetric"
 
 struct AlertInfo
 {
index 6d8bd81da95c270706c34a4ca158bc331bdaa52e..c76e847b2666e9a15412b5226d4f84e15af45ef5 100644 (file)
@@ -76,6 +76,7 @@ enum StreamPolicy : uint8_t
     OS_WINDOWS2K3,
     OS_VISTA,
     OS_PROXY,
+    MISSED_3WHS,
     OS_END_OF_LIST,
     OS_DEFAULT = OS_BSD
 };
index 1768712cafba85ab8d76cc03d21108319662f438..183ece6740c0909b7fd1f841a9a60e581c3aa784 100644 (file)
@@ -119,6 +119,7 @@ const PegInfo tcp_pegs[] =
     { CountType::SUM, "proxy_mode_flows", "number of flows set to proxy normalization policy" },
     { CountType::SUM, "full_retransmits", "number of fully retransmitted segments" },
     { CountType::SUM, "flush_on_asymmetric_flow", "number of flushes on asymmetric flows" },
+    { CountType::SUM, "asymmetric_flows", "number of completed flows having one-way traffic only" },
     { CountType::END, nullptr, nullptr }
 };
 
@@ -363,10 +364,12 @@ bool StreamTcpModule::set(const char*, Value& v, SnortConfig*)
         else
             config->flags |= STREAM_CONFIG_NO_ASYNC_REASSEMBLY;
     }
+
     else if ( v.is("require_3whs") )
     {
         config->hs_timeout = v.get_int32();
     }
+
     else if ( v.is("show_rebuilt_packets") )
     {
         if ( v.get_bool() )
@@ -374,6 +377,7 @@ bool StreamTcpModule::set(const char*, Value& v, SnortConfig*)
         else
             config->flags &= ~STREAM_CONFIG_SHOW_PACKETS;
     }
+
     else if ( v.is("track_only") )
     {
         if ( v.get_bool() )
index 5faba4f6424464dd8d5ed5d6f581e14cfd5e36d1..95c87e2ebfb49aaa9b4d0aeb55c64b926877be06 100644 (file)
@@ -119,6 +119,7 @@ struct TcpStats
     PegCount proxy_mode_flows;
     PegCount full_retransmits;
     PegCount flush_on_asymmetric_flow;
+    PegCount asymmetric_flows;
 };
 
 extern THREAD_LOCAL struct TcpStats tcpStats;
index da8e9ba15830674844c355e44dc9625b1dff0f2b..814d0e842516bf4a78b8e8d3ed50bc68af509211 100644 (file)
@@ -37,34 +37,25 @@ using namespace snort;
 TcpNormalizer::NormStatus TcpNormalizer::apply_normalizations(
     TcpNormalizerState& tns, TcpSegmentDescriptor& tsd, uint32_t seq, bool stream_is_inorder)
 {
-    // if this is a midstream pickup then skip normalizations
-    if ( Stream::is_midstream(tsd.get_flow()) )
-        return NORM_OK;
-
-    // these normalizations can't be done if we missed setup. and
-    // window is zero in one direction until we've seen both sides.
-    if ( tsd.get_flow()->two_way_traffic() )
+    // drop packet if sequence num is invalid
+    if ( !tns.tracker->is_segment_seq_valid(tsd) )
     {
-        // drop packet if sequence num is invalid
-        if ( !tns.tracker->is_segment_seq_valid(tsd) )
-        {
-            bool inline_mode = tsd.is_nap_policy_inline();
-            tcpStats.invalid_seq_num++;
-            log_drop_reason(tns, tsd, inline_mode, "normalizer", "Normalizer: Sequence number is invalid\n");
-            trim_win_payload(tns, tsd, 0, inline_mode);
-            return NORM_BAD_SEQ;
-        }
+        bool inline_mode = tsd.is_nap_policy_inline();
+        tcpStats.invalid_seq_num++;
+        log_drop_reason(tns, tsd, inline_mode, "normalizer", "Normalizer: Sequence number is invalid\n");
+        trim_win_payload(tns, tsd, 0, inline_mode);
+        return NORM_BAD_SEQ;
+    }
 
-        // trim to fit in listener's window and mss
-        log_drop_reason(tns, tsd, false, "normalizer", "Normalizer: Trimming payload to fit window size\n");
-        trim_win_payload(tns, tsd,
-            (tns.tracker->r_win_base + tns.tracker->get_snd_wnd() - tns.tracker->rcv_nxt));
+    // trim to fit in listener's window and mss
+    log_drop_reason(tns, tsd, false, "normalizer", "Normalizer: Trimming payload to fit window size\n");
+    trim_win_payload(tns, tsd,
+        (tns.tracker->r_win_base + tns.tracker->get_snd_wnd() - tns.tracker->rcv_nxt));
 
-        if ( tns.tracker->get_mss() )
-            trim_mss_payload(tns, tsd, tns.tracker->get_mss());
+    if ( tns.tracker->get_mss() )
+        trim_mss_payload(tns, tsd, tns.tracker->get_mss());
 
-        ecn_stripper(tns, tsd);
-    }
+    ecn_stripper(tns, tsd);
 
     if ( stream_is_inorder )
     {
index 68e6a75acf701fa40f9290124ac33c3a35ddbb60..7ddd568a0a4ba43f07cfdf8f960a050d00544b2d 100644 (file)
@@ -66,6 +66,7 @@ public:
     virtual ~TcpNormalizer() = default;
 
     virtual void init(State&) { }
+    virtual void init(TcpNormalizer*) { }
 
     virtual NormStatus apply_normalizations(
         State&, TcpSegmentDescriptor&, uint32_t seq, bool stream_is_inorder);
@@ -111,6 +112,7 @@ protected:
     virtual int handle_paws_no_timestamps(State&, TcpSegmentDescriptor&);
 
     std::string my_name;
+    TcpNormalizer* prev_norm = nullptr;
 };
 
 #endif
index 4baa058dbff1397839fd4f2559ad5e7478d1dcfb..599a3cb11f366d155ccb72ab5faee0c00a51e391 100644 (file)
@@ -207,6 +207,22 @@ public:
     int handle_repeated_syn(TcpNormalizerState&, TcpSegmentDescriptor&) override;
 };
 
+// Normalizations applied or excluded for midstream and one-way asymmetric flows are common
+class TcpNormalizerMissed3whs : public TcpNormalizer
+{
+public:
+    TcpNormalizerMissed3whs()
+    { my_name = "Missed3whs"; }
+
+    void init(TcpNormalizer* prev) override
+    { prev_norm = prev; }
+
+    TcpNormalizer::NormStatus apply_normalizations(
+        TcpNormalizerState&, TcpSegmentDescriptor&, uint32_t seq, bool stream_is_inorder) override;
+    bool validate_rst(TcpNormalizerState&, TcpSegmentDescriptor&) override;
+    int handle_paws(TcpNormalizerState&, TcpSegmentDescriptor&) override;
+    int handle_repeated_syn(TcpNormalizerState&, TcpSegmentDescriptor&) override;
+};
 
 static inline int handle_repeated_syn_mswin(
     TcpStreamTracker* talker, TcpStreamTracker* listener,
@@ -471,8 +487,43 @@ int TcpNormalizerProxy::handle_repeated_syn(
     return ACTION_NOTHING;
 }
 
+TcpNormalizer::NormStatus TcpNormalizerMissed3whs::apply_normalizations(
+    TcpNormalizerState&, TcpSegmentDescriptor&, uint32_t, bool)
+{
+    // when a flow is Midstream/Asymmetric, not all packet normalizations are possible
+    return NORM_OK;
+}
+
+bool TcpNormalizerMissed3whs::validate_rst(
+    TcpNormalizerState& tns, TcpSegmentDescriptor& tsd)
+{
+    if ( tns.session->flow->two_way_traffic() )
+        return prev_norm->validate_rst(tns, tsd);
+
+    if ( !prev_norm->get_name().compare("OS_Hpux11") )
+        return validate_rst_seq_geq(tns, tsd);
+
+    return true;
+}
+
+int TcpNormalizerMissed3whs::handle_paws(
+    TcpNormalizerState& tns, TcpSegmentDescriptor& tsd) 
+{
+    return ACTION_NOTHING;
+}
+
+int TcpNormalizerMissed3whs::handle_repeated_syn(
+    TcpNormalizerState& tns, TcpSegmentDescriptor& tsd)
+{
+    return prev_norm->handle_repeated_syn(tns, tsd);
+}
+
 void TcpNormalizerPolicy::init(StreamPolicy os, TcpStreamSession* ssn, TcpStreamTracker* trk, TcpStreamTracker* peer)
 {
+    TcpNormalizer* prev_norm = nullptr;
+    if ( os == StreamPolicy::MISSED_3WHS and os != tns.os_policy )
+        prev_norm = TcpNormalizerFactory::get_instance(tns.os_policy);
+
     tns.os_policy = os;
     tns.session = ssn;
     tns.tracker = trk;
@@ -492,7 +543,11 @@ void TcpNormalizerPolicy::init(StreamPolicy os, TcpStreamSession* ssn, TcpStream
     tns.opt_block = Normalize_GetMode(NORM_TCP_OPT);
 
     norm = TcpNormalizerFactory::get_instance(os);
-    norm->init(tns);
+
+    if ( prev_norm )
+        norm->init(prev_norm);
+    else
+        norm->init(tns);
 }
 
 TcpNormalizer* TcpNormalizerFactory::normalizers[StreamPolicy::OS_END_OF_LIST];
@@ -513,17 +568,18 @@ void TcpNormalizerFactory::initialize()
     normalizers[StreamPolicy::OS_WINDOWS2K3] = new TcpNormalizerWindows2K3;
     normalizers[StreamPolicy::OS_VISTA] = new TcpNormalizerVista;
     normalizers[StreamPolicy::OS_PROXY] = new TcpNormalizerProxy;
+    normalizers[StreamPolicy::MISSED_3WHS] = new TcpNormalizerMissed3whs;
 }
 
 void TcpNormalizerFactory::term()
 {
-    for ( auto sp = StreamPolicy::OS_FIRST; sp <= StreamPolicy::OS_PROXY; sp++ )
+    for ( auto sp = StreamPolicy::OS_FIRST; sp < StreamPolicy::OS_END_OF_LIST; sp++ )
         delete normalizers[sp];
 }
 
 TcpNormalizer* TcpNormalizerFactory::get_instance(StreamPolicy sp)
 {
-    assert( sp <= StreamPolicy::OS_PROXY );
+    assert( sp < StreamPolicy::OS_END_OF_LIST );
     return normalizers[sp];
 }
 
index 4d53fd46eb336ca92e4788119e95f1cf19b5dc6f..60a3201c6b9430f5371ccd1241f9d2e93ba54ecc 100644 (file)
@@ -178,6 +178,9 @@ void TcpSession::clear_session(bool free_flow_data, bool flush_segments, bool re
     tcp_init = false;
     tcpStats.released++;
 
+    if ( !flow->two_way_traffic() and free_flow_data )
+        tcpStats.asymmetric_flows++;
+
     if ( flush_segments )
     {
         client.reassembler.flush_queued_segments(flow, true, p);
@@ -304,6 +307,19 @@ void TcpSession::update_perf_base_state(char newState)
         DataBus::publish(intrinsic_pub_id, IntrinsicEventIds::FLOW_STATE_CHANGE, nullptr, flow);
 }
 
+void TcpSession::check_flow_missed_3whs()
+{
+    if ( flow->two_way_traffic() )
+        return;
+
+    if ( PacketTracer::is_active() )
+        PacketTracer::log("Stream TCP did not see the complete 3-Way Handshake. "
+        "Not all normalizations will be in effect\n");
+
+    client.normalizer.init(StreamPolicy::MISSED_3WHS, this, &client, &server);
+    server.normalizer.init(StreamPolicy::MISSED_3WHS, this, &server, &client);
+}
+
 void TcpSession::update_stream_order(const TcpSegmentDescriptor& tsd, bool aligned)
 {
     TcpStreamTracker* listener = tsd.get_listener();
@@ -626,6 +642,9 @@ void TcpSession::check_for_session_hijack(TcpSegmentDescriptor& tsd)
 
 bool TcpSession::check_for_window_slam(TcpSegmentDescriptor& tsd)
 {
+    if (Stream::is_midstream(tsd.get_flow()) or !flow->two_way_traffic())
+        return false;
+
     TcpStreamTracker* listener = tsd.get_listener();
 
     if ( tcp_config->max_window && (tsd.get_wnd() > tcp_config->max_window) )
@@ -638,8 +657,7 @@ bool TcpSession::check_for_window_slam(TcpSegmentDescriptor& tsd)
     }
     else if ( tsd.is_packet_from_client() && (tsd.get_wnd() <= SLAM_MAX)
         && (tsd.get_ack() == listener->get_iss() + 1)
-        && !(tsd.get_tcph()->is_fin() || tsd.get_tcph()->is_rst())
-        && !(flow->get_session_flags() & SSNFLAG_MIDSTREAM))
+        && !(tsd.get_tcph()->is_fin() || tsd.get_tcph()->is_rst()))
     {
         /* got a window slam alert! */
         tel.set_tcp_event(EVENT_WINDOW_SLAM);
@@ -1061,6 +1079,13 @@ void TcpSession::init_tcp_packet_analysis(TcpSegmentDescriptor& tsd)
             client.init_flush_policy();
             server.init_flush_policy();
 
+            if ( tsd.is_packet_from_client() ) // Important if the 3-way handshake's ACK contains data
+                flow->set_session_flags(SSNFLAG_SEEN_CLIENT);
+            else
+                flow->set_session_flags(SSNFLAG_SEEN_SERVER);
+
+            check_flow_missed_3whs();
+
             set_no_ack(tcp_config->no_ack);
         }
 
index cdae05701d3502485c91dbfc5ce47476971b36f5..8cd7ce9fefaa96011b3d5f60cfeb41fc295854e5 100644 (file)
@@ -90,6 +90,7 @@ private:
     bool filter_packet_for_reassembly(TcpSegmentDescriptor&, TcpStreamTracker*);
     void check_small_segment_threshold(const TcpSegmentDescriptor&, TcpStreamTracker*);
     int32_t kickstart_asymmetric_flow(const TcpSegmentDescriptor&, TcpStreamTracker*);
+    void check_flow_missed_3whs();
 
 private:
     TcpStateMachine* tsm;
index 0e0488ab3f72ca85aa09ec449f266465ff4e10a9..12b7efc53d9745e52a6f682c1b4f799846088550 100644 (file)
@@ -25,6 +25,7 @@
 
 #include "tcp_state_listen.h"
 
+#include "packet_tracer/packet_tracer.h"
 #include "pub_sub/stream_event_ids.h"
 #include "stream/stream.h"
 
@@ -79,11 +80,21 @@ bool TcpStateListen::data_seg_sent(TcpSegmentDescriptor& tsd, TcpStreamTracker&
     if ( trk.session->is_midstream_allowed(tsd) )
     {
         Flow* flow = tsd.get_flow();
-
         flow->session_state |= STREAM_STATE_MIDSTREAM;
+
         if ( !Stream::is_midstream(flow) )
         {
+            TcpStreamTracker* listener = tsd.get_listener();
+            TcpStreamTracker* talker = tsd.get_talker();
+
+            trk.normalizer.init(StreamPolicy::MISSED_3WHS, trk.session, listener, talker);
+            trk.normalizer.init(StreamPolicy::MISSED_3WHS, trk.session, talker, listener);
             flow->set_session_flags(SSNFLAG_MIDSTREAM);
+
+            if ( PacketTracer::is_active() )
+                PacketTracer::log("Stream TCP did not see the complete 3-Way Handshake. "
+                "Not all normalizations will be in effect\n");
+
             DataBus::publish(Stream::get_pub_id(), StreamEventIds::TCP_MIDSTREAM, tsd.get_pkt());
         }
 
index 23bffd8283c8d9676b664b91a196988b8364bce0..55727ac3cd6db389b334bce9f4d047148fb6c64e 100644 (file)
@@ -25,6 +25,7 @@
 
 #include "tcp_state_none.h"
 
+#include "packet_tracer/packet_tracer.h"
 #include "pub_sub/stream_event_ids.h"
 #include "stream/stream.h"
 
@@ -86,9 +87,20 @@ bool TcpStateNone::data_seg_sent(TcpSegmentDescriptor& tsd, TcpStreamTracker& tr
     {
         Flow* flow = tsd.get_flow();
         flow->session_state |= STREAM_STATE_MIDSTREAM;
+
         if ( !Stream::is_midstream(flow) )
         {
+            TcpStreamTracker* listener = tsd.get_listener();
+            TcpStreamTracker* talker = tsd.get_talker();
+
+            trk.normalizer.init(StreamPolicy::MISSED_3WHS, trk.session, listener, talker);
+            trk.normalizer.init(StreamPolicy::MISSED_3WHS, trk.session, talker, listener);
             flow->set_session_flags(SSNFLAG_MIDSTREAM);
+
+            if ( PacketTracer::is_active() )
+                PacketTracer::log("Stream TCP did not see the complete 3-Way Handshake. "
+                "Not all normalizations will be in effect\n");
+
             DataBus::publish(Stream::get_pub_id(), StreamEventIds::TCP_MIDSTREAM, tsd.get_pkt());
         }