From 15a068258dc45c70dd20ab5c82ceb0875e3af0fc Mon Sep 17 00:00:00 2001 From: Jeff Lucovsky Date: Tue, 20 Oct 2026 09:56:41 -0400 Subject: [PATCH] detect/mpm: use content after must_succeed as fast_pattern transform_result stopped fast_pattern selection, and the validator rejected fast_pattern on its buffer. Content after must_succeed only matches when the transform succeeded, and the prefilter scans the same transformed bytes, so "http.uri; from_base64; transform_result: must_succeed; content:"evil";" lost its only fast pattern and ran on every HTTP transaction. Let content after must_succeed be the fast pattern, and accept fast_pattern there. Issue: 7313 Issue: 7114 --- src/detect-engine-mpm.c | 11 +++++++--- src/detect-isdataat.c | 45 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/detect-engine-mpm.c b/src/detect-engine-mpm.c index fd9a0fd8df..91fc9cbf9f 100644 --- a/src/detect-engine-mpm.c +++ b/src/detect-engine-mpm.c @@ -35,6 +35,7 @@ #include "detect-engine.h" #include "detect-engine-siggroup.h" #include "detect-engine-mpm.h" +#include "detect-isdataat.h" #include "detect-engine-iponly.h" #include "detect-parse.h" #include "detect-engine-prefilter.h" @@ -1120,11 +1121,15 @@ static void SetMpm(Signature *s, SigMatch *mpm_sm, const int mpm_sm_list) } /* absent and transform_result end the part of a buffer that can supply the - * fast_pattern. Repeated buffers can share a list id, so every pass over a - * list's buffers has to stop at them, not just the first. */ + * fast_pattern. transform_result: must_succeed does not: content after it + * can only match when the transform succeeded, and the prefilter then scans + * the same transformed bytes. Repeated buffers can share a list id, so every + * pass over a list's buffers has to stop at them, not just the first. */ static inline bool StopsFastPattern(const SigMatch *sm) { - return sm->type == DETECT_ABSENT || sm->type == DETECT_TRANSFORM_RESULT; + if (sm->type == DETECT_TRANSFORM_RESULT) + return ((const DetectAbsentData *)sm->ctx)->mode != DETECT_ABSENT_MUST_SUCCEED; + return sm->type == DETECT_ABSENT; } static SigMatch *GetMpmForList(const Signature *s, SigMatch *list, SigMatch *mpm_sm, diff --git a/src/detect-isdataat.c b/src/detect-isdataat.c index fc11c6b5a6..df7e74d4ee 100644 --- a/src/detect-isdataat.c +++ b/src/detect-isdataat.c @@ -230,7 +230,7 @@ bool DetectAbsentValidateContentCallback( "buffer"); return false; } - if (has_fast_pattern) { + if (has_fast_pattern && absent->mode != DETECT_ABSENT_MUST_SUCCEED) { SCLogError("signature can't have %s and fast_pattern on the same buffer", kw); return false; } @@ -1072,13 +1072,13 @@ static int DetectAbsentTestParse04(void) "sid:6;)"); FAIL_IF(s != NULL); - /* transform_result: must_succeed + fast_pattern on the same buffer should fail */ + /* transform_result: must_succeed allows fast_pattern on the same buffer */ s = DetectEngineAppendSig(de_ctx, "alert http any any -> any any " "(msg:\"must_succeed + fast_pattern\"; http.uri; " "from_base64: mode strict; transform_result: must_succeed; content:\"x\"; " "fast_pattern; sid:7;)"); - FAIL_IF(s != NULL); + FAIL_IF_NULL(s); /* fast_pattern appearing before absent on the same buffer should still fail * (the simplified walk is order-independent for fast_pattern detection) */ @@ -1134,6 +1134,44 @@ static int DetectAbsentTestParse05(void) PASS; } +/** \test content after transform_result: must_succeed can be the fast_pattern; + * content after error_or cannot */ +static int DetectAbsentTestParse06(void) +{ + const struct { + const char *sig; + bool mpm; + } tests[] = { + { "alert http any any -> any any (http.uri; from_base64; " + "transform_result: must_succeed; content:\"evil\"; sid:1;)", + true }, + { "alert http any any -> any any (http.uri; from_base64; " + "transform_result: error_or; content:\"evil\"; sid:2;)", + false }, + }; + + for (size_t i = 0; i < ARRAY_SIZE(tests); i++) { + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->flags |= DE_QUIET; + + Signature *s = DetectEngineAppendSig(de_ctx, tests[i].sig); + FAIL_IF_NULL(s); + FAIL_IF_NOT(s->init_data->buffer_index == 1); + + bool mpm = false; + for (SigMatch *sm = s->init_data->buffers[0].head; sm != NULL; sm = sm->next) { + if (sm->type == DETECT_CONTENT && + (((const DetectContentData *)sm->ctx)->flags & DETECT_CONTENT_MPM)) + mpm = true; + } + FAIL_IF_NOT(mpm == tests[i].mpm); + + DetectEngineCtxFree(de_ctx); + } + PASS; +} + void DetectAbsentRegisterTests(void) { UtRegisterTest("DetectAbsentTestParse01", DetectAbsentTestParse01); @@ -1141,5 +1179,6 @@ void DetectAbsentRegisterTests(void) UtRegisterTest("DetectAbsentTestParse03", DetectAbsentTestParse03); UtRegisterTest("DetectAbsentTestParse04", DetectAbsentTestParse04); UtRegisterTest("DetectAbsentTestParse05", DetectAbsentTestParse05); + UtRegisterTest("DetectAbsentTestParse06", DetectAbsentTestParse06); } #endif