]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
detect: clean support for multi-protocol keywords
authorPhilippe Antoine <contact@catenacyber.fr>
Fri, 4 Oct 2024 12:53:02 +0000 (14:53 +0200)
committerVictor Julien <victor@inliniac.net>
Sat, 29 Mar 2025 05:37:57 +0000 (06:37 +0100)
such as ja4.

Why ?

We do not want to see hard-coded protocol constants such as
ALPROTO_QUIC directly used in generic code in detect-parse.c

How ?
From the keyword point of view, this commit adds the function
DetectSignatureSetMultiAppProto which is similar to
DetectSignatureSetAppProto but takes multiple alprotos.
It restricts the signature alprotos to a set of possible alprotos
and errors out if the interstion gets empty.

The data structure SignatureInitData gets extended with
a fixed-length array, as the use case is a sparse number of protocols

Ticket: 7304

src/detect-parse.c
src/detect-parse.h
src/detect.h

index 2675b4369f5e92443da642f51b6dcc7736f303ae..c67f342aaf093bfb02b04ae1071e707adbf59045 100644 (file)
@@ -1779,6 +1779,94 @@ int DetectSignatureAddTransform(Signature *s, int transform, void *options)
     SCReturnInt(0);
 }
 
+/**
+ * \brief this function is used to set multiple possible app-layer protos
+ * \brief into the current signature (for example ja4 for both tls and quic)
+ *
+ * \param s pointer to the Current Signature
+ * \param alprotos an array terminated by ALPROTO_UNKNOWN
+ *
+ * \retval 0 on Success
+ * \retval -1 on Failure
+ */
+int DetectSignatureSetMultiAppProto(Signature *s, const AppProto *alprotos)
+{
+    if (s->alproto != ALPROTO_UNKNOWN) {
+        // One alproto was set, check if it matches the new ones proposed
+        while (*alprotos != ALPROTO_UNKNOWN) {
+            if (s->alproto == *alprotos) {
+                // alproto already set to only one
+                return 0;
+            }
+            alprotos++;
+        }
+        // alproto already set and not matching the new set of alprotos
+        return -1;
+    }
+    if (s->init_data->alprotos[0] != ALPROTO_UNKNOWN) {
+        // check intersection of already used alprotos and new ones
+        for (AppProto i = 0; i < SIG_ALPROTO_MAX; i++) {
+            if (s->init_data->alprotos[i] == ALPROTO_UNKNOWN) {
+                break;
+            }
+            // first disable the ones that do not match
+            bool found = false;
+            const AppProto *args = alprotos;
+            while (*args != ALPROTO_UNKNOWN) {
+                if (s->init_data->alprotos[i] == *args) {
+                    found = true;
+                    break;
+                }
+                args++;
+            }
+            if (!found) {
+                s->init_data->alprotos[i] = ALPROTO_UNKNOWN;
+            }
+        }
+        // Then put at the beginning every defined protocol
+        for (AppProto i = 0; i < SIG_ALPROTO_MAX; i++) {
+            if (s->init_data->alprotos[i] == ALPROTO_UNKNOWN) {
+                for (AppProto j = SIG_ALPROTO_MAX - 1; j > i; j--) {
+                    if (s->init_data->alprotos[j] != ALPROTO_UNKNOWN) {
+                        s->init_data->alprotos[i] = s->init_data->alprotos[j];
+                        s->init_data->alprotos[j] = ALPROTO_UNKNOWN;
+                        break;
+                    }
+                }
+                if (s->init_data->alprotos[i] == ALPROTO_UNKNOWN) {
+                    if (i == 0) {
+                        // there was no intersection
+                        return -1;
+                    } else if (i == 1) {
+                        // intersection is singleton, set it as usual
+                        AppProto alproto = s->init_data->alprotos[0];
+                        s->init_data->alprotos[0] = ALPROTO_UNKNOWN;
+                        return DetectSignatureSetAppProto(s, alproto);
+                    }
+                    break;
+                }
+            }
+        }
+    } else {
+        if (alprotos[0] == ALPROTO_UNKNOWN) {
+            // do not allow empty set
+            return -1;
+        }
+        if (alprotos[1] == ALPROTO_UNKNOWN) {
+            // allow singleton, but call traditional setter
+            return DetectSignatureSetAppProto(s, alprotos[0]);
+        }
+        // first time we enforce alprotos
+        for (AppProto i = 0; i < SIG_ALPROTO_MAX; i++) {
+            if (alprotos[i] == ALPROTO_UNKNOWN) {
+                break;
+            }
+            s->init_data->alprotos[i] = alprotos[i];
+        }
+    }
+    return 0;
+}
+
 int DetectSignatureSetAppProto(Signature *s, AppProto alproto)
 {
     if (!AppProtoIsValid(alproto)) {
@@ -1786,6 +1874,24 @@ int DetectSignatureSetAppProto(Signature *s, AppProto alproto)
         return -1;
     }
 
+    if (s->init_data->alprotos[0] != ALPROTO_UNKNOWN) {
+        // Multiple alprotos were set, check if we restrict to one
+        bool found = false;
+        for (AppProto i = 0; i < SIG_ALPROTO_MAX; i++) {
+            if (s->init_data->alprotos[i] == alproto) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            // fail if we set to a alproto which was not in the set
+            return -1;
+        }
+        // we will use s->alproto if there is a single alproto and
+        // we reset s->init_data->alprotos to signal there are no longer multiple alprotos
+        s->init_data->alprotos[0] = ALPROTO_UNKNOWN;
+    }
+
     if (s->alproto != ALPROTO_UNKNOWN) {
         alproto = AppProtoCommon(s->alproto, alproto);
         if (alproto == ALPROTO_FAILED) {
@@ -4554,6 +4660,81 @@ static int SigParseTestActionDrop(void)
     PASS;
 }
 
+static int SigSetMultiAppProto(void)
+{
+    Signature *s = SigAlloc();
+    FAIL_IF_NULL(s);
+
+    AppProto alprotos[] = { 1, 2, 3, ALPROTO_UNKNOWN };
+    FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) < 0);
+
+    // check intersection gives multiple entries
+    alprotos[0] = 3;
+    alprotos[1] = 2;
+    alprotos[2] = ALPROTO_UNKNOWN;
+    FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) < 0);
+    FAIL_IF(s->init_data->alprotos[0] != 3);
+    FAIL_IF(s->init_data->alprotos[1] != 2);
+    FAIL_IF(s->init_data->alprotos[2] != ALPROTO_UNKNOWN);
+
+    // check single after multiple
+    FAIL_IF(DetectSignatureSetAppProto(s, 3) < 0);
+    FAIL_IF(s->init_data->alprotos[0] != ALPROTO_UNKNOWN);
+    FAIL_IF(s->alproto != 3);
+    alprotos[0] = 4;
+    alprotos[1] = 3;
+    alprotos[2] = ALPROTO_UNKNOWN;
+    // check multiple containing singleton
+    FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) < 0);
+    FAIL_IF(s->alproto != 3);
+
+    // reset
+    s->alproto = ALPROTO_UNKNOWN;
+    alprotos[0] = 1;
+    alprotos[1] = 2;
+    alprotos[2] = 3;
+    alprotos[3] = ALPROTO_UNKNOWN;
+    FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) < 0);
+    // fail if set single not in multiple
+    FAIL_IF(DetectSignatureSetAppProto(s, 4) >= 0);
+
+    s->init_data->alprotos[0] = ALPROTO_UNKNOWN;
+    s->alproto = ALPROTO_UNKNOWN;
+    alprotos[0] = 1;
+    alprotos[1] = 2;
+    alprotos[2] = 3;
+    alprotos[3] = ALPROTO_UNKNOWN;
+    FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) < 0);
+    alprotos[0] = 4;
+    alprotos[1] = 5;
+    alprotos[2] = ALPROTO_UNKNOWN;
+    // fail if multiple do not have intersection
+    FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) >= 0);
+
+    s->init_data->alprotos[0] = ALPROTO_UNKNOWN;
+    s->alproto = ALPROTO_UNKNOWN;
+    alprotos[0] = 1;
+    alprotos[1] = 2;
+    alprotos[2] = 3;
+    alprotos[3] = ALPROTO_UNKNOWN;
+    FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) < 0);
+    alprotos[0] = 3;
+    alprotos[1] = 4;
+    alprotos[2] = 5;
+    alprotos[3] = ALPROTO_UNKNOWN;
+    // check multiple intersect to singleton
+    FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) < 0);
+    FAIL_IF(s->alproto != 3);
+    alprotos[0] = 5;
+    alprotos[1] = 4;
+    alprotos[2] = ALPROTO_UNKNOWN;
+    // fail if multiple do not belong to singleton
+    FAIL_IF(DetectSignatureSetMultiAppProto(s, alprotos) >= 0);
+
+    SigFree(NULL, s);
+    PASS;
+}
+
 #endif /* UNITTESTS */
 
 #ifdef UNITTESTS
