]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
detect: allow rule which need both directions to match 12875/head
authorPhilippe Antoine <pantoine@oisf.net>
Thu, 18 Jan 2024 13:15:14 +0000 (14:15 +0100)
committerVictor Julien <victor@inliniac.net>
Sat, 29 Mar 2025 21:14:02 +0000 (22:14 +0100)
Ticket: 5665

This is done with `alert ip any any => any any`
The => operator means that we will need both directions

17 files changed:
doc/userguide/rules/intro.rst
src/detect-engine-mpm.c
src/detect-engine-mpm.h
src/detect-engine-state.c
src/detect-engine.c
src/detect-fast-pattern.c
src/detect-file-data.c
src/detect-filemagic.c
src/detect-filename.c
src/detect-flow.c
src/detect-http-client-body.c
src/detect-http-headers-stub.h
src/detect-parse.c
src/detect-parse.h
src/detect-prefilter.c
src/detect.c
src/detect.h

index 56df9ab494378b09974171ec7df74027fd90a272..58ff2264eabfb600a8a84135d19bb299015a864e 100644 (file)
@@ -228,11 +228,14 @@ Direction
 
 The directional arrow indicates which way the signature will be evaluated.
 In most signatures an arrow to the right (``->``) is used. This means that only
-packets with the same direction can match. However, it is also possible to
-have a rule match both directions (``<>``)::
+packets with the same direction can match.
+There is also the double arrow (``=>``), which respects the directionality as ``->``,
+but allows matching on bidirectional transactions, used with keywords matching each direction.
+Finally, it is also possible to have a rule match either directions (``<>``)::
 
   source -> destination
-  source <> destination  (both directions)
+  source => destination
+  source <> destination  (either directions)
 
 The following example illustrates direction. In this example there is a client
 with IP address 1.2.3.4 using port 1024. A server with IP address 5.6.7.8,
@@ -248,10 +251,56 @@ Now, let's say we have a rule with the following header::
 Only the traffic from the client to the server will be matched by this rule,
 as the direction specifies that we do not want to evaluate the response packet.
 
+Now, if we have a rule with the following header::
+
+    alert tcp 1.2.3.4 any <> 5.6.7.8 80
+
+Suricata will duplicate it and use the same rule with headers in both directions :
+
+    alert tcp 1.2.3.4 any -> 5.6.7.8 80
+    alert tcp 5.6.7.8 80 -> 1.2.3.4 any
+
 .. warning::
 
    There is no 'reverse' style direction, i.e. there is no ``<-``.
 
+Transactional rules
+~~~~~~~~~~~~~~~~~~~
+
+Here is an example of a transactional rule:
+
+.. container:: example-rule
+
+    alert http any any :example-rule-emphasis:`=>` 5.6.7.8 80 (msg:"matching both uri and status"; sid: 1; http.uri; content: "/download"; http.stat_code; content: "200";)
+
+It will match on flows to 5.6.7.8 and port 80.
+And it will match on a full transaction, using both the uri from the request,
+and the stat_code from the response.
+As such, it will match only when Suricata got both request and response.
+
+Transactional rules can use direction-ambiguous keywords, by specifying the direction.
+
+.. container:: example-rule
+
+    alert http any any => 5.6.7.8 80 (msg:"matching json to server and xml to client"; sid: 1; http.content_type: :example-rule-emphasis:`to_server`; content: "json"; http.content_type: :example-rule-emphasis:`to_client`; content: "xml";)
+
+Transactional rules have some limitations :
+
+* They cannot use direction-ambiguous keywords
+* They are only meant to work on transactions with first a request to the server,
+  and then a response to the client, and not the other way around (not tested).
+* They cannot have ``fast_pattern`` or ``prefilter`` the direction to client
+  if they also have a streaming buffer on the direction to server, see example below.
+* They will refuse to load if a single directional rule is enough.
+
+This rule cannot have the ``fast_pattern`` to client, as ``file.data`` is a streaming buffer and will refuse to load.
+
+.. container:: example-rule
+
+    alert http any any => any any (file.data: to_server; content: "123";  http.stat_code; content: "500"; fast_patten;)
+
+If not explicit, a transactional rule will choose a fast_pattern to server by default
+
 Rule options
 ------------
 The rest of the rule consists of options. These are enclosed by parenthesis
