diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
commit | a0aa2307322cd47bbf416810ac0292925e03be87 (patch) | |
tree | 37076262a026c4b48c8a0e84f44ff9187556ca35 /src/detect-engine-threshold.c | |
parent | Initial commit. (diff) | |
download | suricata-a0aa2307322cd47bbf416810ac0292925e03be87.tar.xz suricata-a0aa2307322cd47bbf416810ac0292925e03be87.zip |
Adding upstream version 1:7.0.3.upstream/1%7.0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/detect-engine-threshold.c')
-rw-r--r-- | src/detect-engine-threshold.c | 762 |
1 files changed, 762 insertions, 0 deletions
diff --git a/src/detect-engine-threshold.c b/src/detect-engine-threshold.c new file mode 100644 index 0000000..b5ac867 --- /dev/null +++ b/src/detect-engine-threshold.c @@ -0,0 +1,762 @@ +/* Copyright (C) 2007-2021 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. + */ + +/** + * \defgroup threshold Thresholding + * + * This feature is used to reduce the number of logged alerts for noisy rules. + * This can be tuned to significantly reduce false alarms, and it can also be + * used to write a newer breed of rules. Thresholding commands limit the number + * of times a particular event is logged during a specified time interval. + * + * @{ + */ + +/** + * \file + * + * \author Breno Silva <breno.silva@gmail.com> + * \author Victor Julien <victor@inliniac.net> + * + * Threshold part of the detection engine. + */ + +#include "suricata-common.h" +#include "detect.h" +#include "flow.h" + +#include "host.h" +#include "host-storage.h" + +#include "ippair.h" +#include "ippair-storage.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-threads.h" + +#include "action-globals.h" + +static HostStorageId host_threshold_id = { .id = -1 }; /**< host storage id for thresholds */ +static IPPairStorageId ippair_threshold_id = { .id = -1 }; /**< ip pair storage id for thresholds */ + +HostStorageId ThresholdHostStorageId(void) +{ + return host_threshold_id; +} + +void ThresholdInit(void) +{ + host_threshold_id = HostStorageRegister("threshold", sizeof(void *), NULL, ThresholdListFree); + if (host_threshold_id.id == -1) { + FatalError("Can't initiate host storage for thresholding"); + } + ippair_threshold_id = IPPairStorageRegister("threshold", sizeof(void *), NULL, ThresholdListFree); + if (ippair_threshold_id.id == -1) { + FatalError("Can't initiate IP pair storage for thresholding"); + } +} + +int ThresholdHostHasThreshold(Host *host) +{ + return HostGetStorageById(host, host_threshold_id) ? 1 : 0; +} + +int ThresholdIPPairHasThreshold(IPPair *pair) +{ + return IPPairGetStorageById(pair, ippair_threshold_id) ? 1 : 0; +} + +/** + * \brief Return next DetectThresholdData for signature + * + * \param sig Signature pointer + * \param psm Pointer to a Signature Match pointer + * \param list List to return data from + * + * \retval tsh Return the threshold data from signature or NULL if not found + */ +const DetectThresholdData *SigGetThresholdTypeIter( + const Signature *sig, const SigMatchData **psm, int list) +{ + const SigMatchData *smd = NULL; + const DetectThresholdData *tsh = NULL; + + if (sig == NULL) + return NULL; + + if (*psm == NULL) { + smd = sig->sm_arrays[list]; + } else { + /* Iteration in progress, using provided value */ + smd = *psm; + } + + while (1) { + if (smd->type == DETECT_THRESHOLD || smd->type == DETECT_DETECTION_FILTER) { + tsh = (DetectThresholdData *)smd->ctx; + + if (smd->is_last) { + *psm = NULL; + } else { + *psm = smd + 1; + } + return tsh; + } + + if (smd->is_last) { + break; + } + smd++; + } + *psm = NULL; + return NULL; +} + +/** + * \brief Remove timeout threshold hash elements + * + * \param head Current head element of storage + * \param tv Current time + * + * \retval DetectThresholdEntry Return new head element or NULL if all expired + * + */ + +static DetectThresholdEntry *ThresholdTimeoutCheck(DetectThresholdEntry *head, SCTime_t ts) +{ + DetectThresholdEntry *tmp = head; + DetectThresholdEntry *prev = NULL; + DetectThresholdEntry *new_head = head; + + while (tmp != NULL) { + /* check if the 'check' timestamp is not before the creation ts. + * This can happen due to the async nature of the host timeout + * code that also calls this code from a management thread. */ + SCTime_t entry = SCTIME_ADD_SECS(tmp->tv1, (time_t)tmp->seconds); + if (SCTIME_CMP_LTE(ts, entry)) { + prev = tmp; + tmp = tmp->next; + continue; + } + + /* timed out */ + + DetectThresholdEntry *tde = tmp; + if (prev != NULL) { + prev->next = tmp->next; + } + else { + new_head = tmp->next; + } + tmp = tde->next; + SCFree(tde); + } + + return new_head; +} + +int ThresholdHostTimeoutCheck(Host *host, SCTime_t ts) +{ + DetectThresholdEntry* head = HostGetStorageById(host, host_threshold_id); + DetectThresholdEntry *new_head = ThresholdTimeoutCheck(head, ts); + if (new_head != head) { + HostSetStorageById(host, host_threshold_id, new_head); + } + return new_head == NULL; +} + +int ThresholdIPPairTimeoutCheck(IPPair *pair, SCTime_t ts) +{ + DetectThresholdEntry* head = IPPairGetStorageById(pair, ippair_threshold_id); + DetectThresholdEntry *new_head = ThresholdTimeoutCheck(head, ts); + if (new_head != head) { + IPPairSetStorageById(pair, ippair_threshold_id, new_head); + } + return new_head == NULL; +} + +static DetectThresholdEntry * +DetectThresholdEntryAlloc(const DetectThresholdData *td, Packet *p, + uint32_t sid, uint32_t gid) +{ + SCEnter(); + + DetectThresholdEntry *ste = SCCalloc(1, sizeof(DetectThresholdEntry)); + if (unlikely(ste == NULL)) { + SCReturnPtr(NULL, "DetectThresholdEntry"); + } + + ste->sid = sid; + ste->gid = gid; + ste->track = td->track; + ste->seconds = td->seconds; + + SCReturnPtr(ste, "DetectThresholdEntry"); +} + +static DetectThresholdEntry *ThresholdHostLookupEntry(Host *h, + uint32_t sid, uint32_t gid) +{ + DetectThresholdEntry *e; + + for (e = HostGetStorageById(h, host_threshold_id); e != NULL; e = e->next) { + if (e->sid == sid && e->gid == gid) + break; + } + + return e; +} + +static DetectThresholdEntry *ThresholdIPPairLookupEntry(IPPair *pair, + uint32_t sid, uint32_t gid) +{ + DetectThresholdEntry *e; + + for (e = IPPairGetStorageById(pair, ippair_threshold_id); e != NULL; e = e->next) { + if (e->sid == sid && e->gid == gid) + break; + } + + return e; +} + +static int ThresholdHandlePacketSuppress(Packet *p, + const DetectThresholdData *td, uint32_t sid, uint32_t gid) +{ + int ret = 0; + DetectAddress *m = NULL; + switch (td->track) { + case TRACK_DST: + m = DetectAddressLookupInHead(&td->addrs, &p->dst); + SCLogDebug("TRACK_DST"); + break; + case TRACK_SRC: + m = DetectAddressLookupInHead(&td->addrs, &p->src); + SCLogDebug("TRACK_SRC"); + break; + /* suppress if either src or dst is a match on the suppress + * address list */ + case TRACK_EITHER: + m = DetectAddressLookupInHead(&td->addrs, &p->src); + if (m == NULL) { + m = DetectAddressLookupInHead(&td->addrs, &p->dst); + } + break; + case TRACK_RULE: + default: + SCLogError("track mode %d is not supported", td->track); + break; + } + if (m == NULL) + ret = 1; + else + ret = 2; /* suppressed but still need actions */ + + return ret; +} + +static inline void RateFilterSetAction(Packet *p, PacketAlert *pa, uint8_t new_action) +{ + switch (new_action) { + case TH_ACTION_ALERT: + pa->flags |= PACKET_ALERT_RATE_FILTER_MODIFIED; + pa->action = ACTION_ALERT; + break; + case TH_ACTION_DROP: + pa->flags |= PACKET_ALERT_RATE_FILTER_MODIFIED; + pa->action = ACTION_DROP; + break; + case TH_ACTION_REJECT: + pa->flags |= PACKET_ALERT_RATE_FILTER_MODIFIED; + pa->action = (ACTION_REJECT | ACTION_DROP); + break; + case TH_ACTION_PASS: + pa->flags |= PACKET_ALERT_RATE_FILTER_MODIFIED; + pa->action = ACTION_PASS; + break; + default: + /* Weird, leave the default action */ + break; + } +} + +/** +* \brief Check if the entry reached threshold count limit +* +* \param lookup_tsh Current threshold entry +* \param td Threshold settings +* \param packet_time used to compare against previous detection and to set timeouts +* +* \retval int 1 if threshold reached for this entry +* +*/ +static int IsThresholdReached( + DetectThresholdEntry *lookup_tsh, const DetectThresholdData *td, SCTime_t packet_time) +{ + int ret = 0; + + /* Check if we have a timeout enabled, if so, + * we still matching (and enabling the new_action) */ + if (lookup_tsh->tv_timeout != 0) { + if ((SCTIME_SECS(packet_time) - lookup_tsh->tv_timeout) > td->timeout) { + /* Ok, we are done, timeout reached */ + lookup_tsh->tv_timeout = 0; + } else { + /* Already matching */ + ret = 1; + } /* else - if ((packet_time - lookup_tsh->tv_timeout) > td->timeout) */ + + } + else { + /* Update the matching state with the timeout interval */ + SCTime_t entry = SCTIME_ADD_SECS(lookup_tsh->tv1, td->seconds); + if (SCTIME_CMP_LTE(packet_time, entry)) { + lookup_tsh->current_count++; + if (lookup_tsh->current_count > td->count) { + /* Then we must enable the new action by setting a + * timeout */ + lookup_tsh->tv_timeout = SCTIME_SECS(packet_time); + ret = 1; + } + } else { + lookup_tsh->tv1 = packet_time; + lookup_tsh->current_count = 1; + } + } /* else - if (lookup_tsh->tv_timeout != 0) */ + + return ret; +} + +static void AddEntryToHostStorage(Host *h, DetectThresholdEntry *e, SCTime_t packet_time) +{ + if (h && e) { + e->current_count = 1; + e->tv1 = packet_time; + e->tv_timeout = 0; + e->next = HostGetStorageById(h, host_threshold_id); + HostSetStorageById(h, host_threshold_id, e); + } +} + +static void AddEntryToIPPairStorage(IPPair *pair, DetectThresholdEntry *e, SCTime_t packet_time) +{ + if (pair && e) { + e->current_count = 1; + e->tv1 = packet_time; + e->tv_timeout = 0; + e->next = IPPairGetStorageById(pair, ippair_threshold_id); + IPPairSetStorageById(pair, ippair_threshold_id, e); + } +} + +/** + * \retval 2 silent match (no alert but apply actions) + * \retval 1 normal match + * \retval 0 no match + * + * If a new DetectThresholdEntry is generated to track the threshold + * for this rule, then it will be returned in new_tsh. + */ +static int ThresholdHandlePacket(Packet *p, DetectThresholdEntry *lookup_tsh, + DetectThresholdEntry **new_tsh, const DetectThresholdData *td, + uint32_t sid, uint32_t gid, PacketAlert *pa) +{ + int ret = 0; + + switch(td->type) { + case TYPE_LIMIT: + { + SCLogDebug("limit"); + + if (lookup_tsh != NULL) { + SCTime_t entry = SCTIME_ADD_SECS(lookup_tsh->tv1, td->seconds); + if (SCTIME_CMP_LTE(p->ts, entry)) { + lookup_tsh->current_count++; + + if (lookup_tsh->current_count <= td->count) { + ret = 1; + } else { + ret = 2; + } + } else { + lookup_tsh->tv1 = p->ts; + lookup_tsh->current_count = 1; + + ret = 1; + } + } else { + *new_tsh = DetectThresholdEntryAlloc(td, p, sid, gid); + + ret = 1; + } + break; + } + case TYPE_THRESHOLD: + { + SCLogDebug("threshold"); + + if (lookup_tsh != NULL) { + SCTime_t entry = SCTIME_ADD_SECS(lookup_tsh->tv1, td->seconds); + if (SCTIME_CMP_LTE(p->ts, entry)) { + lookup_tsh->current_count++; + + if (lookup_tsh->current_count >= td->count) { + ret = 1; + lookup_tsh->current_count = 0; + } + } else { + lookup_tsh->tv1 = p->ts; + lookup_tsh->current_count = 1; + } + } else { + if (td->count == 1) { + ret = 1; + } else { + *new_tsh = DetectThresholdEntryAlloc(td, p, sid, gid); + } + } + break; + } + case TYPE_BOTH: + { + SCLogDebug("both"); + + if (lookup_tsh != NULL) { + SCTime_t entry = SCTIME_ADD_SECS(lookup_tsh->tv1, td->seconds); + if (SCTIME_CMP_LTE(p->ts, entry)) { + /* within time limit */ + + lookup_tsh->current_count++; + if (lookup_tsh->current_count == td->count) { + ret = 1; + } else if (lookup_tsh->current_count > td->count) { + /* silent match */ + ret = 2; + } + } else { + /* expired, so reset */ + lookup_tsh->tv1 = p->ts; + lookup_tsh->current_count = 1; + + /* if we have a limit of 1, this is a match */ + if (lookup_tsh->current_count == td->count) { + ret = 1; + } + } + } else { + *new_tsh = DetectThresholdEntryAlloc(td, p, sid, gid); + + /* for the first match we return 1 to + * indicate we should alert */ + if (td->count == 1) { + ret = 1; + } + } + break; + } + /* detection_filter */ + case TYPE_DETECTION: + { + SCLogDebug("detection_filter"); + + if (lookup_tsh != NULL) { + SCTime_t entry = SCTIME_ADD_SECS(lookup_tsh->tv1, td->seconds); + if (SCTIME_CMP_LTE(p->ts, entry)) { + /* within timeout */ + lookup_tsh->current_count++; + if (lookup_tsh->current_count > td->count) { + ret = 1; + } + } else { + /* expired, reset */ + lookup_tsh->tv1 = p->ts; + lookup_tsh->current_count = 1; + } + } else { + *new_tsh = DetectThresholdEntryAlloc(td, p, sid, gid); + } + break; + } + /* rate_filter */ + case TYPE_RATE: + { + SCLogDebug("rate_filter"); + ret = 1; + if (lookup_tsh && IsThresholdReached(lookup_tsh, td, p->ts)) { + RateFilterSetAction(p, pa, td->new_action); + } else if (!lookup_tsh) { + *new_tsh = DetectThresholdEntryAlloc(td, p, sid, gid); + } + break; + } + /* case TYPE_SUPPRESS: is not handled here */ + default: + SCLogError("type %d is not supported", td->type); + } + return ret; +} + +static int ThresholdHandlePacketIPPair(IPPair *pair, Packet *p, const DetectThresholdData *td, + uint32_t sid, uint32_t gid, PacketAlert *pa) +{ + int ret = 0; + + DetectThresholdEntry *lookup_tsh = ThresholdIPPairLookupEntry(pair, sid, gid); + SCLogDebug("ippair lookup_tsh %p sid %u gid %u", lookup_tsh, sid, gid); + + DetectThresholdEntry *new_tsh = NULL; + ret = ThresholdHandlePacket(p, lookup_tsh, &new_tsh, td, sid, gid, pa); + if (new_tsh != NULL) { + AddEntryToIPPairStorage(pair, new_tsh, p->ts); + } + + return ret; +} + +/** + * \retval 2 silent match (no alert but apply actions) + * \retval 1 normal match + * \retval 0 no match + */ +static int ThresholdHandlePacketHost(Host *h, Packet *p, const DetectThresholdData *td, + uint32_t sid, uint32_t gid, PacketAlert *pa) +{ + int ret = 0; + DetectThresholdEntry *lookup_tsh = ThresholdHostLookupEntry(h, sid, gid); + SCLogDebug("lookup_tsh %p sid %u gid %u", lookup_tsh, sid, gid); + + DetectThresholdEntry *new_tsh = NULL; + ret = ThresholdHandlePacket(p, lookup_tsh, &new_tsh, td, sid, gid, pa); + if (new_tsh != NULL) { + AddEntryToHostStorage(h, new_tsh, p->ts); + } + return ret; +} + +static int ThresholdHandlePacketRule(DetectEngineCtx *de_ctx, Packet *p, + const DetectThresholdData *td, const Signature *s, PacketAlert *pa) +{ + int ret = 0; + + DetectThresholdEntry* lookup_tsh = (DetectThresholdEntry *)de_ctx->ths_ctx.th_entry[s->num]; + SCLogDebug("by_rule lookup_tsh %p num %u", lookup_tsh, s->num); + + DetectThresholdEntry *new_tsh = NULL; + ret = ThresholdHandlePacket(p, lookup_tsh, &new_tsh, td, s->id, s->gid, pa); + if (new_tsh != NULL) { + new_tsh->tv1 = p->ts; + new_tsh->current_count = 1; + new_tsh->tv_timeout = 0; + de_ctx->ths_ctx.th_entry[s->num] = new_tsh; + } + + return ret; +} + +/** + * \brief Make the threshold logic for signatures + * + * \param de_ctx Detection Context + * \param tsh_ptr Threshold element + * \param p Packet structure + * \param s Signature structure + * + * \retval 2 silent match (no alert but apply actions) + * \retval 1 alert on this event + * \retval 0 do not alert on this event + */ +int PacketAlertThreshold(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const DetectThresholdData *td, Packet *p, const Signature *s, PacketAlert *pa) +{ + SCEnter(); + + int ret = 0; + if (td == NULL) { + SCReturnInt(0); + } + + if (td->type == TYPE_SUPPRESS) { + ret = ThresholdHandlePacketSuppress(p,td,s->id,s->gid); + } else if (td->track == TRACK_SRC) { + Host *src = HostGetHostFromHash(&p->src); + if (src) { + ret = ThresholdHandlePacketHost(src,p,td,s->id,s->gid,pa); + HostRelease(src); + } + } else if (td->track == TRACK_DST) { + Host *dst = HostGetHostFromHash(&p->dst); + if (dst) { + ret = ThresholdHandlePacketHost(dst,p,td,s->id,s->gid,pa); + HostRelease(dst); + } + } else if (td->track == TRACK_BOTH) { + IPPair *pair = IPPairGetIPPairFromHash(&p->src, &p->dst); + if (pair) { + ret = ThresholdHandlePacketIPPair(pair, p, td, s->id, s->gid, pa); + IPPairRelease(pair); + } + } else if (td->track == TRACK_RULE) { + SCMutexLock(&de_ctx->ths_ctx.threshold_table_lock); + ret = ThresholdHandlePacketRule(de_ctx,p,td,s,pa); + SCMutexUnlock(&de_ctx->ths_ctx.threshold_table_lock); + } + + SCReturnInt(ret); +} + +/** + * \brief Init threshold context hash tables + * + * \param de_ctx Detection Context + * + */ +void ThresholdHashInit(DetectEngineCtx *de_ctx) +{ + if (SCMutexInit(&de_ctx->ths_ctx.threshold_table_lock, NULL) != 0) { + FatalError("Threshold: Failed to initialize hash table mutex."); + } +} + +/** + * \brief Allocate threshold context hash tables + * + * \param de_ctx Detection Context + */ +void ThresholdHashAllocate(DetectEngineCtx *de_ctx) +{ + Signature *s = de_ctx->sig_list; + bool has_by_rule_tracking = false; + const DetectThresholdData *td = NULL; + const SigMatchData *smd; + + /* Find the signature with the highest signature number that is using + thresholding with by_rule tracking. */ + uint32_t highest_signum = 0; + while (s != NULL) { + if (s->sm_arrays[DETECT_SM_LIST_SUPPRESS] != NULL) { + smd = NULL; + do { + td = SigGetThresholdTypeIter(s, &smd, DETECT_SM_LIST_SUPPRESS); + if (td == NULL) { + continue; + } + if (td->track != TRACK_RULE) { + continue; + } + if (s->num >= highest_signum) { + highest_signum = s->num; + has_by_rule_tracking = true; + } + } while (smd != NULL); + } + + if (s->sm_arrays[DETECT_SM_LIST_THRESHOLD] != NULL) { + smd = NULL; + do { + td = SigGetThresholdTypeIter(s, &smd, DETECT_SM_LIST_THRESHOLD); + if (td == NULL) { + continue; + } + if (td->track != TRACK_RULE) { + continue; + } + if (s->num >= highest_signum) { + highest_signum = s->num; + has_by_rule_tracking = true; + } + } while (smd != NULL); + } + + s = s->next; + } + + /* Skip allocating if by_rule tracking is not used */ + if (has_by_rule_tracking == false) { + return; + } + + de_ctx->ths_ctx.th_size = highest_signum + 1; + de_ctx->ths_ctx.th_entry = SCCalloc(de_ctx->ths_ctx.th_size, sizeof(DetectThresholdEntry *)); + if (de_ctx->ths_ctx.th_entry == NULL) { + FatalError( + "failed to allocate memory for \"by_rule\" thresholding (tried to allocate %" PRIu32 + " entries)", + de_ctx->ths_ctx.th_size); + } +} + +/** + * \brief Destroy threshold context hash tables + * + * \param de_ctx Detection Context + * + */ +void ThresholdContextDestroy(DetectEngineCtx *de_ctx) +{ + if (de_ctx->ths_ctx.th_entry != NULL) { + for (uint32_t i = 0; i < de_ctx->ths_ctx.th_size; i++) { + if (de_ctx->ths_ctx.th_entry[i] != NULL) { + SCFree(de_ctx->ths_ctx.th_entry[i]); + } + } + SCFree(de_ctx->ths_ctx.th_entry); + } + SCMutexDestroy(&de_ctx->ths_ctx.threshold_table_lock); +} + +/** + * \brief this function will free all the entries of a list + * DetectTagDataEntry + * + * \param td pointer to DetectTagDataEntryList + */ +void ThresholdListFree(void *ptr) +{ + if (ptr != NULL) { + DetectThresholdEntry *entry = ptr; + + while (entry != NULL) { + DetectThresholdEntry *next_entry = entry->next; + SCFree(entry); + entry = next_entry; + } + } +} + +/** + * @} + */ |