From 99ab49638d58beff6d66fb639dd50a205e93f7a5 Mon Sep 17 00:00:00 2001 From: Jeff Lucovsky Date: Mon, 2 Feb 2026 09:20:05 -0500 Subject: [PATCH] detect/transform_result: add keyword for transform outcome Add transform_result, which matches on the error flag a transform sets on its buffer: must_succeed does not match on an error, must_error matches only on one, and error_or matches on an error, an absent buffer, or the keywords after it. It shares DetectAbsentData with absent, whose bool or_else becomes an enum DetectAbsentMode, and absent rejects the transform_result arguments. The validator requires a transform that can fail before transform_result, rejects absent: or_else on such a buffer, rejects must_error with other keywords and fast_pattern on the buffer, and says when a transform placed after the keyword started a new buffer. The engine analyzer reports a mode string for both keywords in place of the or_else boolean. Issue: 7313 Issue: 7114 --- src/detect-engine-analyzer.c | 10 +- src/detect-engine-content-inspection.c | 46 ++- src/detect-engine-mpm.c | 6 +- src/detect-engine-register.h | 3 +- src/detect-isdataat.c | 453 +++++++++++++++++++++++-- src/detect-isdataat.h | 19 +- src/detect-parse.c | 4 +- 7 files changed, 481 insertions(+), 60 deletions(-) diff --git a/src/detect-engine-analyzer.c b/src/detect-engine-analyzer.c index 44faa77005..9f02e24e97 100644 --- a/src/detect-engine-analyzer.c +++ b/src/detect-engine-analyzer.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2025 Open Information Security Foundation +/* Copyright (C) 2007-2026 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free @@ -872,10 +872,12 @@ static void DumpMatches(RuleAnalyzer *ctx, SCJsonBuilder *js, const SigMatchData SCJbClose(js); break; } - case DETECT_ABSENT: { + case DETECT_ABSENT: + case DETECT_TRANSFORM_RESULT: { const DetectAbsentData *dad = (const DetectAbsentData *)smd->ctx; - SCJbOpenObject(js, "absent"); - SCJbSetBool(js, "or_else", dad->or_else); + SCJbOpenObject( + js, smd->type == DETECT_TRANSFORM_RESULT ? "transform_result" : "absent"); + SCJbSetString(js, "mode", DetectAbsentModeStr(dad->mode)); SCJbClose(js); break; } diff --git a/src/detect-engine-content-inspection.c b/src/detect-engine-content-inspection.c index e2037a748e..4c362602bd 100644 --- a/src/detect-engine-content-inspection.c +++ b/src/detect-engine-content-inspection.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2023 Open Information Security Foundation +/* Copyright (C) 2007-2026 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free @@ -391,13 +391,35 @@ static int DetectEngineContentInspectionInternal(DetectEngineThreadCtx *det_ctx, prev_offset); } while(1); - } else if (smd->type == DETECT_ABSENT) { - const DetectAbsentData *id = (DetectAbsentData *)smd->ctx; - if (!id->or_else) { - // we match only on absent buffer - goto no_match; + } else if (smd->type == DETECT_ABSENT || smd->type == DETECT_TRANSFORM_RESULT) { + const DetectAbsentData *dad = (const DetectAbsentData *)smd->ctx; + switch (dad->mode) { + case DETECT_ABSENT_MUST_ERROR: + if (flags & DETECT_CI_FLAGS_ERROR) { + SCLogDebug("transform_result: must_error: error flag match"); + goto final_match; + } + goto no_match; + case DETECT_ABSENT_ONLY: + goto no_match; + case DETECT_ABSENT_ERROR_OR: + if (flags & DETECT_CI_FLAGS_ERROR) { + SCLogDebug("transform_result: error_or: error flag match"); + goto final_match; + } + /* fall through */ + case DETECT_ABSENT_OR_ELSE: + goto match; + case DETECT_ABSENT_MUST_SUCCEED: + if (flags & DETECT_CI_FLAGS_ERROR) { + SCLogDebug("transform_result: must_succeed: error flag, no match"); + goto no_match; + } + goto match; + default: + DEBUG_VALIDATE_BUG_ON(1); + goto no_match; } - goto match; } else if (smd->type == DETECT_ISDATAAT) { SCLogDebug("inspecting isdataat"); @@ -787,14 +809,20 @@ bool DetectEngineContentInspectionBuffer(DetectEngineCtx *de_ctx, DetectEngineTh bool DetectContentInspectionMatchOnAbsentBuffer(const SigMatchData *smd) { - // we will match on NULL buffers if there is one absent keyword - // absent: error_or does NOT match on absent buffers (only on transform errors) + // match on NULL buffers if the buffer carries absent, or + // transform_result: error_or, which matches an absent buffer as + // absent: or_else does. must_succeed and must_error never match one. bool absent_data = false; while (1) { if (smd->type == DETECT_ABSENT) { absent_data = true; break; } + if (smd->type == DETECT_TRANSFORM_RESULT) { + const DetectAbsentData *dad = (const DetectAbsentData *)smd->ctx; + absent_data = dad->mode == DETECT_ABSENT_ERROR_OR; + break; + } if (smd->is_last) { break; } diff --git a/src/detect-engine-mpm.c b/src/detect-engine-mpm.c index 88e807a4f5..ca1ecb4f81 100644 --- a/src/detect-engine-mpm.c +++ b/src/detect-engine-mpm.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2021 Open Information Security Foundation +/* Copyright (C) 2007-2026 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free @@ -1233,8 +1233,8 @@ void RetrieveFPForSig(const DetectEngineCtx *de_ctx, Signature *s) } for (SigMatch *sm = s->init_data->buffers[x].head; sm != NULL; sm = sm->next) { - // a buffer with absent keyword cannot be used as fast_pattern - if (sm->type == DETECT_ABSENT) + // a buffer with absent or transform_result cannot use fast_pattern + if (sm->type == DETECT_ABSENT || sm->type == DETECT_TRANSFORM_RESULT) break; if (sm->type != DETECT_CONTENT) continue; diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index 748388b86f..6baeb1bea7 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2024 Open Information Security Foundation +/* Copyright (C) 2007-2026 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free @@ -102,6 +102,7 @@ enum DetectKeywordId { DETECT_ISDATAAT, DETECT_URILEN, DETECT_ABSENT, + DETECT_TRANSFORM_RESULT, DETECT_ENTROPY, /* end of content inspection */ diff --git a/src/detect-isdataat.c b/src/detect-isdataat.c index bcda4f7b34..9161117eec 100644 --- a/src/detect-isdataat.c +++ b/src/detect-isdataat.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2020 Open Information Security Foundation +/* Copyright (C) 2007-2026 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free @@ -68,85 +68,253 @@ static void DetectAbsentFree(DetectEngineCtx *de_ctx, void *ptr) SCFree(ptr); } -static int DetectAbsentSetup(DetectEngineCtx *de_ctx, Signature *s, const char *optstr) +const char *DetectAbsentModeStr(enum DetectAbsentMode mode) +{ + switch (mode) { + case DETECT_ABSENT_ONLY: + return "only"; + case DETECT_ABSENT_OR_ELSE: + return "or_else"; + case DETECT_ABSENT_ERROR_OR: + return "error_or"; + case DETECT_ABSENT_MUST_ERROR: + return "must_error"; + case DETECT_ABSENT_MUST_SUCCEED: + return "must_succeed"; + } + return "unknown"; +} + +static int DetectAbsentAppend(DetectEngineCtx *de_ctx, Signature *s, enum DetectAbsentMode mode, + uint16_t sm_type, const char *kw) { if (s->init_data->list == DETECT_SM_LIST_NOTSET) { - SCLogError("no buffer for absent keyword"); + SCLogError("no buffer for %s keyword", kw); return -1; } if (DetectBufferGetActiveList(de_ctx, s) == -1) return -1; - bool or_else; - if (optstr == NULL) { - or_else = false; - } else if (strcmp(optstr, "or_else") == 0) { - or_else = true; - } else { - SCLogError("unhandled value for absent keyword: %s", optstr); - return -1; - } if (s->init_data->curbuf == NULL || s->init_data->list != (int)s->init_data->curbuf->id) { - SCLogError("unspected buffer for absent keyword"); + SCLogError("unexpected buffer for %s keyword", kw); return -1; } const DetectBufferType *b = DetectEngineBufferTypeGetById(de_ctx, s->init_data->list); if (!b || b->frame) { - SCLogError("absent does not work with frames"); + SCLogError("%s does not work with frames", kw); return -1; } if (s->init_data->curbuf->tail != NULL) { - SCLogError("absent must come first right after buffer"); + SCLogError("%s must come first right after buffer", kw); return -1; } DetectAbsentData *dad = SCMalloc(sizeof(DetectAbsentData)); if (unlikely(dad == NULL)) return -1; - dad->or_else = or_else; + dad->mode = mode; - if (SCSigMatchAppendSMToList( - de_ctx, s, DETECT_ABSENT, (SigMatchCtx *)dad, s->init_data->list) == NULL) { + if (SCSigMatchAppendSMToList(de_ctx, s, sm_type, (SigMatchCtx *)dad, s->init_data->list) == + NULL) { DetectAbsentFree(de_ctx, dad); return -1; } return 0; } -bool DetectAbsentValidateContentCallback(const Signature *s, const SignatureInitDataBuffer *b) +static int DetectAbsentSetup(DetectEngineCtx *de_ctx, Signature *s, const char *optstr) { + enum DetectAbsentMode mode = DETECT_ABSENT_ONLY; + + if (optstr == NULL || strcmp(optstr, "or_else") == 0) { + mode = optstr == NULL ? DETECT_ABSENT_ONLY : DETECT_ABSENT_OR_ELSE; + } else if (strcmp(optstr, "error_or") == 0 || strcmp(optstr, "must_error") == 0 || + strcmp(optstr, "must_succeed") == 0) { + SCLogError("absent does not take %s; use `transform_result: %s;` instead", optstr, optstr); + return -1; + } else { + SCLogError("unhandled value for absent keyword: %s. " + "Valid options are 'or_else' or no argument; " + "to match on the result of a transform, use the transform_result keyword " + "(must_succeed, must_error, error_or).", + optstr); + return -1; + } + return DetectAbsentAppend(de_ctx, s, mode, DETECT_ABSENT, "absent"); +} + +static int DetectTransformResultSetup(DetectEngineCtx *de_ctx, Signature *s, const char *optstr) +{ + if (optstr == NULL) { + SCLogError("transform_result requires one of must_succeed, must_error, error_or"); + return -1; + } + + enum DetectAbsentMode mode; + if (strcmp(optstr, "must_succeed") == 0) { + mode = DETECT_ABSENT_MUST_SUCCEED; + } else if (strcmp(optstr, "must_error") == 0) { + mode = DETECT_ABSENT_MUST_ERROR; + } else if (strcmp(optstr, "error_or") == 0) { + mode = DETECT_ABSENT_ERROR_OR; + } else { + SCLogError("unhandled value for transform_result keyword: %s. " + "Valid options are must_succeed, must_error, error_or.", + optstr); + return -1; + } + return DetectAbsentAppend(de_ctx, s, mode, DETECT_TRANSFORM_RESULT, "transform_result"); +} + +/* id of the buffer before any transforms were applied */ +static int DetectAbsentBaseBufferId(const DetectEngineCtx *de_ctx, const int id) +{ + const DetectBufferType *map = DetectEngineBufferTypeGetById(de_ctx, id); + if (map != NULL && map->transforms.cnt > 0) + return map->parent_id; + return id; +} + +/* A transform after absent or transform_result starts a new buffer, which + * takes the keywords that follow it. Tell whether a later buffer in the + * signature comes from the same base buffer as b. */ +static bool DetectAbsentHasLaterTransformedBuffer( + const DetectEngineCtx *de_ctx, const Signature *s, const SignatureInitDataBuffer *b) +{ + const int base = DetectAbsentBaseBufferId(de_ctx, b->id); + for (uint32_t x = (uint32_t)(b - s->init_data->buffers) + 1; x < s->init_data->buffer_index; + x++) { + const SignatureInitDataBuffer *later = &s->init_data->buffers[x]; + if (later->id == b->id || DetectAbsentBaseBufferId(de_ctx, later->id) != base) + continue; + const DetectBufferType *map = DetectEngineBufferTypeGetById(de_ctx, later->id); + if (map != NULL && map->transforms.cnt > 0) + return true; + } + return false; +} + +bool DetectAbsentValidateContentCallback( + const DetectEngineCtx *de_ctx, const Signature *s, const SignatureInitDataBuffer *b) +{ + const DetectAbsentData *absent = NULL; + int absent_count = 0; bool has_other = false; - bool only_absent = false; - bool has_absent = false; + bool has_fast_pattern = false; + for (const SigMatch *sm = b->head; sm != NULL; sm = sm->next) { - if (sm->type == DETECT_ABSENT) { - has_absent = true; - const DetectAbsentData *dad = (const DetectAbsentData *)sm->ctx; - if (!dad->or_else) { - only_absent = true; - } + if (sm->type == DETECT_ABSENT || sm->type == DETECT_TRANSFORM_RESULT) { + absent_count++; + absent = (const DetectAbsentData *)sm->ctx; } else { has_other = true; if (sm->type == DETECT_CONTENT) { const DetectContentData *cd = (DetectContentData *)sm->ctx; - if (has_absent && (cd->flags & DETECT_CONTENT_FAST_PATTERN)) { - SCLogError("signature can't have absent and fast_pattern on the same buffer"); - return false; - } + if (cd->flags & DETECT_CONTENT_FAST_PATTERN) + has_fast_pattern = true; } } } - if (only_absent && has_other) { - SCLogError("signature can't have a buffer tested absent and tested with other keywords " - "such as content"); + if (absent == NULL) + return true; + + const bool is_transform_result = + (absent->mode == DETECT_ABSENT_ERROR_OR || absent->mode == DETECT_ABSENT_MUST_ERROR || + absent->mode == DETECT_ABSENT_MUST_SUCCEED); + const char *kw = is_transform_result ? "transform_result" : "absent"; + + if (absent_count > 1) { + SCLogError("signature can't have more than one absent/transform_result keyword on the same " + "buffer"); return false; - } else if (has_absent && !only_absent && !has_other) { - SCLogError( - "signature with absent: or_else expects other keywords to test on such as content"); + } + if (has_fast_pattern) { + SCLogError("signature can't have %s and fast_pattern on the same buffer", kw); return false; } + + /* mode vs. presence of other keywords */ + switch (absent->mode) { + case DETECT_ABSENT_ONLY: + if (has_other) { + SCLogError("signature can't have a buffer tested absent and tested with other " + "keywords such as content"); + return false; + } + return true; + case DETECT_ABSENT_MUST_ERROR: + if (has_other) { + SCLogError("transform_result: must_error cannot be combined with other keywords"); + return false; + } + break; + case DETECT_ABSENT_OR_ELSE: + if (!has_other && DetectAbsentHasLaterTransformedBuffer(de_ctx, s, b)) { + SCLogError("absent: or_else must come after the buffer's transforms; a transform " + "after it starts a new buffer"); + return false; + } + if (!has_other) { + SCLogError("signature with absent: or_else expects other keywords to test on such " + "as content"); + return false; + } + break; + case DETECT_ABSENT_ERROR_OR: + case DETECT_ABSENT_MUST_SUCCEED: + if (!has_other && DetectAbsentHasLaterTransformedBuffer(de_ctx, s, b)) { + SCLogError("transform_result: %s must come after the buffer's transforms; a " + "transform after it starts a new buffer", + DetectAbsentModeStr(absent->mode)); + return false; + } + if (!has_other) { + SCLogError("signature with transform_result: %s expects other keywords to test on " + "such as content", + DetectAbsentModeStr(absent->mode)); + return false; + } + break; + } + + /* mode vs. transform capability */ + const DetectBufferType *map = DetectEngineBufferTypeGetById(de_ctx, b->id); + DEBUG_VALIDATE_BUG_ON(map == NULL); + if (map == NULL) + return false; + + bool has_can_fail_transform = false; + for (int i = 0; i < map->transforms.cnt; i++) { + int transform = map->transforms.transforms[i].transform; + if (sigmatch_table[transform].flags & SIGMATCH_TRANSFORM_CAN_FAIL) { + has_can_fail_transform = true; + break; + } + } + + switch (absent->mode) { + case DETECT_ABSENT_ERROR_OR: + case DETECT_ABSENT_MUST_ERROR: + case DETECT_ABSENT_MUST_SUCCEED: + if (!has_can_fail_transform) { + SCLogError("transform_result requires a transform that can fail " + "(e.g. from_base64)"); + return false; + } + break; + case DETECT_ABSENT_OR_ELSE: + if (has_can_fail_transform) { + SCLogError("absent: or_else on a buffer with a transform that can fail; " + "use `transform_result: error_or` instead"); + return false; + } + break; + case DETECT_ABSENT_ONLY: + break; + } + return true; } @@ -181,6 +349,13 @@ void DetectIsdataatRegister(void) sigmatch_table[DETECT_ABSENT].RegisterTests = DetectAbsentRegisterTests; #endif + sigmatch_table[DETECT_TRANSFORM_RESULT].name = "transform_result"; + sigmatch_table[DETECT_TRANSFORM_RESULT].desc = + "match on the result of a preceding transform (must_succeed, must_error, error_or)"; + sigmatch_table[DETECT_TRANSFORM_RESULT].url = "/rules/payload-keywords.html#transform-result"; + sigmatch_table[DETECT_TRANSFORM_RESULT].Setup = DetectTransformResultSetup; + sigmatch_table[DETECT_TRANSFORM_RESULT].Free = DetectAbsentFree; + DetectSetupParseRegexes(PARSE_REGEX, &parse_regex); } @@ -715,8 +890,212 @@ static int DetectAbsentTestParse01(void) PASS; } +/** + * \test error_or and must_error validation + */ +static int DetectAbsentTestParse02(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF(de_ctx == NULL); + de_ctx->flags |= DE_QUIET; + + /* transform_result: error_or without a failing transform should fail */ + Signature *s = DetectEngineAppendSig(de_ctx, + "alert http any any -> any any " + "(msg:\"error_or without failing transform\"; http.user_agent; " + "transform_result: error_or; content:\"one\"; sid:1;)"); + FAIL_IF(s != NULL); + + /* transform_result: must_error without a failing transform should fail */ + s = DetectEngineAppendSig(de_ctx, + "alert http any any -> any any " + "(msg:\"must_error without failing transform\"; http.user_agent; " + "transform_result: must_error; sid:2;)"); + FAIL_IF(s != NULL); + + /* transform_result: must_error with content on same buffer should fail */ + s = DetectEngineAppendSig(de_ctx, + "alert http any any -> any any " + "(msg:\"must_error with content\"; http.uri; " + "from_base64: mode strict; transform_result: must_error; content:\"test\"; sid:3;)"); + FAIL_IF(s != NULL); + + /* absent: or_else on buffer with failing transform should fail */ + s = DetectEngineAppendSig(de_ctx, + "alert http any any -> any any " + "(msg:\"or_else with failing transform\"; http.uri; " + "from_base64: mode strict; absent: or_else; content:\"test\"; sid:4;)"); + FAIL_IF(s != NULL); + + /* invalid absent option should fail */ + s = DetectEngineAppendSig(de_ctx, "alert http any any -> any any " + "(msg:\"invalid absent option\"; http.user_agent; " + "absent: invalid; sid:5;)"); + FAIL_IF(s != NULL); + + /* absent: or_else without other keywords should fail */ + s = DetectEngineAppendSig(de_ctx, "alert http any any -> any any " + "(msg:\"or_else alone\"; http.user_agent; " + "absent: or_else; sid:6;)"); + FAIL_IF(s != NULL); + + /* transform_result: error_or without other keywords should fail */ + s = DetectEngineAppendSig(de_ctx, + "alert http any any -> any any " + "(msg:\"error_or alone\"; http.uri; " + "from_base64: mode strict; transform_result: error_or; sid:7;)"); + FAIL_IF(s != NULL); + + /* transform_result: must_succeed without a failing transform should fail */ + s = DetectEngineAppendSig(de_ctx, + "alert http any any -> any any " + "(msg:\"must_succeed without failing transform\"; http.user_agent; " + "transform_result: must_succeed; content:\"test\"; sid:8;)"); + FAIL_IF(s != NULL); + + /* transform_result: must_succeed without other keywords should fail */ + s = DetectEngineAppendSig(de_ctx, + "alert http any any -> any any " + "(msg:\"must_succeed alone\"; http.uri; " + "from_base64: mode strict; transform_result: must_succeed; sid:9;)"); + FAIL_IF(s != NULL); + + /* absent does not take transform_result's arguments */ + s = DetectEngineAppendSig(de_ctx, + "alert http any any -> any any " + "(msg:\"absent with must_succeed\"; http.uri; " + "from_base64: mode strict; absent: must_succeed; content:\"test\"; sid:10;)"); + FAIL_IF(s != NULL); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +/** + * \test Valid absent configurations should succeed + */ +static int DetectAbsentTestParse03(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF(de_ctx == NULL); + de_ctx->flags |= DE_QUIET; + + /* valid: bare absent */ + Signature *s = DetectEngineAppendSig(de_ctx, "alert http any any -> any any " + "(msg:\"bare absent\"; http.user_agent; " + "absent; sid:1;)"); + FAIL_IF(s == NULL); + + /* valid: or_else with content */ + s = DetectEngineAppendSig(de_ctx, "alert http any any -> any any " + "(msg:\"or_else with content\"; http.user_agent; " + "absent: or_else; content:!\"one\"; sid:2;)"); + FAIL_IF(s == NULL); + + /* valid: error_or with failing transform and content */ + s = DetectEngineAppendSig(de_ctx, + "alert http any any -> any any " + "(msg:\"error_or with from_base64\"; http.uri; " + "from_base64: mode strict; transform_result: error_or; content:\"test\"; sid:3;)"); + FAIL_IF(s == NULL); + + /* valid: must_error standalone with failing transform */ + s = DetectEngineAppendSig(de_ctx, + "alert http any any -> any any " + "(msg:\"must_error standalone\"; http.uri; " + "from_base64: mode strict; transform_result: must_error; sid:4;)"); + FAIL_IF(s == NULL); + + /* valid: must_error cross-buffer */ + s = DetectEngineAppendSig(de_ctx, "alert http any any -> any any " + "(msg:\"must_error cross-buffer\"; http.uri; " + "from_base64: mode strict; transform_result: must_error; " + "http.host; content:\"test\"; sid:5;)"); + FAIL_IF(s == NULL); + + /* valid: must_succeed with failing transform and content */ + s = DetectEngineAppendSig(de_ctx, + "alert http any any -> any any " + "(msg:\"must_succeed with from_base64\"; http.uri; " + "from_base64: mode strict; transform_result: must_succeed; content:\"test\"; sid:6;)"); + FAIL_IF(s == NULL); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +/** + * \test Duplicate-absent and fast_pattern rejection in DetectAbsentValidateContentCallback + */ +static int DetectAbsentTestParse04(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF(de_ctx == NULL); + de_ctx->flags |= DE_QUIET; + + /* two bare absents on the same buffer should fail */ + Signature *s = DetectEngineAppendSig(de_ctx, "alert http any any -> any any " + "(msg:\"dup bare absent\"; http.user_agent; " + "absent; absent; sid:1;)"); + FAIL_IF(s != NULL); + + /* bare absent followed by absent: or_else on the same buffer should fail */ + s = DetectEngineAppendSig(de_ctx, "alert http any any -> any any " + "(msg:\"dup absent mixed modes\"; http.user_agent; " + "absent; absent: or_else; content:!\"one\"; sid:2;)"); + FAIL_IF(s != NULL); + + /* two absent: or_else on the same buffer should fail */ + s = DetectEngineAppendSig(de_ctx, + "alert http any any -> any any " + "(msg:\"dup or_else\"; http.user_agent; " + "absent: or_else; absent: or_else; content:!\"one\"; sid:3;)"); + FAIL_IF(s != NULL); + + /* bare absent + fast_pattern on the same buffer should fail */ + s = DetectEngineAppendSig(de_ctx, "alert http any any -> any any " + "(msg:\"bare absent + fast_pattern\"; http.user_agent; " + "absent; content:\"one\"; fast_pattern; sid:4;)"); + FAIL_IF(s != NULL); + + /* absent: or_else + fast_pattern on the same buffer should fail */ + s = DetectEngineAppendSig(de_ctx, "alert http any any -> any any " + "(msg:\"or_else + fast_pattern\"; http.user_agent; " + "absent: or_else; content:!\"one\"; fast_pattern; sid:5;)"); + FAIL_IF(s != NULL); + + /* transform_result: error_or + fast_pattern on the same buffer should fail */ + s = DetectEngineAppendSig(de_ctx, + "alert http any any -> any any " + "(msg:\"error_or + fast_pattern\"; http.uri; " + "from_base64: mode strict; transform_result: error_or; content:\"x\"; fast_pattern; " + "sid:6;)"); + FAIL_IF(s != NULL); + + /* transform_result: must_succeed + fast_pattern on the same buffer should fail */ + 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); + + /* fast_pattern appearing before absent on the same buffer should still fail + * (the simplified walk is order-independent for fast_pattern detection) */ + s = DetectEngineAppendSig(de_ctx, "alert http any any -> any any " + "(msg:\"fast_pattern before or_else\"; http.user_agent; " + "content:!\"one\"; fast_pattern; absent: or_else; sid:8;)"); + FAIL_IF(s != NULL); + + DetectEngineCtxFree(de_ctx); + PASS; +} + void DetectAbsentRegisterTests(void) { UtRegisterTest("DetectAbsentTestParse01", DetectAbsentTestParse01); + UtRegisterTest("DetectAbsentTestParse02", DetectAbsentTestParse02); + UtRegisterTest("DetectAbsentTestParse03", DetectAbsentTestParse03); + UtRegisterTest("DetectAbsentTestParse04", DetectAbsentTestParse04); } #endif diff --git a/src/detect-isdataat.h b/src/detect-isdataat.h index f1932ff56c..3d5977c9cb 100644 --- a/src/detect-isdataat.h +++ b/src/detect-isdataat.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2010 Open Information Security Foundation +/* Copyright (C) 2007-2026 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free @@ -34,14 +34,25 @@ typedef struct DetectIsdataatData_ { uint8_t flags; /* isdataat options*/ } DetectIsdataatData; +enum DetectAbsentMode { + DETECT_ABSENT_ONLY, /**< match only on absent buffer */ + DETECT_ABSENT_OR_ELSE, /**< match on absent buffer or fall through to next keywords */ + DETECT_ABSENT_ERROR_OR, /**< match on transform error or fall through to next keywords */ + DETECT_ABSENT_MUST_ERROR, /**< match only on transform error */ + DETECT_ABSENT_MUST_SUCCEED /**< reject on transform error, fall through to next keywords on + success */ +}; + typedef struct DetectAbsentData_ { - /** absent or try to match with other keywords (false means only absent) */ - bool or_else; + enum DetectAbsentMode mode; } DetectAbsentData; /* prototypes */ void DetectIsdataatRegister (void); -bool DetectAbsentValidateContentCallback(const Signature *s, const SignatureInitDataBuffer *); +bool DetectAbsentValidateContentCallback( + const DetectEngineCtx *de_ctx, const Signature *s, const SignatureInitDataBuffer *); + +const char *DetectAbsentModeStr(enum DetectAbsentMode mode); #endif /* SURICATA_DETECT_ISDATAAT_H */ diff --git a/src/detect-parse.c b/src/detect-parse.c index d79642ffd0..d337a1b8ff 100644 --- a/src/detect-parse.c +++ b/src/detect-parse.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2025 Open Information Security Foundation +/* Copyright (C) 2007-2026 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free @@ -3106,7 +3106,7 @@ static int SigValidateCheckBuffers( if (!DetectBsizeValidateContentCallback(s, b)) { SCReturnInt(0); } - if (!DetectAbsentValidateContentCallback(s, b)) { + if (!DetectAbsentValidateContentCallback(de_ctx, s, b)) { SCReturnInt(0); } }