index 2585d807ff8d210cc44be751a6c4d11f33c539f4..dca7bf4b95024a10c6b9e0429476a0c2092e96f4 100644 (file)
@@ -1071,6 +1071,26 @@ static SigMatch *GetMpmForList(const Signature *s, SigMatch *list, SigMatch *mpm
 
 int g_skip_prefilter = 0;
 
+// tells if a buffer id is only used to client
+bool DetectBufferToClient(const DetectEngineCtx *de_ctx, int buf_id, AppProto alproto)
+{
+    bool r = false;
+    const DetectEngineAppInspectionEngine *app = de_ctx->app_inspect_engines;
+    for (; app != NULL; app = app->next) {
+        if (app->sm_list == buf_id &&
+                (AppProtoEquals(alproto, app->alproto) || alproto == ALPROTO_UNKNOWN)) {
+            if (app->dir == 1) {
+                // do not return yet in case we have app engines on both sides
+                r = true;
+            } else {
+                // ambiguous keywords have a app-engine to server
+                return false;
+            }
+        }
+    }
+    return r;
+}
+
 void RetrieveFPForSig(const DetectEngineCtx *de_ctx, Signature *s)
 {
     if (g_skip_prefilter)
@@ -1168,6 +1188,7 @@ void RetrieveFPForSig(const DetectEngineCtx *de_ctx, Signature *s)
     memset(&final_sm_list, 0, (nlists * sizeof(int)));
 
     int count_final_sm_list = 0;
+    int count_txbidir_toclient_sm_list = 0;
     int priority;
 
     const SCFPSupportSMList *tmp = de_ctx->fp_support_smlist_list;
@@ -1181,6 +1202,17 @@ void RetrieveFPForSig(const DetectEngineCtx *de_ctx, Signature *s)
                 continue;
             if (curr_sm_list[tmp->list_id] == 0)
                 continue;
+            if (s->flags & SIG_FLAG_TXBOTHDIR) {
+                // prefer to choose a fast_pattern to server by default
+                if (DetectBufferToClient(de_ctx, tmp->list_id, s->alproto)) {
+                    if (count_final_sm_list == 0) {
+                        // still put it in in the case we do not have toserver buffer
+                        final_sm_list[count_txbidir_toclient_sm_list++] = tmp->list_id;
+                    }
+                    continue;
+                }
+            }
+            // we may erase tx bidir toclient buffers here as intended if we have a better choice
             final_sm_list[count_final_sm_list++] = tmp->list_id;
             SCLogDebug("tmp->list_id %d", tmp->list_id);
         }
@@ -1188,6 +1220,10 @@ void RetrieveFPForSig(const DetectEngineCtx *de_ctx, Signature *s)
             break;
     }
 
+    if ((s->flags & SIG_FLAG_TXBOTHDIR) && count_final_sm_list == 0) {
+        // forced to pick a fast_pattern to client for tx bidir signature
+        count_final_sm_list = count_txbidir_toclient_sm_list;
+    }
     BUG_ON(count_final_sm_list == 0);
     SCLogDebug("count_final_sm_list %d skip_negated_content %d", count_final_sm_list,
             skip_negated_content);
