]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4758: cip: cip inspector fallback functionality
authorBohdan Hryniv -X (bhryniv - SOFTSERVE INC at Cisco) <bhryniv@cisco.com>
Thu, 10 Jul 2025 20:45:55 +0000 (20:45 +0000)
committerChris Sherwin (chsherwi) <chsherwi@cisco.com>
Thu, 10 Jul 2025 20:45:55 +0000 (20:45 +0000)
Merge in SNORT/snort3 from ~BHRYNIV/snort3:cip_inspector_fallback_functionality to master

Squashed commit of the following:

commit ef51d9515e8b966ada31707535f7edeca3c7471a
Author: Bohdan Hryniv <bhryniv@cisco>
Date:   Wed May 21 11:53:40 2025 -0400

    cip: cip inspector fallback functionality

src/service_inspectors/cip/cip_paf.cc
src/service_inspectors/cip/cip_paf.h
src/service_inspectors/cip/cip_parsing.cc
src/service_inspectors/cip/cip_parsing.h

index a09a727de4ce801854716d0c70f83441f2791b12..503411b8f1460a944ea90718787dd7b948b0c3fb 100644 (file)
@@ -32,6 +32,12 @@ static const uint16_t ENIP_PAF_FIELD_SIZE = 4;
 
 using namespace snort;
 
+static inline void reset_states(cip_paf_data* pafdata)
+{
+    pafdata->paf_state = CIP_PAF_STATE__COMMAND_1;
+    pafdata->enip_length = 0;
+}
+
 /* Function: CIPPaf()
 
    Purpose: CIP PAF callback.
@@ -44,6 +50,8 @@ static StreamSplitter::Status cip_paf(cip_paf_data* pafdata, const uint8_t* data
     uint32_t len, uint32_t* fp)
 {
     uint32_t bytes_processed = 0;
+    uint16_t command = 0;
+    uint32_t total_length = 0;
 
     /* Process this packet 1 byte at a time */
     while (bytes_processed < len)
@@ -51,13 +59,21 @@ static StreamSplitter::Status cip_paf(cip_paf_data* pafdata, const uint8_t* data
         switch (pafdata->paf_state)
         {
         case CIP_PAF_STATE__COMMAND_1:
-            // Skip ENIP command.
+            command = *(data + bytes_processed);
             pafdata->paf_state = CIP_PAF_STATE__COMMAND_2;
             break;
 
         case CIP_PAF_STATE__COMMAND_2:
-            // Skip ENIP command.
-            pafdata->paf_state = CIP_PAF_STATE__LENGTH_1;
+            command |= (*(data + bytes_processed) << 8);
+            if (!enip_command_valid(command))
+            {
+                pafdata->bytes_seen += len;
+                pafdata->paf_state = CIP_PAF_STATE__INVALID;
+            } else
+            {
+                pafdata->bytes_seen = 0;
+                pafdata->paf_state = CIP_PAF_STATE__LENGTH_1;
+            }
             break;
 
         case CIP_PAF_STATE__LENGTH_1:
@@ -70,13 +86,33 @@ static StreamSplitter::Status cip_paf(cip_paf_data* pafdata, const uint8_t* data
             pafdata->paf_state = CIP_PAF_STATE__SET_FLUSH;
             break;
 
+        case CIP_PAF_STATE__INVALID:
+            if (pafdata->bytes_seen > CIP_MAX_OCTETS)
+            {
+                pafdata->bytes_seen = 0;
+                reset_states(pafdata);
+                return StreamSplitter::ABORT;
+            }
+            pafdata->paf_state = CIP_PAF_STATE__COMMAND_1;
+            pafdata->enip_length = 0;
+            return StreamSplitter::SEARCH;
+
         case CIP_PAF_STATE__SET_FLUSH:
-            *fp = bytes_processed +
+            total_length = bytes_processed +
                 pafdata->enip_length + (ENIP_HEADER_SIZE - ENIP_PAF_FIELD_SIZE);
 
-            pafdata->paf_state = CIP_PAF_STATE__COMMAND_1;
-            return StreamSplitter::FLUSH;
-
+            if(total_length > len)
+            {
+                pafdata->bytes_seen += len;
+                pafdata->paf_state = CIP_PAF_STATE__INVALID;
+            } else
+            {
+                pafdata->bytes_seen = 0;
+                *fp = total_length;
+                pafdata->paf_state = CIP_PAF_STATE__COMMAND_1;
+                return StreamSplitter::FLUSH;
+            }
+        break;
         default:
             // Will not happen.
             break;
@@ -92,6 +128,7 @@ CipSplitter::CipSplitter(bool c2s) : StreamSplitter(c2s)
 {
     state.paf_state = CIP_PAF_STATE__COMMAND_1;
     state.enip_length = 0;
+    state.bytes_seen = 0;
 }
 
 StreamSplitter::Status CipSplitter::scan(
@@ -101,4 +138,3 @@ StreamSplitter::Status CipSplitter::scan(
     cip_paf_data* pfdata = &state;
     return cip_paf(pfdata, data, len, fp);
 }
-
index e0afd082a595899a6a819802d61466f18ebc800e..87a6e1ce33c1a610c941e8039e7cb9d26e96db80 100644 (file)
@@ -27,6 +27,8 @@
 
 #include "cip.h"
 
+#define CIP_MAX_OCTETS 15000 // using standard max ethernet frame times 10
+
 /* State-tracking structs */
 enum cip_paf_state
 {
@@ -34,13 +36,15 @@ enum cip_paf_state
     CIP_PAF_STATE__COMMAND_2,
     CIP_PAF_STATE__LENGTH_1,
     CIP_PAF_STATE__LENGTH_2,
-    CIP_PAF_STATE__SET_FLUSH
+    CIP_PAF_STATE__SET_FLUSH,
+    CIP_PAF_STATE__INVALID
 };
 
 struct cip_paf_data
 {
     cip_paf_state paf_state;
     uint16_t enip_length;
+    uint32_t bytes_seen;
 };
 
 class CipSplitter : public snort::StreamSplitter
index 0f735975684ff729295c13f2b6890d8d2772601d..59ac1e74f56dfe68dea7eafbb42f50b2ffb08d55 100644 (file)
@@ -139,7 +139,7 @@ static bool parse_message_router_request(const uint8_t* data,
     CipGlobalSessionData* global_data);
 
 /// Functions
-static bool enip_command_valid(uint16_t command)
+bool enip_command_valid(uint16_t command)
 {
     if ((ENIP_COMMAND_RESERVED1_START <= command && command <= ENIP_COMMAND_RESERVED1_END)
         || (ENIP_COMMAND_RESERVED2_START <= command))
index 005b5120c429f282cc2178d24c25ac5a2bb6d6ff..88c8e6d2e56c76fea9e02a58d7ec2ff3356ad9d5 100644 (file)
@@ -60,6 +60,7 @@ bool parse_enip_layer(const uint8_t* data,
     CipGlobalSessionData* global_data);
 
 void pack_cip_request_event(const CipRequest* request, CipEventData* cip_event_data);
+bool enip_command_valid(uint16_t command);
 
 #endif  // CIP_PARSING_H