]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #4793: iec104: fallback functionality changes
authorAndres Avila Segura (aavilase) <aavilase@cisco.com>
Tue, 22 Jul 2025 15:45:46 +0000 (15:45 +0000)
committerChris Sherwin (chsherwi) <chsherwi@cisco.com>
Tue, 22 Jul 2025 15:45:46 +0000 (15:45 +0000)
Merge in SNORT/snort3 from ~AAVILASE/snort3:iec104_fallback_draft to master

Squashed commit of the following:

commit 1e29d06544ca82bc5b144ae80b0f65edb13be651
Author: Andres Avila <aavilase@cisco.com>
Date:   Tue Jun 17 13:09:07 2025 -0400

    iec104: fallback functionality for abort scenario

src/service_inspectors/iec104/iec104_paf.cc
src/service_inspectors/iec104/iec104_paf.h

index 68a7abed836e7a8b1b0cf6ebd7aeb767b49502f9..54f8b94b74bd470d47f8e5e1d1c6e7725d56e415 100644 (file)
 
 using namespace snort;
 
-#define IEC104_MIN_HDR_LEN 2         // Enough for the Start and Length fields
+#define IEC104_MAX_OCTETS 2560 // using max length of iec104 packet (max size is a byte) times 10
+#define IEC104_LSBIT_MASK 0x01
+#define IEC104_FUNCTION_FIELDS_MASK 0xFC
+#define IEC104_APCI_TYPE_MASK 0x03
+#define IEC104_TYPE_I 0
+#define IEC104_TYPE_S 1
+#define IEC104_TYPE_I_2 2
+#define IEC104_TYPE_U 3
 
 Iec104Splitter::Iec104Splitter(bool b) :
     StreamSplitter(b)
 {
     state = IEC104_PAF_STATE__START;
     iec104_apci_length = 0;
+    bytes_seen = 0;
+    valid_bytes = true;
+}
+
+//each u format frame can only have one function bit set
+static inline bool onlyOneBitSet(uint8_t n) {
+    if (n == 0)
+        return false;
+    return (n & (n - 1)) == 0; // If non-zero after clearing LSB, more than one bit was set
+}
+
+static inline bool validate_control_fields(const uint8_t* data)
+{
+    uint8_t apci_type = data[2] & IEC104_APCI_TYPE_MASK;
+
+    switch(apci_type)
+    {
+        case IEC104_TYPE_U:
+        {
+            bool are_fields_unset = (data[3] | data[4] | data[5]) == 0;
+            uint8_t function_fields = data[2] & IEC104_FUNCTION_FIELDS_MASK;
+            return are_fields_unset and onlyOneBitSet(function_fields);
+        }
+        case IEC104_TYPE_S:
+        {
+            bool is_field3_lsb_unset = (data[4] & IEC104_LSBIT_MASK) == 0;
+            return data[2] == 1 and data[3] == 0 and is_field3_lsb_unset;
+        }
+        default: //IEC104_TYPE_I format
+        {
+            bool is_field3_lsb_unset = (data[4] & IEC104_LSBIT_MASK) == 0;
+            return is_field3_lsb_unset;
+        }
+    }
 }
 
 // IEC104/TCP PAF:
@@ -57,6 +98,9 @@ StreamSplitter::Status Iec104Splitter::scan(Packet*, const uint8_t* data, uint32
 
     uint32_t bytes_processed = 0;
 
+    if (len < IEC104_MIN_LEN or !validate_control_fields(data))
+        valid_bytes = false;
+
     /* Process this packet 1 byte at a time */
     while (bytes_processed < len)
     {
@@ -65,7 +109,12 @@ StreamSplitter::Status Iec104Splitter::scan(Packet*, const uint8_t* data, uint32
         // skip the start state
         case IEC104_PAF_STATE__START:
         {
+            uint8_t iec104_byte = *data;
             state = IEC104_PAF_STATE__LEN;
+            if (iec104_byte != IEC104_START_ID)
+            {
+                valid_bytes = false;
+            }
             break;
         }
 
@@ -74,6 +123,10 @@ StreamSplitter::Status Iec104Splitter::scan(Packet*, const uint8_t* data, uint32
         {
             iec104_apci_length = *(data + bytes_processed);
             state = IEC104_PAF_STATE__SET_FLUSH;
+            if (iec104_apci_length > len)
+            {
+                valid_bytes = false;
+            }
             break;
         }
 
@@ -82,6 +135,19 @@ StreamSplitter::Status Iec104Splitter::scan(Packet*, const uint8_t* data, uint32
             *fp = iec104_apci_length + bytes_processed;      // flush point at the end of payload
             state = IEC104_PAF_STATE__START;
             iec104_apci_length = 0;
+            if (!valid_bytes)
+            {
+                valid_bytes = true;
+                bytes_seen += len;
+                if (bytes_seen > IEC104_MAX_OCTETS)
+                {
+                    return StreamSplitter::ABORT;
+                }
+            }
+            else
+            {
+                bytes_seen = 0;
+            }
             return StreamSplitter::FLUSH;
         }
         }
index 09d266ce237f010e262ab70b856c8572ab66496c..455d1cde2a0eb0ab7ccf95e607b54130e423bf48 100644 (file)
@@ -47,6 +47,8 @@ public:
 private:
     iec104_paf_state_t state;
     uint16_t iec104_apci_length;
+    uint32_t bytes_seen;
+    bool valid_bytes;
 };
 
 #endif