diff --git a/src/Makefile.am b/src/Makefile.am index 010fe01ddb..15f4488a1c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -85,6 +85,7 @@ detect-app-layer-protocol.c detect-app-layer-protocol.h \ detect-asn1.c detect-asn1.h \ detect-base64-data.c detect-base64-data.h \ detect-base64-decode.c detect-base64-decode.h \ +detect-bsize.c detect-bsize.h \ detect-byte-extract.c detect-byte-extract.h \ detect-bytejump.c detect-bytejump.h \ detect-bytetest.c detect-bytetest.h \ diff --git a/src/detect-bsize.c b/src/detect-bsize.c new file mode 100644 index 0000000000..fad18b1cb9 --- /dev/null +++ b/src/detect-bsize.c @@ -0,0 +1,322 @@ +/* Copyright (C) 2017 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 Victor Julien + * + * Implements the bsize generic buffer length keyword + */ + +#include "suricata-common.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" + +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-content.h" + +#include "detect-bsize.h" + +#include "util-misc.h" + +/*prototypes*/ +static int DetectBsizeSetup (DetectEngineCtx *, Signature *, const char *); +static void DetectBsizeFree (void *); +#ifdef UNITTESTS +static void DetectBsizeRegisterTests (void); +#endif + +/** + * \brief Registration function for bsize: keyword + */ + +void DetectBsizeRegister(void) +{ + sigmatch_table[DETECT_BSIZE].name = "bsize"; + sigmatch_table[DETECT_BSIZE].desc = "match on the length of a buffer"; + sigmatch_table[DETECT_BSIZE].url = DOC_URL DOC_VERSION "/rules/payload-keywords.html#bsize"; + sigmatch_table[DETECT_BSIZE].Match = NULL; + sigmatch_table[DETECT_BSIZE].Setup = DetectBsizeSetup; + sigmatch_table[DETECT_BSIZE].Free = DetectBsizeFree; +#ifdef UNITTESTS + sigmatch_table[DETECT_BSIZE].RegisterTests = DetectBsizeRegisterTests; +#endif +} + +#define DETECT_BSIZE_LT 0 +#define DETECT_BSIZE_GT 1 +#define DETECT_BSIZE_RA 2 +#define DETECT_BSIZE_EQ 3 + +typedef struct DetectBsizeData { + uint8_t mode; + uint64_t lo; + uint64_t hi; +} DetectBsizeData; + +/** \brief bsize match function + * + * \param ctx match ctx + * \param buffer_size size of the buffer + * \param eof is the buffer closed? + * + * \retval r 1 match, 0 no match, -1 can't match + * + * \todo check logic around < vs <= + */ +int DetectBsizeMatch(const SigMatchCtx *ctx, const uint64_t buffer_size, bool eof) +{ + const DetectBsizeData *bsz = (const DetectBsizeData *)ctx; + switch (bsz->mode) { + case DETECT_BSIZE_LT: + if (buffer_size < bsz->lo) { + return 1; + } + return -1; + + case DETECT_BSIZE_GT: + if (buffer_size > bsz->lo) { + return 1; + } else if (eof) { + return -1; + } + return 0; + + case DETECT_BSIZE_EQ: + if (buffer_size == bsz->lo) { + return 1; + } else if (buffer_size > bsz->lo) { + return -1; + } else if (eof) { + return -1; + } else { + return 0; + } + + case DETECT_BSIZE_RA: + if (buffer_size > bsz->lo && buffer_size < bsz->hi) { + return 1; + } else if (buffer_size <= bsz->lo && eof) { + return -1; + } else if (buffer_size <= bsz->lo) { + return 0; + } else if (buffer_size >= bsz->hi) { + return -1; + } + } + return 0; +} + +#define ERR(...) do { \ + char _buf[2048]; \ + snprintf(_buf, sizeof(_buf), __VA_ARGS__); \ + SCLogError(SC_ERR_INVALID_RULE_ARGUMENT, "bsize: bad input, %s", _buf); \ +} while(0) + +/** + * \brief This function is used to parse bsize options passed via bsize: keyword + * + * \param bsizestr Pointer to the user provided bsize options + * + * \retval bsized pointer to DetectBsizeData on success + * \retval NULL on failure + */ + +static DetectBsizeData *DetectBsizeParse (const char *str) +{ + uint32_t lo = 0; + uint32_t hi = 0; + + if (str == NULL) + return NULL; + + size_t len = strlen(str); + if (len == 0) + return NULL; + + /* allow for leading spaces */ + while (isspace(*str)) + (str++); + len = strlen(str); + if (len == 0) + return NULL; + + int mode = DETECT_BSIZE_EQ; + switch (*str) { + case '>': + mode = DETECT_BSIZE_GT; + str++; + break; + case '<': + mode = DETECT_BSIZE_LT; + str++; + break; + } + + /* allow for spaces between mode and value */ + while (isspace(*str)) + (str++); + + char str1[11], *p = str1; + memset(str1, 0, sizeof(str1)); + while (*str && isdigit(*str)) { + if (p - str1 >= (int)sizeof(str1)) + return NULL; + *p++ = *str++; + } + /* skip trailing space */ + while (*str && isspace(*str)) { + str++; + } + if (*str == '\0') { + // done + SCLogDebug("str1 '%s'", str1); + + uint64_t val = 0; + if (ParseSizeStringU64(str1, &val) < 0) { + return NULL; + } + lo = val; + + } else if (*str == '<') { + str++; + if (*str != '>') { + ERR("only '<>' allowed"); + return NULL; + } + str++; + + // range + if (mode != DETECT_BSIZE_EQ) { + ERR("mode already set"); + return NULL; + } + mode = DETECT_BSIZE_RA; + + uint64_t val = 0; + if (ParseSizeStringU64(str1, &val) < 0) { + return NULL; + } + lo = val; + + /* allow for spaces between mode and value */ + while (*str && isspace(*str)) + (str++); + + char str2[11]; + p = str2; + memset(str2, 0, sizeof(str2)); + while (*str && isdigit(*str)) { + if (p - str2 >= (int)sizeof(str2)) + return NULL; + *p++ = *str++; + } + /* skip trailing space */ + while (*str && isspace(*str)) { + str++; + } + if (*str == '\0') { + // done + SCLogDebug("str2 '%s'", str2); + + if (ParseSizeStringU64(str2, &val) < 0) { + ERR("'%s' is not a valid u32", str2); + return NULL; + } + hi = val; + if (lo >= hi) { + ERR("%u > %u", lo, hi); + return NULL; + } + + } else { + ERR("trailing data"); + return NULL; + } + + } else { + ERR("'%s'", str); + return NULL; + } + + DetectBsizeData *bsz = SCCalloc(1, sizeof(*bsz)); + if (bsz == NULL) { + return NULL; + } + bsz->mode = (uint8_t)mode; + bsz->lo = lo; + bsz->hi = hi; + return bsz; +} + +/** + * \brief this function is used to parse bsize data into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param bsizestr pointer to the user provided bsize options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectBsizeSetup (DetectEngineCtx *de_ctx, Signature *s, const char *sizestr) +{ + SCEnter(); + SigMatch *sm = NULL; + + int list = s->init_data->list; + if (list == DETECT_SM_LIST_NOTSET) + SCReturnInt(-1); + + DetectBsizeData *bsz = DetectBsizeParse(sizestr); + if (bsz == NULL) + goto error; + sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + sm->type = DETECT_BSIZE; + sm->ctx = (void *)bsz; + + SigMatchAppendSMToList(s, sm, list); + + SCReturnInt(0); + +error: + DetectBsizeFree(bsz); + SCReturnInt(-1); +} + +/** + * \brief this function will free memory associated with DetectBsizeData + * + * \param ptr pointer to DetectBsizeData + */ +void DetectBsizeFree(void *ptr) +{ + if (ptr == NULL) + return; + + DetectBsizeData *bsz = (DetectBsizeData *)ptr; + SCFree(bsz); +} + +#ifdef UNITTESTS +#include "tests/detect-bsize.c" +#endif diff --git a/src/detect-bsize.h b/src/detect-bsize.h new file mode 100644 index 0000000000..6851abcb04 --- /dev/null +++ b/src/detect-bsize.h @@ -0,0 +1,32 @@ +/* Copyright (C) 2017 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 Victor Julien + */ + +#ifndef __DETECT_BSIZE_H__ +#define __DETECT_BSIZE_H__ + +//bool DetectBsizeValidateContent(const Signature *s, int list, const char **); +//void DetectBsizeApplyToContent(Signature *s, int list); +void DetectBsizeRegister(void); +int DetectBsizeMatch(const SigMatchCtx *ctx, const uint64_t buffer_size, bool eof); + +#endif /* __DETECT_URILEN_H__ */ diff --git a/src/detect-engine-content-inspection.c b/src/detect-engine-content-inspection.c index 3e4471c9d7..baafb9072a 100644 --- a/src/detect-engine-content-inspection.c +++ b/src/detect-engine-content-inspection.c @@ -41,6 +41,7 @@ #include "detect-engine-content-inspection.h" #include "detect-uricontent.h" #include "detect-urilen.h" +#include "detect-bsize.h" #include "detect-lua.h" #include "detect-base64-decode.h" #include "detect-base64-data.h" @@ -530,7 +531,20 @@ int DetectEngineContentInspection(DetectEngineCtx *de_ctx, DetectEngineThreadCtx goto match; - /* we should never get here, but bail out just in case */ + } else if (smd->type == DETECT_BSIZE) { + + bool eof = (flags & DETECT_CI_FLAGS_END); + const uint64_t data_size = buffer_len + stream_start_offset; + int r = DetectBsizeMatch(smd->ctx, data_size, eof); + if (r < 0) { + det_ctx->discontinue_matching = 1; + goto no_match; + + } else if (r == 0) { + goto no_match; + } + goto match; + } else if (smd->type == DETECT_AL_URILEN) { SCLogDebug("inspecting uri len"); diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 54f421709a..81c37405ae 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -137,6 +137,7 @@ #include "detect-dce-opnum.h" #include "detect-dce-stub-data.h" #include "detect-urilen.h" +#include "detect-bsize.h" #include "detect-detection-filter.h" #include "detect-http-client-body.h" #include "detect-http-server-body.h" @@ -462,6 +463,7 @@ void SigTableSetup(void) DetectNfsProcedureRegister(); DetectNfsVersionRegister(); DetectUrilenRegister(); + DetectBsizeRegister(); DetectDetectionFilterRegister(); DetectAsn1Register(); DetectSshProtocolRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index 17d03102fd..a5761a501b 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -99,6 +99,8 @@ enum { DETECT_GID, DETECT_MARK, + DETECT_BSIZE, + DETECT_AL_TLS_VERSION, DETECT_AL_TLS_SUBJECT, DETECT_AL_TLS_ISSUERDN, diff --git a/src/tests/detect-bsize.c b/src/tests/detect-bsize.c new file mode 100644 index 0000000000..221ea52b4c --- /dev/null +++ b/src/tests/detect-bsize.c @@ -0,0 +1,134 @@ +/* Copyright (C) 2017 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. + */ + +#include "../util-unittest.h" + +#define TEST_OK(str, m, lo, hi) { \ + DetectBsizeData *bsz = DetectBsizeParse((str)); \ + FAIL_IF_NULL(bsz); \ + FAIL_IF_NOT(bsz->mode == (m)); \ + DetectBsizeFree(bsz); \ + SCLogDebug("str %s OK", (str)); \ +} +#define TEST_FAIL(str) { \ + DetectBsizeData *bsz = DetectBsizeParse((str)); \ + FAIL_IF_NOT_NULL(bsz); \ +} + +static int DetectBsizeTest01(void) +{ + TEST_OK("50", DETECT_BSIZE_EQ, 50, 0); + TEST_OK(" 50", DETECT_BSIZE_EQ, 50, 0); + TEST_OK(" 50", DETECT_BSIZE_EQ, 50, 0); + TEST_OK(" 50 ", DETECT_BSIZE_EQ, 50, 0); + TEST_OK(" 50 ", DETECT_BSIZE_EQ, 50, 0); + + TEST_FAIL("AA"); + TEST_FAIL("5A"); + TEST_FAIL("A5"); + PASS; +} + +static int DetectBsizeTest02(void) +{ + TEST_OK(">50", DETECT_BSIZE_GT, 50, 0); + TEST_OK("> 50", DETECT_BSIZE_GT, 50, 0); + TEST_OK("> 50", DETECT_BSIZE_GT, 50, 0); + TEST_OK(" >50", DETECT_BSIZE_GT, 50, 0); + TEST_OK(" > 50", DETECT_BSIZE_GT, 50, 0); + TEST_OK(" > 50", DETECT_BSIZE_GT, 50, 0); + TEST_OK(" >50 ", DETECT_BSIZE_GT, 50, 0); + TEST_OK(" > 50 ", DETECT_BSIZE_GT, 50, 0); + TEST_OK(" > 50 ", DETECT_BSIZE_GT, 50, 0); + + TEST_FAIL(">>50"); + TEST_FAIL("<>50"); + TEST_FAIL(" > 50A"); + PASS; +} + +static int DetectBsizeTest03(void) +{ + TEST_OK("<50", DETECT_BSIZE_LT, 50, 0); + TEST_OK("< 50", DETECT_BSIZE_LT, 50, 0); + TEST_OK("< 50", DETECT_BSIZE_LT, 50, 0); + TEST_OK(" <50", DETECT_BSIZE_LT, 50, 0); + TEST_OK(" < 50", DETECT_BSIZE_LT, 50, 0); + TEST_OK(" < 50", DETECT_BSIZE_LT, 50, 0); + TEST_OK(" <50 ", DETECT_BSIZE_LT, 50, 0); + TEST_OK(" < 50 ", DETECT_BSIZE_LT, 50, 0); + TEST_OK(" < 50 ", DETECT_BSIZE_LT, 50, 0); + + TEST_FAIL(">>50"); + TEST_FAIL(" < 50A"); + PASS; +} + +static int DetectBsizeTest04(void) +{ + TEST_OK("50<>100", DETECT_BSIZE_RA, 50, 100); + + TEST_FAIL("50<$50"); + TEST_FAIL("100<>50"); + TEST_FAIL(">50<>100"); + PASS; +} + +#undef TEST_OK +#undef TEST_FAIL + +#define TEST_OK(rule) \ +{ \ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); \ + FAIL_IF_NULL(de_ctx); \ + Signature *s = DetectEngineAppendSig(de_ctx, (rule)); \ + FAIL_IF_NULL(s); \ + DetectEngineCtxFree(de_ctx); \ +} + +#define TEST_FAIL(rule) \ +{ \ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); \ + FAIL_IF_NULL(de_ctx); \ + Signature *s = DetectEngineAppendSig(de_ctx, (rule)); \ + FAIL_IF_NOT_NULL(s); \ + DetectEngineCtxFree(de_ctx); \ +} + +static int DetectBsizeSigTest01(void) +{ + TEST_OK("alert http any any -> any any (http_request_line; bsize:10; sid:1;)"); + TEST_OK("alert http any any -> any any (file_data; bsize:>1000; sid:2;)"); + + TEST_FAIL("alert tcp any any -> any any (content:\"abc\"; bsize:10; sid:3;)"); + TEST_FAIL("alert http any any -> any any (content:\"GET\"; http_method; bsize:10; sid:4;)"); + TEST_FAIL("alert http any any -> any any (http_request_line; content:\"GET\"; bsize:<10>; sid:5;)"); + PASS; +} + +#undef TEST_OK +#undef TEST_FAIL + +static void DetectBsizeRegisterTests(void) +{ + UtRegisterTest("DetectBsizeTest01 EQ", DetectBsizeTest01); + UtRegisterTest("DetectBsizeTest02 GT", DetectBsizeTest02); + UtRegisterTest("DetectBsizeTest03 LT", DetectBsizeTest03); + UtRegisterTest("DetectBsizeTest04 RA", DetectBsizeTest04); + + UtRegisterTest("DetectBsizeSigTest01", DetectBsizeSigTest01); +}