@@ -4629,5 +4810,8 @@ void SigParseRegisterTests(void)
             SigParseBidirWithSameSrcAndDest02);
     UtRegisterTest("SigParseTestActionReject", SigParseTestActionReject);
     UtRegisterTest("SigParseTestActionDrop", SigParseTestActionDrop);
+
+    UtRegisterTest("SigSetMultiAppProto", SigSetMultiAppProto);
+
 #endif /* UNITTESTS */
 }
index ec2c204c0f428cf5276db95dd3e2b21df4f27389..c6b69a1bb2ac77b7212d81a0ec425b480c7a402d 100644 (file)
@@ -102,6 +102,7 @@ SigMatch *DetectGetLastSMByListId(const Signature *s, int list_id, ...);
 
 int DetectSignatureAddTransform(Signature *s, int transform, void *options);
 int WARN_UNUSED DetectSignatureSetAppProto(Signature *s, AppProto alproto);
+int WARN_UNUSED DetectSignatureSetMultiAppProto(Signature *s, const AppProto *alprotos);
 
 /* parse regex setup and free util funcs */
 
index 90507ff031881df3e2af0b37aaff7ce8cc922428..89838b936822e61a1d66ef94d7e97eeddd01c02f 100644 (file)
@@ -540,6 +540,8 @@ typedef struct SignatureInitDataBuffer_ {
     SigMatch *tail;
 } SignatureInitDataBuffer;
 
+#define SIG_ALPROTO_MAX 4
+
 typedef struct SignatureInitData_ {
     /** Number of sigmatches. Used for assigning SigMatch::idx */
     uint16_t sm_cnt;
@@ -560,6 +562,9 @@ typedef struct SignatureInitData_ {
     uint32_t init_flags;
     /* coccinelle: SignatureInitData:init_flags:SIG_FLAG_INIT_ */
 
+    /* alproto mask if multiple protocols are possible */
+    AppProto alprotos[SIG_ALPROTO_MAX];
+
     /* used at init to determine max dsize */
     SigMatch *dsize_sm;