diff --git a/doc/userguide/rules/intro.rst b/doc/userguide/rules/intro.rst index 56df9ab494..58ff2264ea 100644 --- a/doc/userguide/rules/intro.rst +++ b/doc/userguide/rules/intro.rst @@ -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 diff --git a/src/detect-engine-mpm.c b/src/detect-engine-mpm.c index 2585d807ff..dca7bf4b95 100644 --- a/src/detect-engine-mpm.c +++ b/src/detect-engine-mpm.c @@ -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)); diff --git a/src/detect-engine-mpm.h b/src/detect-engine-mpm.h index 10bdb86f5b..34d67ae767 100644 --- a/src/detect-engine-mpm.h +++ b/src/detect-engine-mpm.h @@ -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 */ diff --git a/src/detect-engine-state.c b/src/detect-engine-state.c index e7a3f9fcb3..c1552ab893 100644 --- a/src/detect-engine-state.c +++ b/src/detect-engine-state.c @@ -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); diff --git a/src/detect-engine.c b/src/detect-engine.c index 7fa7e71bb1..15423ead65 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -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); diff --git a/src/detect-fast-pattern.c b/src/detect-fast-pattern.c index 1af1daeea3..1a7a617f50 100644 --- a/src/detect-fast-pattern.c +++ b/src/detect-fast-pattern.c @@ -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) || diff --git a/src/detect-file-data.c b/src/detect-file-data.c index 17df5d83e6..202ec2d57b 100644 --- a/src/detect-file-data.c +++ b/src/detect-file-data.c @@ -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; } diff --git a/src/detect-filemagic.c b/src/detect-filemagic.c index 0f8d94a7b5..365aa997ae 100644 --- a/src/detect-filemagic.c +++ b/src/detect-filemagic.c @@ -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; diff --git a/src/detect-filename.c b/src/detect-filename.c index ef144cf440..7d75b5dcb4 100644 --- a/src/detect-filename.c +++ b/src/detect-filename.c @@ -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); diff --git a/src/detect-flow.c b/src/detect-flow.c index 683d53fb70..f2de380ef5 100644 --- a/src/detect-flow.c +++ b/src/detect-flow.c @@ -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; diff --git a/src/detect-http-client-body.c b/src/detect-http-client-body.c index 2c75a33552..f0f0538a89 100644 --- a/src/detect-http-client-body.c +++ b/src/detect-http-client-body.c @@ -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; } diff --git a/src/detect-http-headers-stub.h b/src/detect-http-headers-stub.h index 834a3b25d9..8a0809bd62 100644 --- a/src/detect-http-headers-stub.h +++ b/src/detect-http-headers-stub.h @@ -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, diff --git a/src/detect-parse.c b/src/detect-parse.c index 370b832a59..60d202d6b7 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -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 diff --git a/src/detect-parse.h b/src/detect-parse.h index c6b69a1bb2..2f5b4d6c93 100644 --- a/src/detect-parse.h +++ b/src/detect-parse.h @@ -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 */ diff --git a/src/detect-prefilter.c b/src/detect-prefilter.c index 78b1a3c14b..d3f209d69e 100644 --- a/src/detect-prefilter.c +++ b/src/detect-prefilter.c @@ -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) || diff --git a/src/detect.c b/src/detect.c index 41cbe6d84a..07b8d98001 100644 --- a/src/detect.c +++ b/src/detect.c @@ -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", diff --git a/src/detect.h b/src/detect.h index 8b03c8df5a..89d221f612 100644 --- a/src/detect.h +++ b/src/detect.h @@ -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;