@@ -1211,7 +1247,12 @@ void RetrieveFPForSig(const DetectEngineCtx *de_ctx, Signature *s)
             }
         } else {
             for (uint32_t x = 0; x < s->init_data->buffer_index; x++) {
+                if (s->init_data->buffers[x].only_tc) {
+                    // prefer to choose a fast_pattern to server by default
+                    continue;
+                }
                 const int list_id = s->init_data->buffers[x].id;
+
                 if (final_sm_list[i] == list_id) {
                     SCLogDebug("%u: list_id %d: %s", s->id, list_id,
                             DetectEngineBufferTypeGetNameById(de_ctx, list_id));
index 10bdb86f5bcba4a6fc196aa3910dc75e18964b88..34d67ae7679fc1c9e21a09541b0a473714dc2e9f 100644 (file)
@@ -131,4 +131,6 @@ struct MpmListIdDataArgs {
 
 void EngineAnalysisAddAllRulePatterns(DetectEngineCtx *de_ctx, const Signature *s);
 
+bool DetectBufferToClient(const DetectEngineCtx *de_ctx, int buf_id, AppProto alproto);
+
 #endif /* SURICATA_DETECT_ENGINE_MPM_H */
index e7a3f9fcb3c94ff471ff57a31ef3b2ab84186d8f..c1552ab893ad635fc10cec6d24dfc4016db7abc8 100644 (file)
@@ -231,6 +231,11 @@ void DetectRunStoreStateTx(
         SCLogDebug("destate created for %"PRIu64, tx_id);
     }
     DeStateSignatureAppend(tx_data->de_state, s, inspect_flags, flow_flags);
+    if (s->flags & SIG_FLAG_TXBOTHDIR) {
+        // add also in the other DetectEngineStateDirection
+        DeStateSignatureAppend(tx_data->de_state, s, inspect_flags,
+                flow_flags ^ (STREAM_TOSERVER | STREAM_TOCLIENT));
+    }
     StoreStateTxHandleFiles(sgh, f, tx_data->de_state, flow_flags, tx, tx_id, file_no_match);
 
     SCLogDebug("Stored for TX %"PRIu64, tx_id);
index 7fa7e71bb13eb1d174690ec898aad52dc075a32d..15423ead65ef15717a04726aac41b614fcd2f09b 100644 (file)
@@ -784,6 +784,15 @@ int DetectEngineAppInspectionEngine2Signature(DetectEngineCtx *de_ctx, Signature
             for (const DetectEngineAppInspectionEngine *t = de_ctx->app_inspect_engines; t != NULL;
                     t = t->next) {
                 if (t->sm_list == s->init_data->buffers[x].id) {
+                    if (s->flags & SIG_FLAG_TXBOTHDIR) {
+                        // ambiguous keywords have app engines in both directions
+                        // so we skip the wrong direction for this buffer
+                        if (s->init_data->buffers[x].only_tc && t->dir == 0) {
+                            continue;
+                        } else if (s->init_data->buffers[x].only_ts && t->dir == 1) {
+                            continue;
+                        }
+                    }
                     AppendAppInspectEngine(
                             de_ctx, t, s, smd, mpm_list, files_id, &last_id, &head_is_mpm);
                 }
@@ -1412,7 +1421,12 @@ int DetectBufferSetActiveList(DetectEngineCtx *de_ctx, Signature *s, const int l
 
             } else if (DetectEngineBufferTypeSupportsMultiInstanceGetById(de_ctx, list)) {
                 // fall through
+            } else if (!b->only_ts && (s->init_data->init_flags & SIG_FLAG_INIT_FORCE_TOSERVER)) {
+                // fall through
+            } else if (!b->only_tc && (s->init_data->init_flags & SIG_FLAG_INIT_FORCE_TOCLIENT)) {
+                // fall through
             } else {
+                // we create a new buffer for the same id but forced different direction
                 SCLogWarning("duplicate instance for %s in '%s'",
                         DetectEngineBufferTypeGetNameById(de_ctx, list), s->sig_str);
                 s->init_data->curbuf = b;
@@ -1436,6 +1450,13 @@ int DetectBufferSetActiveList(DetectEngineCtx *de_ctx, Signature *s, const int l
     s->init_data->curbuf->tail = NULL;
     s->init_data->curbuf->multi_capable =
             DetectEngineBufferTypeSupportsMultiInstanceGetById(de_ctx, list);
+    if (s->init_data->init_flags & SIG_FLAG_INIT_FORCE_TOCLIENT) {
+        s->init_data->curbuf->only_tc = true;
+    }
+    if (s->init_data->init_flags & SIG_FLAG_INIT_FORCE_TOSERVER) {
+        s->init_data->curbuf->only_ts = true;
+    }
+
     SCLogDebug("new: idx %u list %d set up curbuf %p", s->init_data->buffer_index - 1, list,
             s->init_data->curbuf);
 
index 1af1daeea3df01cc3ee979681a3c7e64e8dd38b7..1a7a617f50931515c34c906603b9ec63c602b38a 100644 (file)
@@ -237,6 +237,18 @@ static int DetectFastPatternSetup(DetectEngineCtx *de_ctx, Signature *s, const c
         pm = pm2;
     }
 
+    if (s->flags & SIG_FLAG_TXBOTHDIR && s->init_data->curbuf != NULL) {
+        if (DetectBufferToClient(de_ctx, s->init_data->curbuf->id, s->alproto)) {
+            if (s->init_data->init_flags & SIG_FLAG_INIT_TXDIR_STREAMING_TOSERVER) {
+                SCLogError("fast_pattern cannot be used on to_client keyword for "
+                           "transactional rule with a streaming buffer to server %u",
+                        s->id);
+                goto error;
+            }
+            s->init_data->init_flags |= SIG_FLAG_INIT_TXDIR_FAST_TOCLIENT;
+        }
+    }
+
     cd = (DetectContentData *)pm->ctx;
     if ((cd->flags & DETECT_CONTENT_NEGATED) &&
         ((cd->flags & DETECT_CONTENT_DISTANCE) ||
index 17df5d83e604d9871a2c919437042340b5dc010c..202ec2d57b05f92772f662c2e3072ffad527eca6 100644 (file)
@@ -78,7 +78,7 @@ void DetectFiledataRegister(void)
 #ifdef UNITTESTS
     sigmatch_table[DETECT_FILE_DATA].RegisterTests = DetectFiledataRegisterTests;
 #endif
-    sigmatch_table[DETECT_FILE_DATA].flags = SIGMATCH_NOOPT;
+    sigmatch_table[DETECT_FILE_DATA].flags = SIGMATCH_OPTIONAL_OPT;
 
     filehandler_table[DETECT_FILE_DATA].name = "file_data";
     filehandler_table[DETECT_FILE_DATA].priority = 2;
@@ -140,6 +140,11 @@ static int DetectFiledataSetup (DetectEngineCtx *de_ctx, Signature *s, const cha
         return -1;
     }
 
+    if (DetectSetupDirection(s, str) < 0) {
+        SCLogError("file.data failed to setup direction");
+        return -1;
+    }
+
     if (s->alproto == ALPROTO_SMTP && (s->init_data->init_flags & SIG_FLAG_INIT_FLOW) &&
         !(s->flags & SIG_FLAG_TOSERVER) && (s->flags & SIG_FLAG_TOCLIENT)) {
         SCLogError("The 'file-data' keyword cannot be used with SMTP flow:to_client or "
@@ -151,6 +156,19 @@ static int DetectFiledataSetup (DetectEngineCtx *de_ctx, Signature *s, const cha
         return -1;
 
     s->init_data->init_flags |= SIG_FLAG_INIT_FILEDATA;
+    if ((s->init_data->init_flags & SIG_FLAG_INIT_FORCE_TOCLIENT) == 0) {
+        // we cannot use a transactional rule with a fast pattern to client and this
+        if (s->init_data->init_flags & SIG_FLAG_INIT_TXDIR_FAST_TOCLIENT) {
+            SCLogError("fast_pattern cannot be used on to_client keyword for "
+                       "transactional rule with a streaming buffer to server %u",
+                    s->id);
+            return -1;
+        }
+        s->init_data->init_flags |= SIG_FLAG_INIT_TXDIR_STREAMING_TOSERVER;
+    }
+    s->init_data->init_flags &= ~SIG_FLAG_INIT_FORCE_TOSERVER;
+    s->init_data->init_flags &= ~SIG_FLAG_INIT_FORCE_TOCLIENT;
+
     SetupDetectEngineConfig(de_ctx);
     return 0;
 }
index 0f8d94a7b5b16e5d817a0492eaa6400eb7b71ce2..365aa997ae457ae2cd8f609650177192cd59dc11 100644 (file)
@@ -113,7 +113,7 @@ void DetectFilemagicRegister(void)
     sigmatch_table[DETECT_FILE_MAGIC].desc = "sticky buffer to match on the file magic";
     sigmatch_table[DETECT_FILE_MAGIC].url = "/rules/file-keywords.html#filemagic";
     sigmatch_table[DETECT_FILE_MAGIC].Setup = DetectFilemagicSetupSticky;
-    sigmatch_table[DETECT_FILE_MAGIC].flags = SIGMATCH_NOOPT|SIGMATCH_INFO_STICKY_BUFFER;
+    sigmatch_table[DETECT_FILE_MAGIC].flags = SIGMATCH_OPTIONAL_OPT | SIGMATCH_INFO_STICKY_BUFFER;
 
     filehandler_table[DETECT_FILE_MAGIC].name = "file.magic",
     filehandler_table[DETECT_FILE_MAGIC].priority = 2;
@@ -249,6 +249,10 @@ static int DetectFilemagicSetup (DetectEngineCtx *de_ctx, Signature *s, const ch
  */
 static int DetectFilemagicSetupSticky(DetectEngineCtx *de_ctx, Signature *s, const char *str)
 {
+    if (DetectSetupDirection(s, str) < 0) {
+        SCLogError("file.magic failed to setup direction");
+        return -1;
+    }
     if (DetectBufferSetActiveList(de_ctx, s, g_file_magic_buffer_id) < 0)
         return -1;
 
index ef144cf44086179ddf4348e6a86e38aaa1959744..7d75b5dcb4ed944f31c40997b38420f3bd0c5485 100644 (file)
@@ -99,7 +99,7 @@ void DetectFilenameRegister(void)
     sigmatch_table[DETECT_FILE_NAME].desc = "sticky buffer to match on the file name";
     sigmatch_table[DETECT_FILE_NAME].url = "/rules/file-keywords.html#filename";
     sigmatch_table[DETECT_FILE_NAME].Setup = DetectFilenameSetupSticky;
-    sigmatch_table[DETECT_FILE_NAME].flags = SIGMATCH_NOOPT|SIGMATCH_INFO_STICKY_BUFFER;
+    sigmatch_table[DETECT_FILE_NAME].flags = SIGMATCH_OPTIONAL_OPT | SIGMATCH_INFO_STICKY_BUFFER;
 
     DetectBufferTypeSetDescriptionByName("file.name", "file name");
 
@@ -207,6 +207,10 @@ static int DetectFilenameSetup (DetectEngineCtx *de_ctx, Signature *s, const cha
  */
 static int DetectFilenameSetupSticky(DetectEngineCtx *de_ctx, Signature *s, const char *str)
 {
+    if (DetectSetupDirection(s, str) < 0) {
+        SCLogError("file.name failed to setup direction");
+        return -1;
+    }
     if (DetectBufferSetActiveList(de_ctx, s, g_file_name_buffer_id) < 0)
         return -1;
     s->file_flags |= (FILE_SIG_NEED_FILE | FILE_SIG_NEED_FILENAME);
index 683d53fb70cb6f8e086f2776d8d9909b5dd008cc..f2de380ef554ac37951cee981a045c93941a7e36 100644 (file)
@@ -391,8 +391,18 @@ int DetectFlowSetup (DetectEngineCtx *de_ctx, Signature *s, const char *flowstr)
     bool appendsm = true;
     /* set the signature direction flags */
     if (fd->flags & DETECT_FLOW_FLAG_TOSERVER) {
+        if (s->flags & SIG_FLAG_TXBOTHDIR) {
+            SCLogError(
+                    "rule %u means to use both directions, cannot specify a flow direction", s->id);
+            goto error;
+        }
         s->flags |= SIG_FLAG_TOSERVER;
     } else if (fd->flags & DETECT_FLOW_FLAG_TOCLIENT) {
+        if (s->flags & SIG_FLAG_TXBOTHDIR) {
+            SCLogError(
+                    "rule %u means to use both directions, cannot specify a flow direction", s->id);
+            goto error;
+        }
         s->flags |= SIG_FLAG_TOCLIENT;
     } else {
         s->flags |= SIG_FLAG_TOSERVER;
index 2c75a33552468303b9966f451c8f56f35613d268..f0f0538a8908ca99473426de049b5fbd5c3794e0 100644 (file)
@@ -168,6 +168,14 @@ static int DetectHttpClientBodySetupSticky(DetectEngineCtx *de_ctx, Signature *s
         return -1;
     if (DetectSignatureSetAppProto(s, ALPROTO_HTTP) < 0)
         return -1;
+    // we cannot use a transactional rule with a fast pattern to client and this
+    if (s->init_data->init_flags & SIG_FLAG_INIT_TXDIR_FAST_TOCLIENT) {
+        SCLogError("fast_pattern cannot be used on to_client keyword for "
+                   "transactional rule with a streaming buffer to server %u",
+                s->id);
+        return -1;
+    }
+    s->init_data->init_flags |= SIG_FLAG_INIT_TXDIR_STREAMING_TOSERVER;
     return 0;
 }
 
index 834a3b25d92a0818ad52913ab077d4c81d54bda6..8a0809bd6266d182de2c4aef8eb7a498d2b251da 100644 (file)
@@ -162,9 +162,17 @@ static InspectionBuffer *GetResponseData2(DetectEngineThreadCtx *det_ctx,
  */
 static int DetectHttpHeadersSetupSticky(DetectEngineCtx *de_ctx, Signature *s, const char *str)
 {
+    if (DetectSetupDirection(s, str) < 0) {
+        SCLogError(KEYWORD_NAME " failed to setup direction");
+        return -1;
+    }
+
     if (DetectBufferSetActiveList(de_ctx, s, g_buffer_id) < 0)
         return -1;
 
+    s->init_data->init_flags &= ~SIG_FLAG_INIT_FORCE_TOSERVER;
+    s->init_data->init_flags &= ~SIG_FLAG_INIT_FORCE_TOCLIENT;
+
     if (DetectSignatureSetAppProto(s, ALPROTO_HTTP) < 0)
         return -1;
 
@@ -180,7 +188,11 @@ static void DetectHttpHeadersRegisterStub(void)
     sigmatch_table[KEYWORD_ID].desc = KEYWORD_NAME " sticky buffer for the " BUFFER_DESC;
     sigmatch_table[KEYWORD_ID].url = "/rules/" KEYWORD_DOC;
     sigmatch_table[KEYWORD_ID].Setup = DetectHttpHeadersSetupSticky;
+#if defined(KEYWORD_TOSERVER) && defined(KEYWORD_TOSERVER)
+    sigmatch_table[KEYWORD_ID].flags |= SIGMATCH_OPTIONAL_OPT | SIGMATCH_INFO_STICKY_BUFFER;
+#else
     sigmatch_table[KEYWORD_ID].flags |= SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER;
+#endif
 
 #ifdef KEYWORD_TOSERVER
     DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmRegister,
index 370b832a59db51c1c4b247f64941eca5e4ac6e67..60d202d6b7fd991f035510e276b6f5c3b02bd169 100644 (file)
@@ -1418,6 +1418,8 @@ static int SigParseBasics(DetectEngineCtx *de_ctx, Signature *s, const char *sig
 
     if (strcmp(parser->direction, "<>") == 0) {
         s->init_data->init_flags |= SIG_FLAG_INIT_BIDIREC;
+    } else if (strcmp(parser->direction, "=>") == 0) {
+        s->flags |= SIG_FLAG_TXBOTHDIR;
     } else if (strcmp(parser->direction, "->") != 0) {
         SCLogError("\"%s\" is not a valid direction modifier, "
                    "\"->\" and \"<>\" are supported.",
@@ -2142,6 +2144,9 @@ static int SigValidate(DetectEngineCtx *de_ctx, Signature *s)
     } bufdir[nlists + 1];
     memset(&bufdir, 0, (nlists + 1) * sizeof(struct BufferVsDir));
 
+    int ts_excl = 0;
+    int tc_excl = 0;
+
     for (uint32_t x = 0; x < s->init_data->buffer_index; x++) {
         SignatureInitDataBuffer *b = &s->init_data->buffers[x];
         const DetectBufferType *bt = DetectEngineBufferTypeGetById(de_ctx, b->id);
@@ -2179,8 +2184,16 @@ static int SigValidate(DetectEngineCtx *de_ctx, Signature *s)
                         DetectEngineBufferTypeGetNameById(de_ctx, app->sm_list), app->dir,
                         app->alproto);
                 SCLogDebug("b->id %d nlists %d", b->id, nlists);
-                bufdir[b->id].ts += (app->dir == 0);
-                bufdir[b->id].tc += (app->dir == 1);
+                if (b->only_tc) {
+                    if (app->dir == 1)
+                        tc_excl++;
+                } else if (b->only_ts) {
+                    if (app->dir == 0)
+                        ts_excl++;
+                } else {
+                    bufdir[b->id].ts += (app->dir == 0);
+                    bufdir[b->id].tc += (app->dir == 1);
+                }
             }
         }
 
@@ -2196,8 +2209,6 @@ static int SigValidate(DetectEngineCtx *de_ctx, Signature *s)
         }
     }
 
-    int ts_excl = 0;
-    int tc_excl = 0;
     int dir_amb = 0;
     for (int x = 0; x < nlists; x++) {
         if (bufdir[x].ts == 0 && bufdir[x].tc == 0)
@@ -2209,8 +2220,22 @@ static int SigValidate(DetectEngineCtx *de_ctx, Signature *s)
         SCLogDebug("%s/%d: %d/%d", DetectEngineBufferTypeGetNameById(de_ctx, x), x, bufdir[x].ts,
                 bufdir[x].tc);
     }
-    if (ts_excl && tc_excl) {
-        SCLogError("rule %u mixes keywords with conflicting directions", s->id);
+    if (s->flags & SIG_FLAG_TXBOTHDIR) {
+        if (!ts_excl || !tc_excl) {
+            SCLogError("rule %u should use both directions, but does not", s->id);
+            SCReturnInt(0);
+        }
+        if (dir_amb) {
+            SCLogError("rule %u means to use both directions, cannot have keywords ambiguous about "
+                       "directions",
+                    s->id);
+            SCReturnInt(0);
+        }
+    } else if (ts_excl && tc_excl) {
+        SCLogError(
+                "rule %u mixes keywords with conflicting directions, a transactional rule with => "
+                "should be used",
+                s->id);
         SCReturnInt(0);
     } else if (ts_excl) {
         SCLogDebug("%u: implied rule direction is toserver", s->id);
@@ -3006,6 +3031,42 @@ void DetectSetupParseRegexes(const char *parse_str, DetectParseRegex *detect_par
     }
 }
 
+/**
+ * \brief Parse and setup a direction
+ *
+ * \param s siganture
+ * \param str argument to the keyword
+ *
+ * \retval 0 on success, -1 on failure
+ */
+int DetectSetupDirection(Signature *s, const char *str)
+{
+    if (str) {
+        if (strcmp(str, "to_client") == 0) {
+            s->init_data->init_flags |= SIG_FLAG_INIT_FORCE_TOCLIENT;
+            if ((s->flags & SIG_FLAG_TXBOTHDIR) == 0) {
+                if (s->flags & SIG_FLAG_TOSERVER) {
+                    SCLogError("contradictory directions");
+                    return -1;
+                }
+                s->flags |= SIG_FLAG_TOCLIENT;
+            }
+        } else if (strcmp(str, "to_server") == 0) {
+            s->init_data->init_flags |= SIG_FLAG_INIT_FORCE_TOSERVER;
+            if ((s->flags & SIG_FLAG_TXBOTHDIR) == 0) {
+                if (s->flags & SIG_FLAG_TOCLIENT) {
+                    SCLogError("contradictory directions");
+                    return -1;
+                }
+                s->flags |= SIG_FLAG_TOSERVER;
+            }
+        } else {
+            SCLogError("unknown option: only accepts to_server or to_client");
+            return -1;
+        }
+    }
+    return 0;
+}
 
 /*
  * TESTS
index c6b69a1bb2ac77b7212d81a0ec425b480c7a402d..2f5b4d6c93db9628f999760150502e035410a859 100644 (file)
@@ -121,4 +121,6 @@ int SC_Pcre2SubstringCopy(
 int SC_Pcre2SubstringGet(pcre2_match_data *match_data, uint32_t number, PCRE2_UCHAR **bufferptr,
         PCRE2_SIZE *bufflen);
 
+int DetectSetupDirection(Signature *s, const char *str);
+
 #endif /* SURICATA_DETECT_PARSE_H */
index 78b1a3c14b7967c89049607f2926ac54fd150394..d3f209d69eebec514bc36b5a94d73fed928ed460 100644 (file)
@@ -29,6 +29,7 @@
 #include "detect.h"
 #include "detect-parse.h"
 #include "detect-content.h"
+#include "detect-engine-mpm.h"
 #include "detect-prefilter.h"
 #include "util-debug.h"
 
@@ -75,6 +76,19 @@ static int DetectPrefilterSetup (DetectEngineCtx *de_ctx, Signature *s, const ch
     /* if the sig match is content, prefilter should act like
      * 'fast_pattern' w/o options. */
     if (sm->type == DETECT_CONTENT) {
+        if (s->flags & SIG_FLAG_TXBOTHDIR && s->init_data->curbuf != NULL) {
+            if (s->init_data->init_flags & SIG_FLAG_INIT_TXDIR_STREAMING_TOSERVER) {
+                if (DetectBufferToClient(de_ctx, s->init_data->curbuf->id, s->alproto)) {
+                    SCLogError("prefilter cannot be used on to_client keyword for "
+                               "transactional rule %u",
+                            s->id);
+                    SCReturnInt(-1);
+                } else {
+                    s->init_data->init_flags |= SIG_FLAG_INIT_TXDIR_FAST_TOCLIENT;
+                }
+            }
+        }
+
         DetectContentData *cd = (DetectContentData *)sm->ctx;
         if ((cd->flags & DETECT_CONTENT_NEGATED) &&
                 ((cd->flags & DETECT_CONTENT_DISTANCE) ||
index 41cbe6d84a7fa1e485e07e3523bf4772307adf53..07b8d98001b5a199d247905453c58b10abce5de4 100644 (file)
@@ -1166,9 +1166,11 @@ static bool DetectRunTxInspectRule(ThreadVars *tv,
     const DetectEngineAppInspectionEngine *engine = s->app_inspect;
     do {
         TRACE_SID_TXS(s->id, tx, "engine %p inspect_flags %x", engine, inspect_flags);
+        // also if it is not the same direction, but
+        // this is a transactional signature, and we are toclient
         if (!(inspect_flags & BIT_U32(engine->id)) &&
-                direction == engine->dir)
-        {
+                (direction == engine->dir || ((s->flags & SIG_FLAG_TXBOTHDIR) && direction == 1))) {
+
             void *tx_ptr = DetectGetInnerTx(tx->tx_ptr, f->alproto, engine->alproto, flow_flags);
             if (tx_ptr == NULL) {
                 if (engine->alproto != ALPROTO_UNKNOWN) {
@@ -1204,6 +1206,10 @@ static bool DetectRunTxInspectRule(ThreadVars *tv,
                 }
             }
 
+            uint8_t engine_flags = flow_flags;
+            if (direction != engine->dir) {
+                engine_flags = flow_flags ^ (STREAM_TOCLIENT | STREAM_TOSERVER);
+            }
             /* run callback: but bypass stream callback if we can */
             uint8_t match;
             if (unlikely(engine->stream && can->stream_stored)) {
@@ -1213,7 +1219,7 @@ static bool DetectRunTxInspectRule(ThreadVars *tv,
                 KEYWORD_PROFILING_SET_LIST(det_ctx, engine->sm_list);
                 DEBUG_VALIDATE_BUG_ON(engine->v2.Callback == NULL);
                 match = engine->v2.Callback(
-                        de_ctx, det_ctx, engine, s, f, flow_flags, alstate, tx_ptr, tx->tx_id);
+                        de_ctx, det_ctx, engine, s, f, engine_flags, alstate, tx_ptr, tx->tx_id);
                 TRACE_SID_TXS(s->id, tx, "engine %p match %d", engine, match);
                 if (engine->stream) {
                     can->stream_stored = true;
@@ -1247,7 +1253,19 @@ static bool DetectRunTxInspectRule(ThreadVars *tv,
                 inspect_flags |= BIT_U32(engine->id);
             }
             break;
+        } else if (!(inspect_flags & BIT_U32(engine->id)) && s->flags & SIG_FLAG_TXBOTHDIR &&
+                   direction != engine->dir) {
+            // for transactional rules, the engines on the opposite direction
+            // are ordered by progress on the different side
+            // so we have a two mixed-up lists, and we skip the elements
+            if (direction == 0 && engine->next == NULL) {
+                // do not match yet on request only
+                break;
+            }
+            engine = engine->next;
+            continue;
         }
+
         engine = engine->next;
     } while (engine != NULL);
     TRACE_SID_TXS(s->id, tx, "inspect_flags %x, total_matches %u, engine %p",
index 8b03c8df5ae9e9d36afd3e920cae309e5bcec382..89d221f612ea21021b55fd2f18bd9a38bf505415 100644 (file)
@@ -246,6 +246,7 @@ typedef struct DetectPort_ {
 
 #define SIG_FLAG_DSIZE                  BIT_U32(5)  /**< signature has a dsize setting */
 #define SIG_FLAG_APPLAYER               BIT_U32(6) /**< signature applies to app layer instead of packets */
+#define SIG_FLAG_TXBOTHDIR              BIT_U32(7) /**< signature needs tx with both directions to match */
 
 // vacancy
 
@@ -295,7 +296,14 @@ typedef struct DetectPort_ {
 #define SIG_FLAG_INIT_NEED_FLUSH            BIT_U32(7)
 #define SIG_FLAG_INIT_PRIO_EXPLICIT                                                                \
     BIT_U32(8) /**< priority is explicitly set by the priority keyword */
-#define SIG_FLAG_INIT_FILEDATA BIT_U32(9) /**< signature has filedata keyword */
+#define SIG_FLAG_INIT_FILEDATA       BIT_U32(9)  /**< signature has filedata keyword */
+#define SIG_FLAG_INIT_FORCE_TOCLIENT BIT_U32(10) /**< signature now takes keywords toclient */
+#define SIG_FLAG_INIT_FORCE_TOSERVER BIT_U32(11) /**< signature now takes keywords toserver */
+// Two following flags are meant to be mutually exclusive
+#define SIG_FLAG_INIT_TXDIR_STREAMING_TOSERVER                                                     \
+    BIT_U32(12) /**< transactional signature uses a streaming buffer to server */
+#define SIG_FLAG_INIT_TXDIR_FAST_TOCLIENT                                                          \
+    BIT_U32(13) /**< transactional signature uses a fast pattern to client */
 
 /* signature mask flags */
 /** \note: additions should be added to the rule analyzer as well */
@@ -534,6 +542,8 @@ typedef struct SignatureInitDataBuffer_ {
                      set up. */
     bool multi_capable; /**< true if we can have multiple instances of this buffer, so e.g. for
                            http.uri. */
+    bool only_tc;       /**< true if we can only used toclient. */
+    bool only_ts;       /**< true if we can only used toserver. */
     /* sig match list */
     SigMatch *head;
     SigMatch *tail;