mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1185 lines
42 KiB
C
1185 lines
42 KiB
C
/* 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
|
|
* Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* version 2 along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* \file
|
|
*
|
|
* \author Pablo Rincon <pablo.rincon.crespo@gmail.com>
|
|
*
|
|
* Implements isdataat keyword
|
|
*/
|
|
|
|
#include "suricata-common.h"
|
|
#include "decode.h"
|
|
#include "detect.h"
|
|
#include "detect-engine.h"
|
|
#include "detect-engine-buffer.h"
|
|
#include "detect-parse.h"
|
|
#include "app-layer.h"
|
|
|
|
#include "util-unittest-helper.h"
|
|
|
|
#include "detect-isdataat.h"
|
|
#include "detect-content.h"
|
|
#include "detect-bytetest.h"
|
|
#include "detect-uricontent.h"
|
|
#include "detect-engine-build.h"
|
|
|
|
#include "flow.h"
|
|
#include "flow-var.h"
|
|
|
|
#include "util-debug.h"
|
|
#include "util-byte.h"
|
|
#include "detect-pcre.h"
|
|
#include "detect-byte.h"
|
|
|
|
/**
|
|
* \brief Regex for parsing our isdataat options
|
|
*/
|
|
#define PARSE_REGEX "^\\s*!?([^\\s,]+)\\s*(,\\s*relative)?\\s*(,\\s*rawbytes\\s*)?\\s*$"
|
|
|
|
static DetectParseRegex parse_regex;
|
|
|
|
int DetectIsdataatSetup (DetectEngineCtx *, Signature *, const char *);
|
|
#ifdef UNITTESTS
|
|
static void DetectIsdataatRegisterTests(void);
|
|
static void DetectAbsentRegisterTests(void);
|
|
#endif
|
|
void DetectIsdataatFree(DetectEngineCtx *, void *);
|
|
|
|
static int DetectEndsWithSetup (DetectEngineCtx *de_ctx, Signature *s, const char *nullstr);
|
|
|
|
static void DetectAbsentFree(DetectEngineCtx *de_ctx, void *ptr)
|
|
{
|
|
SCFree(ptr);
|
|
}
|
|
|
|
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 %s keyword", kw);
|
|
return -1;
|
|
}
|
|
|
|
if (DetectBufferGetActiveList(de_ctx, s) == -1)
|
|
return -1;
|
|
|
|
if (s->init_data->curbuf == NULL || s->init_data->list != (int)s->init_data->curbuf->id) {
|
|
SCLogError("unexpected buffer for %s keyword", kw);
|
|
return -1;
|
|
}
|
|
const DetectBufferType *b = DetectEngineBufferTypeGetById(de_ctx, s->init_data->list);
|
|
if (!b || b->frame) {
|
|
SCLogError("%s does not work with frames", kw);
|
|
return -1;
|
|
}
|
|
if (s->init_data->curbuf->tail != NULL) {
|
|
SCLogError("%s must come first right after buffer", kw);
|
|
return -1;
|
|
}
|
|
DetectAbsentData *dad = SCMalloc(sizeof(DetectAbsentData));
|
|
if (unlikely(dad == NULL))
|
|
return -1;
|
|
|
|
dad->mode = mode;
|
|
|
|
if (SCSigMatchAppendSMToList(de_ctx, s, sm_type, (SigMatchCtx *)dad, s->init_data->list) ==
|
|
NULL) {
|
|
DetectAbsentFree(de_ctx, dad);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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 has_fast_pattern = false;
|
|
|
|
for (const SigMatch *sm = b->head; sm != NULL; sm = sm->next) {
|
|
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 (cd->flags & DETECT_CONTENT_FAST_PATTERN)
|
|
has_fast_pattern = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/**
|
|
* \brief Registration function for isdataat: keyword
|
|
*/
|
|
void DetectIsdataatRegister(void)
|
|
{
|
|
sigmatch_table[DETECT_ISDATAAT].name = "isdataat";
|
|
sigmatch_table[DETECT_ISDATAAT].desc = "check if there is still data at a specific part of the payload";
|
|
sigmatch_table[DETECT_ISDATAAT].url = "/rules/payload-keywords.html#isdataat";
|
|
/* match is handled in DetectEngineContentInspection() */
|
|
sigmatch_table[DETECT_ISDATAAT].Match = NULL;
|
|
sigmatch_table[DETECT_ISDATAAT].Setup = DetectIsdataatSetup;
|
|
sigmatch_table[DETECT_ISDATAAT].Free = DetectIsdataatFree;
|
|
#ifdef UNITTESTS
|
|
sigmatch_table[DETECT_ISDATAAT].RegisterTests = DetectIsdataatRegisterTests;
|
|
#endif
|
|
sigmatch_table[DETECT_ENDS_WITH].name = "endswith";
|
|
sigmatch_table[DETECT_ENDS_WITH].desc = "make sure the previous content matches exactly at the end of the buffer";
|
|
sigmatch_table[DETECT_ENDS_WITH].url = "/rules/payload-keywords.html#endswith";
|
|
sigmatch_table[DETECT_ENDS_WITH].Setup = DetectEndsWithSetup;
|
|
sigmatch_table[DETECT_ENDS_WITH].flags = SIGMATCH_NOOPT;
|
|
|
|
sigmatch_table[DETECT_ABSENT].name = "absent";
|
|
sigmatch_table[DETECT_ABSENT].desc = "test if the buffer is absent";
|
|
sigmatch_table[DETECT_ABSENT].url = "/rules/payload-keywords.html#absent";
|
|
sigmatch_table[DETECT_ABSENT].Setup = DetectAbsentSetup;
|
|
sigmatch_table[DETECT_ABSENT].Free = DetectAbsentFree;
|
|
sigmatch_table[DETECT_ABSENT].flags = SIGMATCH_OPTIONAL_OPT;
|
|
#ifdef UNITTESTS
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* \brief This function is used to parse isdataat options passed via isdataat: keyword
|
|
*
|
|
* \param de_ctx Pointer to the detection engine context
|
|
* \param isdataatstr Pointer to the user provided isdataat options
|
|
*
|
|
* \retval idad pointer to DetectIsdataatData on success
|
|
* \retval NULL on failure
|
|
*/
|
|
static DetectIsdataatData *DetectIsdataatParse (DetectEngineCtx *de_ctx, const char *isdataatstr, char **offset)
|
|
{
|
|
DetectIsdataatData *idad = NULL;
|
|
char *args[3] = {NULL,NULL,NULL};
|
|
int res = 0;
|
|
size_t pcre2_len;
|
|
int i=0;
|
|
|
|
pcre2_match_data *match = NULL;
|
|
int ret = DetectParsePcreExec(&parse_regex, &match, isdataatstr, 0, 0);
|
|
if (ret < 1 || ret > 4) {
|
|
SCLogError("pcre_exec parse error, ret %" PRId32 ", string %s", ret, isdataatstr);
|
|
goto error;
|
|
}
|
|
|
|
if (ret > 1) {
|
|
const char *str_ptr;
|
|
res = pcre2_substring_get_bynumber(match, 1, (PCRE2_UCHAR8 **)&str_ptr, &pcre2_len);
|
|
if (res < 0) {
|
|
SCLogError("pcre2_substring_get_bynumber failed");
|
|
goto error;
|
|
}
|
|
args[0] = (char *)str_ptr;
|
|
|
|
|
|
if (ret > 2) {
|
|
res = pcre2_substring_get_bynumber(match, 2, (PCRE2_UCHAR8 **)&str_ptr, &pcre2_len);
|
|
if (res < 0) {
|
|
SCLogError("pcre2_substring_get_bynumber failed");
|
|
goto error;
|
|
}
|
|
args[1] = (char *)str_ptr;
|
|
}
|
|
if (ret > 3) {
|
|
res = pcre2_substring_get_bynumber(match, 3, (PCRE2_UCHAR8 **)&str_ptr, &pcre2_len);
|
|
if (res < 0) {
|
|
SCLogError("pcre2_substring_get_bynumber failed");
|
|
goto error;
|
|
}
|
|
args[2] = (char *)str_ptr;
|
|
}
|
|
|
|
idad = SCMalloc(sizeof(DetectIsdataatData));
|
|
if (unlikely(idad == NULL))
|
|
goto error;
|
|
|
|
idad->flags = 0;
|
|
idad->dataat = 0;
|
|
|
|
if (args[0][0] != '-' && isalpha((unsigned char)args[0][0])) {
|
|
if (offset == NULL) {
|
|
SCLogError("isdataat supplied with "
|
|
"var name for offset. \"offset\" argument supplied to "
|
|
"this function has to be non-NULL");
|
|
goto error;
|
|
}
|
|
*offset = SCStrdup(args[0]);
|
|
if (*offset == NULL)
|
|
goto error;
|
|
} else {
|
|
if (StringParseUint16(&idad->dataat, 10,
|
|
strlen(args[0]), args[0]) < 0 ) {
|
|
SCLogError("isdataat out of range");
|
|
SCFree(idad);
|
|
idad = NULL;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (args[1] !=NULL) {
|
|
idad->flags |= ISDATAAT_RELATIVE;
|
|
|
|
if(args[2] !=NULL)
|
|
idad->flags |= ISDATAAT_RAWBYTES;
|
|
}
|
|
|
|
if (isdataatstr[0] == '!') {
|
|
idad->flags |= ISDATAAT_NEGATED;
|
|
}
|
|
|
|
for (i = 0; i < (ret -1); i++) {
|
|
if (args[i] != NULL)
|
|
pcre2_substring_free((PCRE2_UCHAR8 *)args[i]);
|
|
}
|
|
|
|
pcre2_match_data_free(match);
|
|
return idad;
|
|
|
|
}
|
|
|
|
error:
|
|
if (match) {
|
|
pcre2_match_data_free(match);
|
|
}
|
|
for (i = 0; i < (ret -1) && i < 3; i++){
|
|
if (args[i] != NULL)
|
|
pcre2_substring_free((PCRE2_UCHAR8 *)args[i]);
|
|
}
|
|
|
|
if (idad != NULL)
|
|
DetectIsdataatFree(de_ctx, idad);
|
|
return NULL;
|
|
|
|
}
|
|
|
|
/**
|
|
* \brief This function is used to add the parsed isdataatdata into the current
|
|
* signature.
|
|
* \param de_ctx pointer to the Detection Engine Context
|
|
* \param s pointer to the Current Signature
|
|
* \param isdataatstr pointer to the user provided isdataat options
|
|
*
|
|
* \retval 0 on Success
|
|
* \retval -1 on Failure
|
|
*/
|
|
int DetectIsdataatSetup (DetectEngineCtx *de_ctx, Signature *s, const char *isdataatstr)
|
|
{
|
|
SigMatch *prev_pm = NULL;
|
|
DetectIsdataatData *idad = NULL;
|
|
char *offset = NULL;
|
|
int ret = -1;
|
|
|
|
idad = DetectIsdataatParse(de_ctx, isdataatstr, &offset);
|
|
if (idad == NULL)
|
|
return -1;
|
|
|
|
int sm_list;
|
|
if (s->init_data->list != DETECT_SM_LIST_NOTSET) {
|
|
if (DetectBufferGetActiveList(de_ctx, s) == -1)
|
|
goto end;
|
|
sm_list = s->init_data->list;
|
|
|
|
if (idad->flags & ISDATAAT_RELATIVE) {
|
|
prev_pm = SCDetectGetLastSMFromLists(s, DETECT_CONTENT, DETECT_PCRE, -1);
|
|
}
|
|
} else if (idad->flags & ISDATAAT_RELATIVE) {
|
|
prev_pm = SCDetectGetLastSMFromLists(s, DETECT_CONTENT, DETECT_PCRE, DETECT_BYTETEST,
|
|
DETECT_BYTEJUMP, DETECT_BYTE_EXTRACT, DETECT_ISDATAAT, DETECT_BYTEMATH, -1);
|
|
if (prev_pm == NULL)
|
|
sm_list = DETECT_SM_LIST_PMATCH;
|
|
else {
|
|
sm_list = SigMatchListSMBelongsTo(s, prev_pm);
|
|
if (sm_list < 0)
|
|
goto end;
|
|
}
|
|
} else {
|
|
sm_list = DETECT_SM_LIST_PMATCH;
|
|
}
|
|
|
|
if (offset != NULL) {
|
|
DetectByteIndexType index;
|
|
if (!DetectByteRetrieveSMVar(offset, s, -1, &index)) {
|
|
SCLogError("Unknown byte_extract var "
|
|
"seen in isdataat - %s\n",
|
|
offset);
|
|
goto end;
|
|
}
|
|
idad->dataat = index;
|
|
idad->flags |= ISDATAAT_OFFSET_VAR;
|
|
SCLogDebug("isdataat uses byte_extract with local id %u", idad->dataat);
|
|
SCFree(offset);
|
|
offset = NULL;
|
|
}
|
|
|
|
/* 'ends with' scenario */
|
|
if (prev_pm != NULL && prev_pm->type == DETECT_CONTENT &&
|
|
idad->dataat == 1 &&
|
|
(idad->flags & (ISDATAAT_RELATIVE|ISDATAAT_NEGATED)) == (ISDATAAT_RELATIVE|ISDATAAT_NEGATED))
|
|
{
|
|
DetectIsdataatFree(de_ctx, idad);
|
|
DetectContentData *cd = (DetectContentData *)prev_pm->ctx;
|
|
cd->flags |= DETECT_CONTENT_ENDS_WITH;
|
|
ret = 0;
|
|
goto end;
|
|
}
|
|
|
|
if (SCSigMatchAppendSMToList(de_ctx, s, DETECT_ISDATAAT, (SigMatchCtx *)idad, sm_list) ==
|
|
NULL) {
|
|
goto end;
|
|
}
|
|
|
|
if (!(idad->flags & ISDATAAT_RELATIVE)) {
|
|
ret = 0;
|
|
goto end;
|
|
}
|
|
|
|
if (prev_pm == NULL) {
|
|
ret = 0;
|
|
goto end;
|
|
}
|
|
|
|
if (prev_pm->type == DETECT_CONTENT) {
|
|
DetectContentData *cd = (DetectContentData *)prev_pm->ctx;
|
|
cd->flags |= DETECT_CONTENT_RELATIVE_NEXT;
|
|
} else if (prev_pm->type == DETECT_PCRE) {
|
|
DetectPcreData *pd = (DetectPcreData *)prev_pm->ctx;
|
|
pd->flags |= DETECT_PCRE_RELATIVE_NEXT;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
end:
|
|
if (offset)
|
|
SCFree(offset);
|
|
if (ret != 0)
|
|
DetectIsdataatFree(de_ctx, idad);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* \brief this function will free memory associated with DetectIsdataatData
|
|
*
|
|
* \param idad pointer to DetectIsdataatData
|
|
*/
|
|
void DetectIsdataatFree(DetectEngineCtx *de_ctx, void *ptr)
|
|
{
|
|
DetectIsdataatData *idad = (DetectIsdataatData *)ptr;
|
|
SCFree(idad);
|
|
}
|
|
|
|
static int DetectEndsWithSetup (DetectEngineCtx *de_ctx, Signature *s, const char *nullstr)
|
|
{
|
|
SigMatch *pm = NULL;
|
|
int ret = -1;
|
|
|
|
/* retrieve the sm to apply the depth against */
|
|
pm = SCDetectGetLastSMFromLists(s, DETECT_CONTENT, -1);
|
|
if (pm == NULL) {
|
|
SCLogError("endswith needs a "
|
|
"preceding content option");
|
|
goto end;
|
|
}
|
|
|
|
/* verify other conditions. */
|
|
DetectContentData *cd = (DetectContentData *)pm->ctx;
|
|
|
|
cd->flags |= DETECT_CONTENT_ENDS_WITH;
|
|
|
|
ret = 0;
|
|
end:
|
|
return ret;
|
|
}
|
|
|
|
#ifdef UNITTESTS
|
|
static int g_dce_stub_data_buffer_id = 0;
|
|
|
|
/**
|
|
* \test DetectIsdataatTestParse01 is a test to make sure that we return a correct IsdataatData structure
|
|
* when given valid isdataat opt
|
|
*/
|
|
static int DetectIsdataatTestParse01 (void)
|
|
{
|
|
int result = 0;
|
|
DetectIsdataatData *idad = NULL;
|
|
idad = DetectIsdataatParse(NULL, "30 ", NULL);
|
|
if (idad != NULL) {
|
|
DetectIsdataatFree(NULL, idad);
|
|
result = 1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* \test DetectIsdataatTestParse02 is a test to make sure that we return a correct IsdataatData structure
|
|
* when given valid isdataat opt
|
|
*/
|
|
static int DetectIsdataatTestParse02 (void)
|
|
{
|
|
int result = 0;
|
|
DetectIsdataatData *idad = NULL;
|
|
idad = DetectIsdataatParse(NULL, "30 , relative", NULL);
|
|
if (idad != NULL && idad->flags & ISDATAAT_RELATIVE && !(idad->flags & ISDATAAT_RAWBYTES)) {
|
|
DetectIsdataatFree(NULL, idad);
|
|
result = 1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* \test DetectIsdataatTestParse03 is a test to make sure that we return a correct IsdataatData structure
|
|
* when given valid isdataat opt
|
|
*/
|
|
static int DetectIsdataatTestParse03 (void)
|
|
{
|
|
int result = 0;
|
|
DetectIsdataatData *idad = NULL;
|
|
idad = DetectIsdataatParse(NULL, "30,relative, rawbytes ", NULL);
|
|
if (idad != NULL && idad->flags & ISDATAAT_RELATIVE && idad->flags & ISDATAAT_RAWBYTES) {
|
|
DetectIsdataatFree(NULL, idad);
|
|
result = 1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* \test Test isdataat option for dce sig.
|
|
*/
|
|
static int DetectIsdataatTestParse04(void)
|
|
{
|
|
Signature *s = SigAlloc();
|
|
FAIL_IF_NULL(s);
|
|
|
|
FAIL_IF(SCDetectSignatureSetAppProto(s, ALPROTO_DCERPC) < 0);
|
|
|
|
FAIL_IF_NOT(DetectIsdataatSetup(NULL, s, "30") == 0);
|
|
SigMatch *sm = DetectBufferGetFirstSigMatch(s, g_dce_stub_data_buffer_id);
|
|
FAIL_IF_NOT_NULL(sm);
|
|
FAIL_IF_NULL(s->init_data->smlists[DETECT_SM_LIST_PMATCH]);
|
|
SigFree(NULL, s);
|
|
|
|
s = SigAlloc();
|
|
FAIL_IF_NULL(s);
|
|
FAIL_IF(SCDetectSignatureSetAppProto(s, ALPROTO_DCERPC) < 0);
|
|
/* relative w/o preceeding match defaults to "pmatch" */
|
|
FAIL_IF_NOT(DetectIsdataatSetup(NULL, s, "30,relative") == 0);
|
|
sm = DetectBufferGetFirstSigMatch(s, g_dce_stub_data_buffer_id);
|
|
FAIL_IF_NOT_NULL(sm);
|
|
FAIL_IF_NULL(s->init_data->smlists[DETECT_SM_LIST_PMATCH]);
|
|
|
|
SigFree(NULL, s);
|
|
PASS;
|
|
}
|
|
|
|
static int DetectIsdataatTestParse06(void)
|
|
{
|
|
DetectEngineCtx *de_ctx = DetectEngineCtxInit();
|
|
FAIL_IF(de_ctx == NULL);
|
|
de_ctx->flags |= DE_QUIET;
|
|
|
|
Signature *s = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any "
|
|
"(msg:\"Testing bytejump_body\"; "
|
|
"content:\"one\"; "
|
|
"isdataat:!4,relative; sid:1;)");
|
|
FAIL_IF(s == NULL);
|
|
|
|
FAIL_IF(s->init_data->smlists_tail[DETECT_SM_LIST_PMATCH] == NULL);
|
|
|
|
FAIL_IF_NOT(s->init_data->smlists_tail[DETECT_SM_LIST_PMATCH]->type == DETECT_ISDATAAT);
|
|
DetectIsdataatData *data =
|
|
(DetectIsdataatData *)s->init_data->smlists_tail[DETECT_SM_LIST_PMATCH]->ctx;
|
|
|
|
FAIL_IF_NOT(data->flags & ISDATAAT_RELATIVE);
|
|
FAIL_IF(data->flags & ISDATAAT_RAWBYTES);
|
|
FAIL_IF_NOT(data->flags & ISDATAAT_NEGATED);
|
|
|
|
s = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any "
|
|
"(msg:\"Testing bytejump_body\"; "
|
|
"content:\"one\"; "
|
|
"isdataat: !4,relative; sid:2;)");
|
|
FAIL_IF(s == NULL);
|
|
|
|
FAIL_IF(s->init_data->smlists_tail[DETECT_SM_LIST_PMATCH] == NULL);
|
|
|
|
FAIL_IF_NOT(s->init_data->smlists_tail[DETECT_SM_LIST_PMATCH]->type == DETECT_ISDATAAT);
|
|
data = (DetectIsdataatData *)s->init_data->smlists_tail[DETECT_SM_LIST_PMATCH]->ctx;
|
|
|
|
FAIL_IF_NOT(data->flags & ISDATAAT_RELATIVE);
|
|
FAIL_IF(data->flags & ISDATAAT_RAWBYTES);
|
|
FAIL_IF_NOT(data->flags & ISDATAAT_NEGATED);
|
|
DetectEngineCtxFree(de_ctx);
|
|
|
|
PASS;
|
|
}
|
|
|
|
/**
|
|
* \test DetectIsdataatTestPacket01 is a test to check matches of
|
|
* isdataat, and isdataat relative
|
|
*/
|
|
static int DetectIsdataatTestPacket01 (void)
|
|
{
|
|
int result = 0;
|
|
uint8_t *buf = (uint8_t *)"Hi all!";
|
|
uint16_t buflen = strlen((char *)buf);
|
|
Packet *p[3];
|
|
p[0] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_TCP);
|
|
p[1] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_UDP);
|
|
p[2] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_ICMP);
|
|
|
|
if (p[0] == NULL || p[1] == NULL ||p[2] == NULL)
|
|
goto end;
|
|
|
|
const char *sigs[5];
|
|
sigs[0]= "alert ip any any -> any any (msg:\"Testing window 1\"; isdataat:6; sid:1;)";
|
|
sigs[1]= "alert ip any any -> any any (msg:\"Testing window 2\"; content:\"all\"; isdataat:1, relative; isdataat:6; sid:2;)";
|
|
sigs[2]= "alert ip any any -> any any (msg:\"Testing window 3\"; isdataat:8; sid:3;)";
|
|
sigs[3]= "alert ip any any -> any any (msg:\"Testing window 4\"; content:\"Hi\"; isdataat:5, relative; sid:4;)";
|
|
sigs[4]= "alert ip any any -> any any (msg:\"Testing window 4\"; content:\"Hi\"; isdataat:6, relative; sid:5;)";
|
|
|
|
uint32_t sid[5] = {1, 2, 3, 4, 5};
|
|
|
|
uint32_t results[3][5] = {
|
|
/* packet 0 match sid 1 but should not match sid 2 */
|
|
{1, 1, 0, 1, 0},
|
|
/* packet 1 should not match */
|
|
{1, 1, 0, 1, 0},
|
|
/* packet 2 should not match */
|
|
{1, 1, 0, 1, 0} };
|
|
|
|
result = UTHGenericTest(p, 3, sigs, sid, (uint32_t *) results, 5);
|
|
|
|
UTHFreePackets(p, 3);
|
|
end:
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* \test DetectIsdataatTestPacket02 is a test to check matches of
|
|
* isdataat, and isdataat relative works if the previous keyword is pcre
|
|
* (bug 144)
|
|
*/
|
|
static int DetectIsdataatTestPacket02 (void)
|
|
{
|
|
int result = 0;
|
|
uint8_t *buf = (uint8_t *)"GET /AllWorkAndNoPlayMakesWillADullBoy HTTP/1.0"
|
|
"User-Agent: Wget/1.11.4"
|
|
"Accept: */*"
|
|
"Host: www.google.com"
|
|
"Connection: Keep-Alive"
|
|
"Date: Mon, 04 Jan 2010 17:29:39 GMT";
|
|
uint16_t buflen = strlen((char *)buf);
|
|
Packet *p;
|
|
p = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_TCP);
|
|
|
|
if (p == NULL)
|
|
goto end;
|
|
|
|
char sig[] = "alert tcp any any -> any any (msg:\"pcre with"
|
|
" isdataat + relative\"; pcre:\"/A(ll|pp)WorkAndNoPlayMakesWillA"
|
|
"DullBoy/\"; isdataat:96,relative; sid:1;)";
|
|
|
|
result = UTHPacketMatchSig(p, sig);
|
|
|
|
UTHFreePacket(p);
|
|
end:
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* \test DetectIsdataatTestPacket03 is a test to check matches of
|
|
* isdataat, and isdataat relative works if the previous keyword is byte_jump
|
|
* (bug 146)
|
|
*/
|
|
static int DetectIsdataatTestPacket03 (void)
|
|
{
|
|
int result = 0;
|
|
uint8_t *buf = (uint8_t *)"GET /AllWorkAndNoPlayMakesWillADullBoy HTTP/1.0"
|
|
"User-Agent: Wget/1.11.4"
|
|
"Accept: */*"
|
|
"Host: www.google.com"
|
|
"Connection: Keep-Alive"
|
|
"Date: Mon, 04 Jan 2010 17:29:39 GMT";
|
|
uint16_t buflen = strlen((char *)buf);
|
|
Packet *p;
|
|
p = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_TCP);
|
|
|
|
if (p == NULL)
|
|
goto end;
|
|
|
|
char sig[] = "alert tcp any any -> any any (msg:\"byte_jump match = 0 "
|
|
"with distance content HTTP/1. relative against HTTP/1.0\"; byte_jump:1,"
|
|
"46,string,dec; isdataat:87,relative; sid:109; rev:1;)";
|
|
|
|
result = UTHPacketMatchSig(p, sig);
|
|
|
|
UTHFreePacket(p);
|
|
end:
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* \brief this function registers unit tests for DetectIsdataat
|
|
*/
|
|
void DetectIsdataatRegisterTests(void)
|
|
{
|
|
g_dce_stub_data_buffer_id = DetectBufferTypeGetByName("dce_stub_data");
|
|
|
|
UtRegisterTest("DetectIsdataatTestParse01", DetectIsdataatTestParse01);
|
|
UtRegisterTest("DetectIsdataatTestParse02", DetectIsdataatTestParse02);
|
|
UtRegisterTest("DetectIsdataatTestParse03", DetectIsdataatTestParse03);
|
|
UtRegisterTest("DetectIsdataatTestParse04", DetectIsdataatTestParse04);
|
|
UtRegisterTest("DetectIsdataatTestParse06", DetectIsdataatTestParse06);
|
|
|
|
UtRegisterTest("DetectIsdataatTestPacket01", DetectIsdataatTestPacket01);
|
|
UtRegisterTest("DetectIsdataatTestPacket02", DetectIsdataatTestPacket02);
|
|
UtRegisterTest("DetectIsdataatTestPacket03", DetectIsdataatTestPacket03);
|
|
}
|
|
|
|
static int DetectAbsentTestParse01(void)
|
|
{
|
|
DetectEngineCtx *de_ctx = DetectEngineCtxInit();
|
|
FAIL_IF(de_ctx == NULL);
|
|
de_ctx->flags |= DE_QUIET;
|
|
|
|
Signature *s = DetectEngineAppendSig(de_ctx,
|
|
"alert http any any -> any any "
|
|
"(msg:\"invalid absent only with negated content\"; http.user_agent; "
|
|
"absent; content:!\"one\"; sid:2;)");
|
|
FAIL_IF(s != NULL);
|
|
s = DetectEngineAppendSig(de_ctx, "alert http any any -> any any "
|
|
"(msg:\"invalid absent\"; http.user_agent; "
|
|
"content:!\"one\"; absent; sid:2;)");
|
|
FAIL_IF(s != NULL);
|
|
s = DetectEngineAppendSig(de_ctx, "alert http any any -> any any "
|
|
"(msg:\"invalid absent\"; http.user_agent; "
|
|
"content:\"one\"; absent: or_else; sid:2;)");
|
|
FAIL_IF(s != NULL);
|
|
s = DetectEngineAppendSig(de_ctx, "alert http any any -> any any "
|
|
"(msg:\"absent without sticky buffer\"; "
|
|
"content:!\"one\"; absent: or_else; sid:2;)");
|
|
FAIL_IF(s != NULL);
|
|
s = DetectEngineAppendSig(de_ctx,
|
|
"alert websocket any any -> any any "
|
|
"(msg:\"absent with frame\"; "
|
|
"frame: websocket.pdu; absent: or_else; content:!\"one\"; sid:2;)");
|
|
FAIL_IF(s != NULL);
|
|
DetectEngineCtxFree(de_ctx);
|
|
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 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_NULL(s);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/** \test fast_pattern is not taken from a buffer with absent when a repeated
|
|
* buffer shares its list id */
|
|
static int DetectAbsentTestParse05(void)
|
|
{
|
|
const char *sigs[] = {
|
|
"alert http any any -> any any (http.request_header; content:\"Host\"; "
|
|
"http.request_header; absent: or_else; content:\"a-much-longer-pattern\"; sid:1;)",
|
|
"alert http any any -> any any (http.request_header; absent: or_else; "
|
|
"content:\"a-much-longer-pattern\"; http.request_header; content:\"Host\"; sid:2;)",
|
|
"alert http any any -> any any (http.request_header; to_lowercase; content:\"host\"; "
|
|
"http.request_header; to_lowercase; absent: or_else; "
|
|
"content:\"a-much-longer-pattern\"; sid:3;)",
|
|
};
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(sigs); i++) {
|
|
DetectEngineCtx *de_ctx = DetectEngineCtxInit();
|
|
FAIL_IF_NULL(de_ctx);
|
|
de_ctx->flags |= DE_QUIET;
|
|
|
|
Signature *s = DetectEngineAppendSig(de_ctx, sigs[i]);
|
|
FAIL_IF_NULL(s);
|
|
FAIL_IF_NOT(s->init_data->buffer_index == 2);
|
|
FAIL_IF_NOT(s->init_data->buffers[0].id == s->init_data->buffers[1].id);
|
|
|
|
int mpm_cnt = 0;
|
|
for (uint32_t x = 0; x < s->init_data->buffer_index; x++) {
|
|
for (SigMatch *sm = s->init_data->buffers[x].head; sm != NULL; sm = sm->next) {
|
|
if (sm->type != DETECT_CONTENT)
|
|
continue;
|
|
const DetectContentData *cd = (const DetectContentData *)sm->ctx;
|
|
if (cd->flags & DETECT_CONTENT_MPM) {
|
|
FAIL_IF_NOT(cd->content_len == 4);
|
|
mpm_cnt++;
|
|
}
|
|
}
|
|
}
|
|
FAIL_IF_NOT(mpm_cnt == 1);
|
|
|
|
DetectEngineCtxFree(de_ctx);
|
|
}
|
|
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);
|
|
UtRegisterTest("DetectAbsentTestParse02", DetectAbsentTestParse02);
|
|
UtRegisterTest("DetectAbsentTestParse03", DetectAbsentTestParse03);
|
|
UtRegisterTest("DetectAbsentTestParse04", DetectAbsentTestParse04);
|
|
UtRegisterTest("DetectAbsentTestParse05", DetectAbsentTestParse05);
|
|
UtRegisterTest("DetectAbsentTestParse06", DetectAbsentTestParse06);
|
|
}
|
|
#endif
|