From 69eb869cc9027282e8faa7eca2ce55d4c557b0ff Mon Sep 17 00:00:00 2001 From: Breno Silva Date: Tue, 22 Dec 2009 22:40:04 -0200 Subject: [PATCH] Threshold Rule --- src/Makefile.am | 1 + src/detect-engine-iponly.c | 6 +- src/detect-engine-threshold.c | 428 +++++++++++++++++++++++ src/detect-engine-threshold.h | 25 ++ src/detect-engine.c | 5 +- src/detect-threshold.c | 628 +++++++++++++++++++++++++++++++++- src/detect-threshold.h | 50 ++- src/detect.c | 12 +- src/detect.h | 12 + 9 files changed, 1142 insertions(+), 25 deletions(-) create mode 100644 src/detect-engine-threshold.c create mode 100644 src/detect-engine-threshold.h diff --git a/src/Makefile.am b/src/Makefile.am index cac4e8bf9c..ffb95fe452 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -34,6 +34,7 @@ detect-rpc.c detect-rpc.h \ detect-isdataat.c detect-isdataat.h \ detect-window.c detect-window.h \ detect-engine-address.c detect-engine-address.h \ +detect-engine-threshold.c detect-engine-threshold.h \ detect-engine-address-ipv4.c detect-engine-address-ipv4.h \ detect-engine-address-ipv6.c detect-engine-address-ipv6.h \ detect-engine-proto.c detect-engine-proto.h \ diff --git a/src/detect-engine-iponly.c b/src/detect-engine-iponly.c index 0a00d5fc0a..77acbc9adf 100644 --- a/src/detect-engine-iponly.c +++ b/src/detect-engine-iponly.c @@ -23,6 +23,10 @@ #include "detect-engine-port.h" #include "detect-engine-mpm.h" +#include "detect-engine-threshold.h" + +#include "detect-threshold.h" + #include "util-debug.h" #include "util-unittest.h" @@ -375,8 +379,8 @@ void IPOnlyMatchPacket(DetectEngineCtx *de_ctx, DetectEngineIPOnlyCtx *io_ctx, if (!(s->flags & SIG_FLAG_NOALERT)) { - PacketAlertAppend(p, s->gid, s->id, s->rev, s->prio, s->msg); + PacketAlertHandle(de_ctx,s,p); /* set verdict on packet */ p->action = s->action; } diff --git a/src/detect-engine-threshold.c b/src/detect-engine-threshold.c new file mode 100644 index 0000000000..07d3ea6c9c --- /dev/null +++ b/src/detect-engine-threshold.c @@ -0,0 +1,428 @@ +/* Copyright (c) 2009 Open Information Security Foundation */ + +/** \file + * \author Breno Silva + */ + + +#include "suricata-common.h" +#include "debug.h" +#include "detect.h" +#include "flow.h" + +#include "detect-parse.h" +#include "detect-engine-sigorder.h" + +#include "detect-engine-siggroup.h" +#include "detect-engine-address.h" +#include "detect-engine-port.h" +#include "detect-engine-mpm.h" +#include "detect-engine-iponly.h" + +#include "detect-engine.h" +#include "detect-engine-threshold.h" + +#include "detect-content.h" +#include "detect-uricontent.h" + +#include "util-hash.h" +#include "util-time.h" +#include "util-error.h" +#include "util-debug.h" + +#include "util-var-name.h" +#include "tm-modules.h" + +/** + * \brief Handle a packet and check if needs a threshold logic + * + * \param de_ctx Detection Context + * \param sig Signature pointer + * \param p Packet structure + * + */ +void PacketAlertHandle(DetectEngineCtx *de_ctx, Signature *sig, Packet *p) +{ + DetectThresholdData *tsh = NULL; + + tsh = SigGetThresholdType(sig,p); + + if (tsh == NULL) { + PacketAlertAppend(p, sig->gid, sig->id, sig->rev, sig->prio, sig->msg); + } else { + PacketAlertThreshold(de_ctx,tsh,p,sig); + } + + return; +} +/** + * \brief Check if a certain signature has threshold option + * + * \param sig Signature pointer + * \param p Packet structure + * + * \retval tsh Return the threshold options from signature or NULL if not found + */ +DetectThresholdData *SigGetThresholdType(Signature *sig, Packet *p) +{ + SigMatch *sm = sig->match; + DetectThresholdData *tsh = NULL; + + if(p == NULL) + return NULL; + + while (sm != NULL) { + if (sm->type == DETECT_THRESHOLD) { + tsh = (DetectThresholdData *)sm->ctx; + if (tsh != NULL) { + if (PKT_IS_IPV4(p)) + tsh->ipv = 4; + else if (PKT_IS_IPV6(p)) + tsh->ipv = 6; + tsh->sid = sig->id; + tsh->gid = sig->gid; + if (tsh->track == TRACK_DST ) + tsh->addr = p->dst; + else if (tsh->track == TRACK_SRC ) + tsh->addr = p->src; + } + return tsh; + } + + sm = sm->next; + } + + return NULL; +} + + +/** + * \brief Search for a threshold data into threshold hash table + * + * \param de_ctx Dectection Context + * \param tsh_ptr Threshold element + * \param p Packet structure + * + * \retval lookup_tsh Return the threshold element + */ +DetectThresholdData *ThresholdHashSearch(DetectEngineCtx *de_ctx, DetectThresholdData *tsh_ptr, Packet *p) +{ + DetectThresholdData *lookup_tsh = NULL; + + if (tsh_ptr->track == TRACK_DST) { + if (PKT_IS_IPV4(p)) + lookup_tsh = HashListTableLookup(de_ctx->ths_ctx.threshold_hash_table_dst, tsh_ptr, sizeof(DetectThresholdData)); + else if (PKT_IS_IPV6(p)) + lookup_tsh = HashListTableLookup(de_ctx->ths_ctx.threshold_hash_table_dst_ipv6, tsh_ptr, sizeof(DetectThresholdData)); + } else if (tsh_ptr->track == TRACK_SRC) { + if (PKT_IS_IPV4(p)) + lookup_tsh = HashListTableLookup(de_ctx->ths_ctx.threshold_hash_table_src, tsh_ptr, sizeof(DetectThresholdData)); + else if (PKT_IS_IPV6(p)) + lookup_tsh = HashListTableLookup(de_ctx->ths_ctx.threshold_hash_table_src_ipv6, tsh_ptr, sizeof(DetectThresholdData)); + } + + return lookup_tsh; +} + +/** + * \brief Remove timeout threshold hash elements + * + * \param de_ctx Dectection Context + * + */ + +/** \todo In some conditions HashListtableRemove returns at dt->array = NULL + * Must need to check it + **/ + +void ThresholdTimeoutRemove(DetectEngineCtx *de_ctx) +{ + struct timeval tv; + DetectThresholdData *tsh = NULL; + HashListTableBucket *next = NULL; + + memset(&tv, 0x00, sizeof(tv)); + TimeGet(&tv); + + SCMutexLock(&de_ctx->ths_ctx.threshold_table_lock); + next = HashListTableGetListHead(de_ctx->ths_ctx.threshold_hash_table_src); + + while (next != NULL) { + + tsh = HashListTableGetListData(next); + + if (tsh && ((tv.tv_sec - tsh->tv_sec1) > tsh->seconds)) { + HashListTableRemove(de_ctx->ths_ctx.threshold_hash_table_src, tsh, sizeof(DetectThresholdData)); + HashListTableRemove(de_ctx->ths_ctx.threshold_hash_table_dst, tsh, sizeof(DetectThresholdData)); + HashListTableRemove(de_ctx->ths_ctx.threshold_hash_table_src_ipv6, tsh, sizeof(DetectThresholdData)); + HashListTableRemove(de_ctx->ths_ctx.threshold_hash_table_dst_ipv6, tsh, sizeof(DetectThresholdData)); + } + + next = HashListTableGetListNext(next); + } + + SCMutexUnlock(&de_ctx->ths_ctx.threshold_table_lock); + + return; +} + +/** + * \brief Add threshold element into hash table + * + * \param de_ctx Dectection Context + * \param tsh_ptr Threshold element + * \param p Packet structure + * + */ +void ThresholdHashAdd(DetectEngineCtx *de_ctx, DetectThresholdData *tsh_ptr, Packet *p) +{ + int ret = 0; + + if (PKT_IS_IPV4(p)) { + if (tsh_ptr->track == TRACK_DST) + ret = HashListTableAdd(de_ctx->ths_ctx.threshold_hash_table_dst, tsh_ptr, sizeof(DetectThresholdData)); + else if (tsh_ptr->track == TRACK_SRC) + ret = HashListTableAdd(de_ctx->ths_ctx.threshold_hash_table_src, tsh_ptr, sizeof(DetectThresholdData)); + } else if (PKT_IS_IPV6(p)) { + if (tsh_ptr->track == TRACK_DST) + ret = HashListTableAdd(de_ctx->ths_ctx.threshold_hash_table_dst_ipv6, tsh_ptr, sizeof(DetectThresholdData)); + else if (tsh_ptr->track == TRACK_SRC) + ret = HashListTableAdd(de_ctx->ths_ctx.threshold_hash_table_src_ipv6, tsh_ptr, sizeof(DetectThresholdData)); + } + + if(ret == -1) { + SCLogError(SC_ERR_MEM_ALLOC, + "Threshold: Failed to Add element into the hash table."); + } + + return; +} + +/** + * \brief Make the threshold logic for signatures + * + * \param de_ctx Dectection Context + * \param tsh_ptr Threshold element + * \param p Packet structure + * \param s Signature structure + * + */ +void PacketAlertThreshold(DetectEngineCtx *de_ctx, DetectThresholdData *tsh_ptr, Packet *p, Signature *s) +{ + struct timeval ts; + DetectThresholdData *lookup_tsh = NULL; + + if (tsh_ptr == NULL) + return; + + memset(&ts, 0x00, sizeof(ts)); + TimeGet(&ts); + + SCMutexLock(&de_ctx->ths_ctx.threshold_table_lock); + switch(tsh_ptr->type) { + case TYPE_LIMIT: + + lookup_tsh = ThresholdHashSearch(de_ctx,tsh_ptr,p); + + if (lookup_tsh != NULL) { + if ((ts.tv_sec - lookup_tsh->tv_sec1) < lookup_tsh->seconds) { + + if (lookup_tsh->current_count < lookup_tsh->count) { + PacketAlertAppend(p, s->gid, s->id, s->rev, s->prio, s->msg); + } + + lookup_tsh->current_count++; + } else { + lookup_tsh->tv_sec1 = ts.tv_sec; + lookup_tsh->current_count = 1; + PacketAlertAppend(p, s->gid, s->id, s->rev, s->prio, s->msg); + } + } else { + tsh_ptr->tv_sec1 = ts.tv_sec; + tsh_ptr->current_count = 1; + PacketAlertAppend(p, s->gid, s->id, s->rev, s->prio, s->msg); + + if (tsh_ptr->count == 1) { + tsh_ptr->current_count = 0; + } else { + ThresholdHashAdd(de_ctx,tsh_ptr,p); + } + } + break; + + case TYPE_THRESHOLD: + + lookup_tsh = ThresholdHashSearch(de_ctx,tsh_ptr,p); + + if (lookup_tsh != NULL) { + if ((ts.tv_sec - lookup_tsh->tv_sec1) < lookup_tsh->seconds) { + + lookup_tsh->current_count++; + + if (lookup_tsh->current_count >= lookup_tsh->count) { + PacketAlertAppend(p, s->gid, s->id, s->rev, s->prio, s->msg); + lookup_tsh->current_count = 0; + } + } else { + lookup_tsh->tv_sec1 = ts.tv_sec; + lookup_tsh->current_count = 1; + } + } else { + tsh_ptr->current_count = 1; + tsh_ptr->tv_sec1 = ts.tv_sec; + + if (tsh_ptr->count == 1) { + PacketAlertAppend(p, s->gid, s->id, s->rev, s->prio, s->msg); + tsh_ptr->current_count = 0; + } else { + ThresholdHashAdd(de_ctx,tsh_ptr,p); + } + } + break; + + case TYPE_BOTH: + + lookup_tsh = ThresholdHashSearch(de_ctx,tsh_ptr,p); + + if (lookup_tsh != NULL) { + + if ((ts.tv_sec - lookup_tsh->tv_sec1) < lookup_tsh->seconds) { + + lookup_tsh->current_count++; + if (lookup_tsh->current_count == lookup_tsh->count) { + PacketAlertAppend(p, s->gid, s->id, s->rev, s->prio, s->msg); + } + } else { + lookup_tsh->tv_sec1 = ts.tv_sec; + lookup_tsh->current_count = 1; + } + } else { + tsh_ptr->current_count = 1; + tsh_ptr->tv_sec1 = ts.tv_sec; + + if (tsh_ptr->count == 1) { + PacketAlertAppend(p, s->gid, s->id, s->rev, s->prio, s->msg); + tsh_ptr->current_count = 0; + } else { + ThresholdHashAdd(de_ctx,tsh_ptr,p); + } + + } + break; + } + SCMutexUnlock(&de_ctx->ths_ctx.threshold_table_lock); + + ThresholdTimeoutRemove(de_ctx); +} + +void ThresholdFreeFunc(void *data) +{ + return; +} + +/** + * \brief Compare elements into the hash table + * + * \param data1 First element to compare + * \param len1 length of first element + * \param data2 Second element to compare + * \param len2 length of second element + * + * \retval 1 Match or 0 No Match + */ +char ThresholdCompareFunc(void *data1, uint16_t len1, void *data2,uint16_t len2) +{ + DetectThresholdData *a = (DetectThresholdData *)data1; + DetectThresholdData *b = (DetectThresholdData *)data2; + + if ((a->sid == b->sid) && (a->gid == b->gid) && (CMP_ADDR(&a->addr,&b->addr))) + return 1; + + return 0; +} + +/** + * \brief Create the hash for threshold tables + * + * \param ht Hash Table + * \param data Data that will be used to create the hash + * \param datalen Data length + * + * \retval hash the hash + */ +uint32_t ThresholdHashFunc(HashListTable *ht, void *data, uint16_t datalen) +{ + DetectThresholdData *dt = (DetectThresholdData *)data; + uint32_t hash = 0; + + if (dt->ipv == 4) + hash = (dt->sid + dt->gid + dt->addr.addr_data32[0]) % THRESHOLD_HASH_SIZE; + else if (dt->ipv == 6) + hash = (dt->sid + dt->gid + dt->addr.addr_data32[0] + dt->addr.addr_data32[1] + dt->addr.addr_data32[2] + dt->addr.addr_data32[3]) % THRESHOLD_HASH_SIZE; + + return hash; +} + +/** + * \brief Init threshold context hash tables + * + * \param de_ctx Dectection Context + * + */ +void ThresholdHashInit(DetectEngineCtx *de_ctx) +{ + if (de_ctx->ths_ctx.threshold_hash_table_dst == NULL || de_ctx->ths_ctx.threshold_hash_table_src == NULL || de_ctx->ths_ctx.threshold_hash_table_src_ipv6 == NULL || de_ctx->ths_ctx.threshold_hash_table_dst_ipv6 == NULL) { + de_ctx->ths_ctx.threshold_hash_table_dst = HashListTableInit(THRESHOLD_HASH_SIZE, ThresholdHashFunc, ThresholdCompareFunc, ThresholdFreeFunc); + + if(de_ctx->ths_ctx.threshold_hash_table_dst == NULL) { + SCLogError(SC_ERR_MEM_ALLOC, + "Threshold: Failed to initialize ipv4 dst hash table."); + exit(EXIT_FAILURE); + } + + de_ctx->ths_ctx.threshold_hash_table_src = HashListTableInit(THRESHOLD_HASH_SIZE, ThresholdHashFunc, ThresholdCompareFunc, ThresholdFreeFunc); + + if(de_ctx->ths_ctx.threshold_hash_table_dst == NULL) { + SCLogError(SC_ERR_MEM_ALLOC, + "Threshold: Failed to initialize ipv4 src hash table."); + exit(EXIT_FAILURE); + } + + de_ctx->ths_ctx.threshold_hash_table_src_ipv6 = HashListTableInit(THRESHOLD_HASH_SIZE, ThresholdHashFunc, ThresholdCompareFunc, ThresholdFreeFunc); + + if(de_ctx->ths_ctx.threshold_hash_table_dst == NULL) { + SCLogError(SC_ERR_MEM_ALLOC, + "Threshold: Failed to initialize ipv6 src hash table."); + exit(EXIT_FAILURE); + } + + de_ctx->ths_ctx.threshold_hash_table_dst_ipv6 = HashListTableInit(THRESHOLD_HASH_SIZE, ThresholdHashFunc, ThresholdCompareFunc, ThresholdFreeFunc); + + if(de_ctx->ths_ctx.threshold_hash_table_dst == NULL) { + SCLogError(SC_ERR_MEM_ALLOC, + "Threshold: Failed to initialize ipv6 dst hash table."); + exit(EXIT_FAILURE); + } + + if (SCMutexInit(&de_ctx->ths_ctx.threshold_table_lock, NULL) != 0) { + SCLogError(SC_ERR_MEM_ALLOC, + "Threshold: Failed to initialize hash table mutex."); + exit(EXIT_FAILURE); + } + + } +} + +/** + * \brief Destroy threshold context hash tables + * + * \param de_ctx Dectection Context + * + */ +void ThresholdContextDestroy(DetectEngineCtx *de_ctx) +{ + HashListTableFree(de_ctx->ths_ctx.threshold_hash_table_dst); + HashListTableFree(de_ctx->ths_ctx.threshold_hash_table_src); + HashListTableFree(de_ctx->ths_ctx.threshold_hash_table_dst_ipv6); + HashListTableFree(de_ctx->ths_ctx.threshold_hash_table_src_ipv6); +} diff --git a/src/detect-engine-threshold.h b/src/detect-engine-threshold.h new file mode 100644 index 0000000000..6fcf013acc --- /dev/null +++ b/src/detect-engine-threshold.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2009 Open Information Security Foundation */ + +/** \file + * \author Breno Silva + */ + + +#ifndef __DETECT_ENGINE_THRESHOLD_H__ +#define __DETECT_ENGINE_THRESHOLD_H__ + +#include "detect.h" + +#define THRESHOLD_HASH_SIZE 0xffff + +void PacketAlertHandle(DetectEngineCtx *de_ctx, Signature *sig, Packet *p); +DetectThresholdData *SigGetThresholdType(Signature *, Packet *); +void PacketAlertThreshold(DetectEngineCtx *,DetectThresholdData *, Packet *, Signature *); +void ThresholdFreeFunc(void *data); +char ThresholdCompareFunc(void *data1, uint16_t len1, void *data2,uint16_t len2); +uint32_t ThresholdHashFunc(HashListTable *ht, void *data, uint16_t datalen); +void ThresholdHashInit(DetectEngineCtx *de_ctx); +void ThresholdTimeoutRemove(DetectEngineCtx *de_ctx); +void ThresholdContextDestroy(DetectEngineCtx *de_ctx); + +#endif /* __DETECT_ENGINE_THRESHOLD_H__ */ diff --git a/src/detect-engine.c b/src/detect-engine.c index 2e9ea67081..9391fffcdc 100644 --- a/src/detect-engine.c +++ b/src/detect-engine.c @@ -19,6 +19,7 @@ #include "detect-content.h" #include "detect-uricontent.h" +#include "detect-engine-threshold.h" //#include "util-mpm.h" #include "util-hash.h" @@ -50,7 +51,7 @@ DetectEngineCtx *DetectEngineCtxInit(void) { SigGroupHeadDPortHashInit(de_ctx); DetectPortSpHashInit(de_ctx); DetectPortDpHashInit(de_ctx); - + ThresholdHashInit(de_ctx); VariableNameInitHash(de_ctx); return de_ctx; error: @@ -73,7 +74,7 @@ void DetectEngineCtxFree(DetectEngineCtx *de_ctx) { SCSigSignatureOrderingModuleCleanup(de_ctx); DetectPortSpHashFree(de_ctx); DetectPortDpHashFree(de_ctx); - + ThresholdContextDestroy(de_ctx); SigCleanSignatures(de_ctx); VariableNameFreeHash(de_ctx); diff --git a/src/detect-threshold.c b/src/detect-threshold.c index 9a14bd0310..5a81a03c1b 100644 --- a/src/detect-threshold.c +++ b/src/detect-threshold.c @@ -1,33 +1,631 @@ -/* THRESHOLD part of the detection engine. */ +/* Copyright (c) 2009 Open Information Security Foundation */ + +/** \file + * \author Breno Silva + */ #include "suricata-common.h" +#include "suricata.h" +#include "decode.h" #include "detect.h" +#include "flow-var.h" +#include "decode-events.h" +#include "stream-tcp.h" + +#include "detect-threshold.h" +#include "util-unittest.h" +#include "util-byte.h" + +#define PARSE_REGEX "^\\s*type\\s+(limit|both|threshold)\\s*,\\s*track\\s+(by_src|by_dst)\\s*,\\s*count\\s+(\\d+)\\s*,\\s*seconds\\s+(\\d+)\\s*" + +static pcre *parse_regex; +static pcre_extra *parse_regex_study; + +static int DetectThresholdMatch (ThreadVars *, DetectEngineThreadCtx *, Packet *, Signature *, SigMatch *); +static int DetectThresholdSetup (DetectEngineCtx *, Signature *s, SigMatch *m, char *str); +static void DetectThresholdFree(void *); -int DetectThresholdSetup (DetectEngineCtx *, Signature *s, SigMatch *m, char *str); +/** + * \brief Registration function for threshold: keyword + */ void DetectThresholdRegister (void) { sigmatch_table[DETECT_THRESHOLD].name = "threshold"; - sigmatch_table[DETECT_THRESHOLD].Match = NULL; + sigmatch_table[DETECT_THRESHOLD].Match = DetectThresholdMatch; sigmatch_table[DETECT_THRESHOLD].Setup = DetectThresholdSetup; - sigmatch_table[DETECT_THRESHOLD].Free = NULL; - sigmatch_table[DETECT_THRESHOLD].RegisterTests = NULL; + sigmatch_table[DETECT_THRESHOLD].Free = DetectThresholdFree; + sigmatch_table[DETECT_THRESHOLD].RegisterTests = ThresholdRegisterTests; + + const char *eb; + int opts = 0; + int eo; + + parse_regex = pcre_compile(PARSE_REGEX, opts, &eb, &eo, NULL); + if (parse_regex == NULL) + { + printf("pcre compile of \"%s\" failed at offset %" PRId32 ": %s\n", PARSE_REGEX, eo, eb); + goto error; + } + + parse_regex_study = pcre_study(parse_regex, 0, &eb); + if (eb != NULL) + { + printf("pcre study failed: %s\n", eb); + goto error; + } + +error: + return; + +} + +static int DetectThresholdMatch (ThreadVars *thv, DetectEngineThreadCtx *det_ctx, Packet *p, Signature *s, SigMatch *sm) +{ + return 1; } -int DetectThresholdSetup (DetectEngineCtx *de_ctx, Signature *s, SigMatch *m, char *rawstr) +/** + * \internal + * \brief This function is used to parse threshold options passed via threshold: keyword + * + * \param rawstr Pointer to the user provided threshold options + * + * \retval de pointer to DetectThresholdData on success + * \retval NULL on failure + */ +static DetectThresholdData *DetectThresholdParse (char *rawstr) { - char *str = rawstr; - char dubbed = 0; + DetectThresholdData *de = NULL; +#define MAX_SUBSTRINGS 30 + int ret = 0, res = 0; + int ov[MAX_SUBSTRINGS]; + const char *str_ptr = NULL; + char *args[4] = { NULL, NULL, NULL, NULL }; + int i; - /* strip "'s */ - if (rawstr[0] == '\"' && rawstr[strlen(rawstr)-1] == '\"') { - str = strdup(rawstr+1); - str[strlen(rawstr)-2] = '\0'; - dubbed = 1; + ret = pcre_exec(parse_regex, parse_regex_study, rawstr, strlen(rawstr), 0, 0, ov, MAX_SUBSTRINGS); + + if (ret < 5) + goto error; + + de = malloc(sizeof(DetectThresholdData)); + if (de == NULL) { + printf("DetectThresholdSetup malloc failed\n"); + goto error; } - /* XXX */ + memset(de,0,sizeof(DetectThresholdData)); + + for (i = 0; i < (ret - 1); i++) { + + res = pcre_get_substring((char *)rawstr, ov, MAX_SUBSTRINGS,i + 1, &str_ptr); + + if (res < 0) + goto error; + + args[i] = (char *)str_ptr; + + if (strncasecmp(args[i],"limit",strlen("limit")) == 0) + de->type = TYPE_LIMIT; + if (strncasecmp(args[i],"both",strlen("both")) == 0) + de->type = TYPE_BOTH; + if (strncasecmp(args[i],"threshold",strlen("threshold")) == 0) + de->type = TYPE_THRESHOLD; + if (strncasecmp(args[i],"by_dst",strlen("by_dst")) == 0) + de->track = TRACK_DST; + if (strncasecmp(args[i],"by_src",strlen("by_src")) == 0) + de->track = TRACK_SRC; + } + + if (ByteExtractStringUint32(&de->count, 10, strlen(args[2]), args[2]) <= 0) { + goto error; + } + + if (ByteExtractStringUint32(&de->seconds, 10, strlen(args[3]), args[3]) <= 0) { + goto error; + } + + for (i = 0; i < (ret - 1); i++){ + if (args[i] != NULL) free(args[i]); + } + return de; + +error: + for (i = 0; i < (ret - 1); i++){ + if (args[i] != NULL) free(args[i]); + } + if (de) free(de); + return NULL; +} + +/** + * \internal + * \brief this function is used to add the parsed threshold into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param m pointer to the Current SigMatch + * \param rawstr pointer to the user provided threshold options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectThresholdSetup (DetectEngineCtx *de_ctx, Signature *s, SigMatch *m, char *rawstr) +{ + DetectThresholdData *de = NULL; + SigMatch *sm = NULL; + + de = DetectThresholdParse(rawstr); + if (de == NULL) + goto error; + + sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + + sm->type = DETECT_THRESHOLD; + sm->ctx = (void *)de; + + SigMatchAppend(s,m,sm); - if (dubbed) free(str); return 0; + +error: + if (de) free(de); + if (sm) free(sm); + return -1; } +/** + * \internal + * \brief this function will free memory associated with DetectThresholdData + * + * \param de pointer to DetectThresholdData + */ +static void DetectThresholdFree(void *de_ptr) { + DetectThresholdData *de = (DetectThresholdData *)de_ptr; + if (de) free(de); +} + +/* + * ONLY TESTS BELOW THIS COMMENT + */ +#ifdef UNITTESTS + +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-mpm.h" +#include "detect-engine-threshold.h" +#include "util-time.h" +#include "util-hashlist.h" + +/** + * \test ThresholdTestParse01 is a test for a valid threshold options + * + * \retval 1 on succces + * \retval 0 on failure + */ +static int ThresholdTestParse01 (void) { + DetectThresholdData *de = NULL; + de = DetectThresholdParse("type limit,track by_dst,count 10,seconds 60"); + if (de && (de->type == TYPE_LIMIT) && (de->track == TRACK_DST) && (de->count == 10) && (de->seconds == 60)) { + DetectThresholdFree(de); + return 1; + } + + return 0; +} + +/** + * \test ThresholdTestParse02 is a test for a invalid threshold options + * + * \retval 1 on succces + * \retval 0 on failure + */ +static int ThresholdTestParse02 (void) { + DetectThresholdData *de = NULL; + de = DetectThresholdParse("type any,track by_dst,count 10,seconds 60"); + if (de && (de->type == TYPE_LIMIT) && (de->track == TRACK_DST) && (de->count == 10) && (de->seconds == 60)) { + DetectThresholdFree(de); + return 1; + } + + return 0; +} + +/** + * \test DetectThresholdTestSig1 is a test for checking the working of limit keyword + * by setting up the signature and later testing its working by matching + * the received packet against the sig. + * + * \retval 1 on succces + * \retval 0 on failure + */ + +static int DetectThresholdTestSig1(void) { + + Packet p; + Signature *s = NULL; + ThreadVars th_v; + DetectEngineThreadCtx *det_ctx; + int result = 0; + int alerts = 0; + IPV4Hdr ip4h; + + memset(&th_v, 0, sizeof(th_v)); + memset(&p, 0, sizeof(p)); + memset(&ip4h, 0, sizeof(ip4h)); + + p.src.family = AF_INET; + p.dst.family = AF_INET; + p.proto = IPPROTO_TCP; + p.ip4h = &ip4h; + p.ip4h->ip_src.s_addr = 0x01010101; + p.ip4h->ip_dst.s_addr = 0x02020202; + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) { + goto end; + } + + de_ctx->flags |= DE_QUIET; + + s = de_ctx->sig_list = SigInit(de_ctx,"alert tcp any any -> any any (msg:\"Threshold limit\"; threshold: type limit, track by_dst, count 5, seconds 60; sid:1;)"); + if (s == NULL) { + goto end; + } + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + + alerts = PacketAlertCheck(&p, 1); + + if(alerts == 5) + result = 1; + else + goto cleanup; + +cleanup: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + +end: + return result; +} + +/** + * \test DetectThresholdTestSig2 is a test for checking the working of threshold keyword + * by setting up the signature and later testing its working by matching + * the received packet against the sig. + * + * \retval 1 on succces + * \retval 0 on failure + */ + +static int DetectThresholdTestSig2(void) { + + Packet p; + Signature *s = NULL; + ThreadVars th_v; + DetectEngineThreadCtx *det_ctx; + int result = 0; + int alerts = 0; + IPV4Hdr ip4h; + + memset(&th_v, 0, sizeof(th_v)); + memset(&p, 0, sizeof(p)); + memset(&ip4h, 0, sizeof(ip4h)); + + p.src.family = AF_INET; + p.dst.family = AF_INET; + p.proto = IPPROTO_TCP; + p.ip4h = &ip4h; + p.ip4h->ip_src.s_addr = 0x01010101; + p.ip4h->ip_dst.s_addr = 0x02020202; + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) { + goto end; + } + + de_ctx->flags |= DE_QUIET; + + s = de_ctx->sig_list = SigInit(de_ctx,"alert tcp any any -> any any (msg:\"Threshold\"; threshold: type threshold, track by_dst, count 5, seconds 60; sid:1;)"); + if (s == NULL) { + goto end; + } + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + + alerts = PacketAlertCheck(&p, 1); + + if (alerts == 2) + result = 1; + else + goto cleanup; + +cleanup: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + +end: + return result; +} + +/** + * \test DetectThresholdTestSig3 is a test for checking the working of limit keyword + * by setting up the signature and later testing its working by matching + * the received packet against the sig. + * + * \retval 1 on succces + * \retval 0 on failure + */ + +static int DetectThresholdTestSig3(void) { + + Packet p; + Signature *s = NULL; + ThreadVars th_v; + DetectEngineThreadCtx *det_ctx; + int result = 0; + int alerts = 0; + IPV4Hdr ip4h; + struct timeval ts; + DetectThresholdData *tsh = NULL; + DetectThresholdData *lookup_tsh = NULL; + + memset (&ts, 0, sizeof(struct timeval)); + TimeGet(&ts); + + memset(&th_v, 0, sizeof(th_v)); + memset(&p, 0, sizeof(p)); + memset(&ip4h, 0, sizeof(ip4h)); + + p.src.family = AF_INET; + p.dst.family = AF_INET; + p.proto = IPPROTO_TCP; + p.ip4h = &ip4h; + p.ip4h->ip_src.s_addr = 0x01010101; + p.ip4h->ip_dst.s_addr = 0x02020202; + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) { + goto end; + } + + de_ctx->flags |= DE_QUIET; + + s = de_ctx->sig_list = SigInit(de_ctx,"alert tcp any any -> any any (msg:\"Threshold limit\"; threshold: type limit, track by_dst, count 5, seconds 60; sid:10;)"); + if (s == NULL) { + goto end; + } + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + tsh = SigGetThresholdType(s,&p); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + + lookup_tsh = (DetectThresholdData *)HashListTableLookup(de_ctx->ths_ctx.threshold_hash_table_dst, tsh, sizeof(DetectThresholdData)); + + TimeSetIncrementTime(200); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + + if (lookup_tsh) + alerts = lookup_tsh->current_count; + + if (alerts == 3) + result = 1; + else + goto cleanup; + +cleanup: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); +end: + return result; +} + +/** + * \test DetectThresholdTestSig4 is a test for checking the working of both keyword + * by setting up the signature and later testing its working by matching + * the received packet against the sig. + * + * \retval 1 on succces + * \retval 0 on failure + */ + +static int DetectThresholdTestSig4(void) { + + Packet p; + Signature *s = NULL; + ThreadVars th_v; + DetectEngineThreadCtx *det_ctx; + int result = 0; + int alerts = 0; + IPV4Hdr ip4h; + struct timeval ts; + + memset (&ts, 0, sizeof(struct timeval)); + TimeGet(&ts); + + memset(&th_v, 0, sizeof(th_v)); + memset(&p, 0, sizeof(p)); + memset(&ip4h, 0, sizeof(ip4h)); + + p.src.family = AF_INET; + p.dst.family = AF_INET; + p.proto = IPPROTO_TCP; + p.ip4h = &ip4h; + p.ip4h->ip_src.s_addr = 0x01010101; + p.ip4h->ip_dst.s_addr = 0x02020202; + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) { + goto end; + } + + de_ctx->flags |= DE_QUIET; + + s = de_ctx->sig_list = SigInit(de_ctx,"alert tcp any any -> any any (msg:\"Threshold both\"; threshold: type both, track by_dst, count 2, seconds 60; sid:10;)"); + if (s == NULL) { + goto end; + } + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + + TimeSetIncrementTime(200); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + + alerts = PacketAlertCheck(&p, 10); + + if (alerts == 2) + result = 1; + else + goto cleanup; + +cleanup: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); +end: + return result; +} + +/** + * \test DetectThresholdTestSig5 is a test for checking the working of limit keyword + * by setting up the signature and later testing its working by matching + * the received packet against the sig. + * + * \retval 1 on succces + * \retval 0 on failure + */ + +static int DetectThresholdTestSig5(void) { + + Packet p; + Signature *s = NULL; + ThreadVars th_v; + DetectEngineThreadCtx *det_ctx; + int result = 0; + int alerts = 0; + IPV4Hdr ip4h; + + memset(&th_v, 0, sizeof(th_v)); + memset(&p, 0, sizeof(p)); + memset(&ip4h, 0, sizeof(ip4h)); + + p.src.family = AF_INET; + p.dst.family = AF_INET; + p.proto = IPPROTO_TCP; + p.ip4h = &ip4h; + p.ip4h->ip_src.s_addr = 0x01010101; + p.ip4h->ip_dst.s_addr = 0x02020202; + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) { + goto end; + } + + de_ctx->flags |= DE_QUIET; + + s = de_ctx->sig_list = SigInit(de_ctx,"alert tcp any any -> any any (msg:\"Threshold limit sid 1\"; threshold: type limit, track by_dst, count 5, seconds 60; sid:1;)"); + if (s == NULL) { + goto end; + } + + s = s->next = SigInit(de_ctx,"alert tcp any any -> any any (msg:\"Threshold limit sid 1000\"; threshold: type limit, track by_dst, count 5, seconds 60; sid:1000;)"); + if (s == NULL) { + goto end; + } + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + + alerts = PacketAlertCheck(&p, 1); + + alerts += PacketAlertCheck(&p, 1000); + + if(alerts == 10) + result = 1; + else + goto cleanup; + +cleanup: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + +end: + return result; +} +#endif /* UNITTESTS */ + +void ThresholdRegisterTests(void) { +#ifdef UNITTESTS + UtRegisterTest("ThresholdTestParse01", ThresholdTestParse01, 1); + UtRegisterTest("ThresholdTestParse02", ThresholdTestParse02, 0); + UtRegisterTest("DetectThresholdTestSig1", DetectThresholdTestSig1, 1); + UtRegisterTest("DetectThresholdTestSig2", DetectThresholdTestSig2, 1); + UtRegisterTest("DetectThresholdTestSig3", DetectThresholdTestSig3, 1); + UtRegisterTest("DetectThresholdTestSig4", DetectThresholdTestSig4, 1); + UtRegisterTest("DetectThresholdTestSig5", DetectThresholdTestSig5, 1); +#endif /* UNITTESTS */ +} diff --git a/src/detect-threshold.h b/src/detect-threshold.h index 2cf669d611..86c8e4dd35 100644 --- a/src/detect-threshold.h +++ b/src/detect-threshold.h @@ -1,8 +1,54 @@ +/* Copyright (c) 2009 Open Information Security Foundation */ + +/** \file + * \author Breno Silva + */ + #ifndef __DETECT_THRESHOLD_H__ #define __DETECT_THRESHOLD_H__ -/* prototypes */ +#include "decode-events.h" +#include "decode-ipv4.h" +#include "decode-tcp.h" + +#define TYPE_LIMIT 1 +#define TYPE_BOTH 2 +#define TYPE_THRESHOLD 3 + +#define TRACK_DST 1 +#define TRACK_SRC 2 + +/** + * \typedef DetectThresholdData + * A typedef for DetectThresholdData_ + */ + +typedef struct DetectThresholdData_ { + uint8_t type; /**< Threshold type : limit , threshold, both */ + uint8_t track; /**< Track type: by_src, by_src */ + uint32_t count; /**< Event count */ + uint32_t seconds; /**< Event seconds */ + uint32_t sid; /**< Signature id */ + uint8_t gid; /**< Signature group id */ + uint8_t ipv; /**< Packet ip version */ + + Address addr; /**< Var used to store dst or src addr */ + + uint32_t tv_sec1; /**< Var for time control */ + uint32_t current_count; /**< Var for count control */ +} DetectThresholdData; + + +/** + * Registration function for threshold: keyword + */ + void DetectThresholdRegister (void); -#endif /* __DETECT_THRESHOLD_H__ */ +/** + * This function registers unit tests for Threshold + */ + +void ThresholdRegisterTests(void); +#endif /*__DETECT_THRESHOLD_H__ */ diff --git a/src/detect.c b/src/detect.c index 7950edbfe9..ad17023649 100644 --- a/src/detect.c +++ b/src/detect.c @@ -16,6 +16,7 @@ #include "detect-engine-mpm.h" #include "detect-engine-iponly.h" #include "detect-http-cookie.h" +#include "detect-engine-threshold.h" #include "detect-decode-event.h" @@ -506,7 +507,8 @@ static int SigMatchSignaturesAppLayer(ThreadVars *th_v, DetectEngineCtx *de_ctx, if (s->match == NULL) { fmatch = 1; if (!(s->flags & SIG_FLAG_NOALERT)) { - PacketAlertAppend(p, 1, s->id, s->rev, s->prio, s->msg); + + PacketAlertHandle(de_ctx,s,p); /* set verdict on packet */ p->action = s->action; @@ -531,8 +533,8 @@ static int SigMatchSignaturesAppLayer(ThreadVars *th_v, DetectEngineCtx *de_ctx, fmatch = 1; //printf("DE : sig %" PRIu32 " matched\n", s->id); if (!(s->flags & SIG_FLAG_NOALERT)) { - PacketAlertAppend(p, 1, s->id, s->rev, s->prio, s->msg); + PacketAlertHandle(de_ctx,s,p); /* set verdict on packet */ p->action = s->action; } @@ -687,8 +689,8 @@ int SigMatchSignatures(ThreadVars *th_v, DetectEngineCtx *de_ctx, DetectEngineTh fmatch = 1; if (!(s->flags & SIG_FLAG_NOALERT)) { - PacketAlertAppend(p, s->gid, s->id, s->rev, s->prio, s->msg); + PacketAlertHandle(de_ctx,s,p); /* set verdict on packet */ p->action = s->action; } @@ -720,8 +722,8 @@ int SigMatchSignatures(ThreadVars *th_v, DetectEngineCtx *de_ctx, DetectEngineTh if (!(s->flags & SIG_FLAG_NOALERT)) { /* only add once */ if (rmatch == 0) { - PacketAlertAppend(p, s->gid, s->id, s->rev, s->prio, s->msg); + PacketAlertHandle(de_ctx,s,p); /* set verdict on packet */ p->action = s->action; } @@ -757,8 +759,8 @@ int SigMatchSignatures(ThreadVars *th_v, DetectEngineCtx *de_ctx, DetectEngineTh if (sm == NULL) { fmatch = 1; if (!(s->flags & SIG_FLAG_NOALERT)) { - PacketAlertAppend(p, s->gid, s->id, s->rev, s->prio, s->msg); + PacketAlertHandle(de_ctx,s,p); /* set verdict on packet */ p->action = s->action; } diff --git a/src/detect.h b/src/detect.h index e1e9dfc116..90d405ab6d 100644 --- a/src/detect.h +++ b/src/detect.h @@ -12,6 +12,8 @@ #include "util-hash.h" #include "util-hashlist.h" +#include "detect-threshold.h" + #define COUNTER_DETECT_ALERTS 1 /* forward declarations for the structures from detect-engine-sigorder.h */ @@ -219,6 +221,15 @@ typedef struct DetectEngineLookupDsize_ { */ #define DSIZE_STATES 2 +/** \brief threshold ctx */ +typedef struct ThresholdCtx_ { + HashListTable *threshold_hash_table_dst; /**< Ipv4 dst hash table */ + HashListTable *threshold_hash_table_src; /**< Ipv4 src hash table */ + HashListTable *threshold_hash_table_dst_ipv6; /**< Ipv6 dst hash table */ + HashListTable *threshold_hash_table_src_ipv6; /**< Ipv6 src hash table */ + pthread_mutex_t threshold_table_lock; /**< Mutex for hash table */ +}ThresholdCtx; + /** \brief main detection engine ctx */ typedef struct DetectEngineCtx_ { uint8_t flags; @@ -270,6 +281,7 @@ typedef struct DetectEngineCtx_ { uint32_t mpm_memory_size; DetectEngineIPOnlyCtx io_ctx; + ThresholdCtx ths_ctx; uint16_t mpm_matcher; /**< mpm matcher this ctx uses */ } DetectEngineCtx;