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.
suricata/src/detect-engine-tag.c

811 lines
28 KiB
C

/* Copyright (C) 2007-2010 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 detect-engine-tag.c
*
* \author Pablo Rincon Crespo <pablo.rincon.crespo@gmail.com>
*
* Implements a global context to store data related to hosts flagged
* tag keyword
*/
#include "suricata-common.h"
#include "util-hash.h"
#include "util-atomic.h"
#include "util-time.h"
#include "util-hashlist.h"
#include "detect-engine-tag.h"
#include "detect-tag.h"
static void TagTimeoutRemove(DetectTagHostCtx *tag_ctx, struct timeval *tv);
SC_ATOMIC_DECLARE(unsigned int, num_tags); /**< Atomic counter, to know if we
have tagged hosts/sessions,
to avoid locking */
/* Global Ctx for tagging hosts */
DetectTagHostCtx *tag_ctx = NULL;
void TagFreeFunc(void *data)
{
DetectTagDataListFree(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 TagCompareFunc(void *data1, uint16_t len1, void *data2,uint16_t len2)
{
SCEnter();
DetectTagDataEntryList *a = (DetectTagDataEntryList *)data1;
DetectTagDataEntryList *b = (DetectTagDataEntryList *)data2;
if (CMP_ADDR(&a->addr,&b->addr)) {
SCReturnInt(1);
}
SCReturnInt(0);
}
/**
* \brief Create the hash for tag tables
*
* \param ht Hash Table
* \param data DataEntry that will be used to create the hash
* \param datalen DataEntry length
*
* \retval hash the hash
*/
uint32_t TagHashFunc(HashListTable *ht, void *data, uint16_t datalen)
{
SCEnter();
if (data == NULL) return 0;
DetectTagDataEntryList *dt = (DetectTagDataEntryList *)data;
uint32_t hash = 0;
if (dt->ipv == 4)
hash = (dt->addr.addr_data32[0]);
else if (dt->ipv == 6)
hash = (dt->addr.addr_data32[0] +
dt->addr.addr_data32[1] +
dt->addr.addr_data32[2] +
dt->addr.addr_data32[3]);
else {
SCLogDebug("no dt->ipv");
}
SCReturnInt(hash % TAG_HASH_SIZE);
}
void TagInitCtx(void) {
tag_ctx = SCMalloc(sizeof(DetectTagHostCtx));
if (tag_ctx == NULL) {
exit(EXIT_FAILURE);
}
memset(tag_ctx, 0, sizeof(DetectTagHostCtx));
TimeGet(&tag_ctx->last_ts);
if (SCMutexInit(&tag_ctx->lock, NULL) != 0) {
SCLogError(SC_ERR_MEM_ALLOC,
"Tag: Failed to initialize hash table mutex.");
exit(EXIT_FAILURE);
}
TagHashInit(tag_ctx);
SC_ATOMIC_INIT(num_tags);
}
/**
* \brief Destroy tag context hash tables
*
* \param tag_ctx Tag Context
*
*/
void TagDestroyCtx(void)
{
HashListTableFree(tag_ctx->tag_hash_table_ipv4);
tag_ctx->tag_hash_table_ipv4 = NULL;
HashListTableFree(tag_ctx->tag_hash_table_ipv6);
tag_ctx->tag_hash_table_ipv6 = NULL;
SCMutexDestroy(&tag_ctx->lock);
SCFree(tag_ctx);
tag_ctx = NULL;
#ifdef DEBUG
BUG_ON(SC_ATOMIC_GET(num_tags) != 0);
#endif
SC_ATOMIC_DESTROY(num_tags);
}
/** \brief Reset the tagging engine context
*/
void TagRestartCtx() {
TagDestroyCtx();
TagInitCtx();
}
/**
* \brief Init tag context hash tables
*
* \param det_ctx Dectection Thread Context
*
*/
void TagHashInit(DetectTagHostCtx *tag_ctx)
{
tag_ctx->tag_hash_table_ipv4 = HashListTableInit(TAG_HASH_SIZE, TagHashFunc, TagCompareFunc, TagFreeFunc);
if(tag_ctx->tag_hash_table_ipv4 == NULL) {
SCLogError(SC_ERR_MEM_ALLOC,
"Tag: Failed to initialize ipv4 dst hash table.");
exit(EXIT_FAILURE);
}
tag_ctx->tag_hash_table_ipv6 = HashListTableInit(TAG_HASH_SIZE, TagHashFunc, TagCompareFunc, TagFreeFunc);
if(tag_ctx->tag_hash_table_ipv6 == NULL) {
SCLogError(SC_ERR_MEM_ALLOC,
"Tag: Failed to initialize ipv4 src hash table.");
exit(EXIT_FAILURE);
}
}
/**
* \brief Search for a tag data into tag hash table
*
* \param de_ctx Dectection Context
* \param dtde Tag element
* \param p Packet structure
*
* \retval lookup_tde Return the tag element
*/
DetectTagDataEntryList *TagHashSearch(DetectTagHostCtx *tag_ctx, DetectTagDataEntryList *dtde,
Packet *p)
{
SCEnter();
DetectTagDataEntryList *lookup_tde = NULL;
if (PKT_IS_IPV4(p)) {
SCLogDebug("ipv4 search");
lookup_tde = HashListTableLookup(tag_ctx->tag_hash_table_ipv4, dtde, sizeof(DetectTagDataEntryList));
} else if (PKT_IS_IPV6(p)) {
SCLogDebug("ipv6 search");
lookup_tde = HashListTableLookup(tag_ctx->tag_hash_table_ipv6, dtde, sizeof(DetectTagDataEntryList));
}
SCReturnPtr(lookup_tde, "DetectTagDataEntryList");
}
/**
* \brief Add tag element into hash table
*
* \param de_ctx Dectection Context
* \param dtde Tag element
* \param p Packet structure
*
*/
int TagHashAdd(DetectTagHostCtx *tag_ctx, DetectTagDataEntryList *dtde, Packet *p)
{
SCEnter();
int ret = 0;
if (PKT_IS_IPV4(p)) {
dtde->ipv = 4;
ret = HashListTableAdd(tag_ctx->tag_hash_table_ipv4,
dtde, sizeof(DetectTagDataEntry));
} else if (PKT_IS_IPV6(p)) {
dtde->ipv = 6;
ret = HashListTableAdd(tag_ctx->tag_hash_table_ipv6,
dtde, sizeof(DetectTagDataEntry));
}
SCReturnInt((ret == 0));
}
/**
* \brief Add a tag entry for a host. If it already exist, update it.
*
* \param tag_ctx Tag context for hosts
* \param tde Tag data
* \param p packet
*
* \retval 0 if it was added, 1 if it was updated
*/
int TagHashAddTag(DetectTagHostCtx *tag_ctx, DetectTagDataEntry *tde, Packet *p)
{
SCEnter();
DetectTagDataEntryList *entry = NULL;
uint8_t updated = 0;
uint16_t num_tags = 0;
/* local, just for searching */
DetectTagDataEntryList tdl;
tdl.header_entry = NULL;
tdl.header_entry = tde;
SCMutexLock(&tag_ctx->lock);
/* first search if we already have an entry of this host */
if (PKT_IS_IPV4(p)) {
tdl.ipv = 4;
if (tde->td->direction == DETECT_TAG_DIR_SRC) {
SET_IPV4_SRC_ADDR(p, &tdl.addr);
} else if (tde->td->direction == DETECT_TAG_DIR_DST) {
SET_IPV4_DST_ADDR(p, &tdl.addr);
}
} else if (PKT_IS_IPV6(p)) {
tdl.ipv = 6;
if (tde->td->direction == DETECT_TAG_DIR_SRC) {
SET_IPV6_SRC_ADDR(p, &tdl.addr);
} else if (tde->td->direction == DETECT_TAG_DIR_DST) {
SET_IPV6_DST_ADDR(p, &tdl.addr);
}
}
entry = TagHashSearch(tag_ctx, &tdl, p);
if (entry == NULL) {
DetectTagDataEntryList *new = SCMalloc(sizeof(DetectTagDataEntryList));
if (new != NULL) {
memcpy(new, &tdl, sizeof(DetectTagDataEntryList));
/* get a new tde as the one we have is on the stack */
DetectTagDataEntry *new_tde = DetectTagDataCopy(tde);
if (new_tde == NULL) {
SCFree(new);
} else {
new->header_entry = new_tde;
}
/* increment num_tags before adding to prevent a minor race,
* on setting and checking the first tag */
SC_ATOMIC_ADD(num_tags, 1);
if (!(TagHashAdd(tag_ctx, new, p))) {
SC_ATOMIC_SUB(num_tags, 1);
SCFree(new_tde);
SCFree(new);
}
} else {
SCLogDebug("Failed to allocate a new session");
}
} else {
/* Append the tag to the list of this host */
/* First iterate installed entries searching a duplicated sid/gid */
DetectTagDataEntry *iter = NULL;
for (iter = entry->header_entry; iter != NULL; iter = iter->next) {
num_tags++;
if (iter->sid == tde->sid && iter->gid == tde->gid) {
iter->cnt_match++;
/* If so, update data, unless the maximum MATCH limit is
* reached. This prevents possible DOS attacks */
if (iter->cnt_match < DETECT_TAG_MATCH_LIMIT) {
/* Reset time and counters */
iter->first_ts.tv_sec = iter->last_ts.tv_sec = tde->first_ts.tv_sec;
iter->packets = 0;
iter->bytes = 0;
}
updated = 1;
break;
}
}
/* If there was no entry of this rule, append the new tde */
if (updated == 0 && num_tags < DETECT_TAG_MAX_TAGS) {
/* get a new tde as the one we have is on the stack */
DetectTagDataEntry *new_tde = DetectTagDataCopy(tde);
if (new_tde != NULL) {
SC_ATOMIC_ADD(num_tags, 1);
new_tde->next = entry->header_entry;
entry->header_entry = new_tde;
}
} else if (num_tags == DETECT_TAG_MAX_TAGS) {
SCLogDebug("Max tags for sessions reached (%"PRIu16")", num_tags);
}
}
SCMutexUnlock(&tag_ctx->lock);
SCReturnInt(updated);
}
/**
* \brief Search tags for src and dst. Update entries of the tag, remove if necessary
*
* \param de_ctx Detect context
* \param det_ctx Detect thread context
* \param p packet
*
*/
void TagHandlePacket(DetectEngineCtx *de_ctx,
DetectEngineThreadCtx *det_ctx, Packet *p)
{
DetectTagDataEntry *tde = NULL;
DetectTagDataEntry *prev = NULL;
DetectTagDataEntry *iter = NULL;
DetectTagDataEntryList tdl;
DetectTagDataEntryList *tde_src = NULL;
DetectTagDataEntryList *tde_dst = NULL;
/* If there's no tag, get out of here */
unsigned int current_tags = SC_ATOMIC_GET(num_tags);
if (current_tags == 0)
return;
uint8_t flag_added = 0;
/* First update and get session tags */
if (p->flow != NULL) {
SCMutexLock(&p->flow->m);
if (p->flow->tag_list != NULL) {
iter = p->flow->tag_list->header_entry;
prev = NULL;
while (iter != NULL) {
/* update counters */
iter->last_ts.tv_sec = p->ts.tv_sec;
iter->packets++;
iter->bytes += GET_PKT_LEN(p);
/* If this packet triggered the rule with tag, we dont need
* to log it (the alert will log it) */
if (iter->skipped_first == 0) {
iter->skipped_first = 1;
} else if (iter->td != NULL) {
/* Update metrics; remove if tag expired; and set alerts */
switch (iter->td->metric) {
case DETECT_TAG_METRIC_PACKET:
if (iter->packets > iter->td->count) {
/* tag expired */
if (prev != NULL) {
tde = iter;
prev->next = iter->next;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
continue;
} else {
p->flow->tag_list->header_entry = iter->next;
tde = iter;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
continue;
}
} else if (flag_added == 0) {
/* It's matching the tag. Add it to be logged and
* update "flag_added" to add the packet once. */
p->flags |= PKT_HAS_TAG;
flag_added++;
}
break;
case DETECT_TAG_METRIC_BYTES:
if (iter->bytes > iter->td->count) {
/* tag expired */
if (prev != NULL) {
tde = iter;
prev->next = iter->next;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
continue;
} else {
p->flow->tag_list->header_entry = iter->next;
tde = iter;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
continue;
}
} else if (flag_added == 0) {
/* It's matching the tag. Add it to be logged and
* update "flag_added" to add the packet once. */
p->flags |= PKT_HAS_TAG;
flag_added++;
}
break;
case DETECT_TAG_METRIC_SECONDS:
/* last_ts handles this metric, but also a generic time based
* expiration to prevent dead sessions/hosts */
if (iter->last_ts.tv_sec - iter->first_ts.tv_sec > (int)iter->td->count) {
/* tag expired */
if (prev != NULL) {
tde = iter;
prev->next = iter->next;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
continue;
} else {
p->flow->tag_list->header_entry = iter->next;
tde = iter;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
continue;
}
} else if (flag_added == 0) {
/* It's matching the tag. Add it to be logged and
* update "flag_added" to add the packet once. */
p->flags |= PKT_HAS_TAG;
flag_added++;
}
break;
}
}
prev = iter;
iter = iter->next;
}
iter = NULL;
}
SCMutexUnlock(&p->flow->m);
}
/* Then search the src and dst hosts at the ctx */
SCMutexLock(&tag_ctx->lock);
/* Check for timeout tags if we reached the interval for checking it */
if (p->ts.tv_sec - tag_ctx->last_ts.tv_sec > TAG_TIMEOUT_CHECK_INTERVAL) {
TagTimeoutRemove(tag_ctx, &p->ts);
tag_ctx->last_ts.tv_sec = p->ts.tv_sec;
}
if (PKT_IS_IPV4(p)) {
tdl.ipv = 4;
/* search tags for source */
SET_IPV4_SRC_ADDR(p, &tdl.addr);
tde_src = TagHashSearch(tag_ctx, &tdl, p);
/* search tags for dest */
SET_IPV4_DST_ADDR(p, &tdl.addr);
tde_dst = TagHashSearch(tag_ctx, &tdl, p);
} else if (PKT_IS_IPV6(p)) {
tdl.ipv = 6;
/* search tags for source */
SET_IPV6_SRC_ADDR(p, &tdl.addr);
tde_src = TagHashSearch(tag_ctx, &tdl, p);
/* search tags for dest */
SET_IPV6_DST_ADDR(p, &tdl.addr);
tde_dst = TagHashSearch(tag_ctx, &tdl, p);
}
if (tde_src != NULL) {
iter = tde_src->header_entry;
prev = NULL;
while (iter != NULL) {
/* update counters */
iter->last_ts.tv_sec = p->ts.tv_sec;
iter->packets++;
iter->bytes += GET_PKT_LEN(p);
/* If this packet triggered the rule with tag, we dont need
* to log it (the alert will log it) */
if (iter->skipped_first == 0) {
iter->skipped_first = 1;
} else if (iter->td != NULL) {
/* Update metrics; remove if tag expired; and set alerts */
switch (iter->td->metric) {
case DETECT_TAG_METRIC_PACKET:
if (iter->packets > iter->td->count) {
/* tag expired */
if (prev != NULL) {
tde = iter;
prev->next = iter->next;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
continue;
} else {
tde = iter;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
tde_src->header_entry = iter;
continue;
}
} else if (flag_added == 0) {
/* It's matching the tag. Add it to be logged and
* update "flag_added" to add the packet once. */
p->flags |= PKT_HAS_TAG;
flag_added++;
}
break;
case DETECT_TAG_METRIC_BYTES:
if (iter->bytes > iter->td->count) {
/* tag expired */
if (prev != NULL) {
tde = iter;
prev->next = iter->next;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
continue;
} else {
tde = iter;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
tde_src->header_entry = iter;
continue;
}
} else if (flag_added == 0) {
/* It's matching the tag. Add it to be logged and
* update "flag_added" to add the packet once. */
p->flags |= PKT_HAS_TAG;
flag_added++;
}
break;
case DETECT_TAG_METRIC_SECONDS:
/* last_ts handles this metric, but also a generic time based
* expiration to prevent dead sessions/hosts */
if (iter->last_ts.tv_sec - iter->first_ts.tv_sec > (int)iter->td->count) {
/* tag expired */
if (prev != NULL) {
tde = iter;
prev->next = iter->next;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
continue;
} else {
tde = iter;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
tde_src->header_entry = iter;
continue;
}
} else if (flag_added == 0) {
/* It's matching the tag. Add it to be logged and
* update "flag_added" to add the packet once. */
p->flags |= PKT_HAS_TAG;
flag_added++;
}
break;
}
}
prev = iter;
iter = iter->next;
}
}
if (tde_dst != NULL) {
iter = tde_dst->header_entry;
prev = NULL;
while (iter != NULL) {
/* update counters */
iter->last_ts.tv_sec = p->ts.tv_sec;
iter->packets++;
iter->bytes += GET_PKT_LEN(p);
/* If this packet triggered the rule with tag, we dont need
* to log it (the alert will log it) */
if (iter->skipped_first == 0) {
iter->skipped_first = 1;
} else if (iter->td != NULL) {
/* Update metrics; remove if tag expired; and set alerts */
switch (iter->td->metric) {
case DETECT_TAG_METRIC_PACKET:
if (iter->packets > iter->td->count) {
/* tag expired */
if (prev != NULL) {
tde = iter;
prev->next = iter->next;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
continue;
} else {
tde = iter;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
tde_dst->header_entry = iter;
continue;
}
} else if (flag_added == 0) {
/* It's matching the tag. Add it to be logged and
* update "flag_added" to add the packet once. */
p->flags |= PKT_HAS_TAG;
flag_added++;
}
break;
case DETECT_TAG_METRIC_BYTES:
if (iter->bytes > iter->td->count) {
/* tag expired */
if (prev != NULL) {
tde = iter;
prev->next = iter->next;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
continue;
} else {
tde = iter;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
tde_dst->header_entry = iter;
continue;
}
} else if (flag_added == 0) {
/* It's matching the tag. Add it to be logged and
* update "flag_added" to add the packet once. */
p->flags |= PKT_HAS_TAG;
flag_added++;
}
break;
case DETECT_TAG_METRIC_SECONDS:
/* last_ts handles this metric, but also a generic time based
* expiration to prevent dead sessions/hosts */
if (iter->last_ts.tv_sec - iter->first_ts.tv_sec > (int)iter->td->count) {
/* tag expired */
if (prev != NULL) {
tde = iter;
prev->next = iter->next;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
continue;
} else {
tde = iter;
iter = iter->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
tde_dst->header_entry = iter;
continue;
}
} else if (flag_added == 0) {
/* It's matching the tag. Add it to be logged and
* update "flag_added" to add the packet once. */
p->flags |= PKT_HAS_TAG;
flag_added++;
}
break;
}
}
prev = iter;
iter = iter->next;
}
}
SCMutexUnlock(&tag_ctx->lock);
}
/**
* \brief Removes the entries exceding the max timeout value
*
* \param tag_ctx Tag context
* \param ts the current time
*
*/
static void TagTimeoutRemove(DetectTagHostCtx *tag_ctx, struct timeval *tv)
{
HashListTableBucket *next = NULL;
HashListTableBucket *buck = NULL;
DetectTagDataEntry *tde = NULL;
DetectTagDataEntry *tmp = NULL;
DetectTagDataEntry *prev = NULL;
DetectTagDataEntryList *tdl = NULL;
buck = HashListTableGetListHead(tag_ctx->tag_hash_table_ipv4);
while (buck != NULL) {
/* get the next before we free "buck" */
next = HashListTableGetListNext(buck);
tdl = HashListTableGetListData(buck);
if (tdl != NULL && tdl->header_entry != NULL) {
tmp = tdl->header_entry;
prev = NULL;
while (tmp != NULL) {
if ((tv->tv_sec - tmp->last_ts.tv_sec) <= TAG_MAX_LAST_TIME_SEEN) {
prev = tmp;
tmp = tmp->next;
continue;
}
if (prev != NULL) {
prev->next = tmp->next;
tde = tmp;
tmp = tmp->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
} else {
tdl->header_entry = tmp->next;
tde = tmp;
tmp = tmp->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
}
}
}
buck = next;
}
buck = HashListTableGetListHead(tag_ctx->tag_hash_table_ipv6);
while (buck != NULL) {
/* get the next before we free "buck" */
next = HashListTableGetListNext(buck);
tdl = HashListTableGetListData(buck);
if (tdl != NULL && tdl->header_entry != NULL) {
tmp = tdl->header_entry;
prev = NULL;
while (tmp != NULL) {
if ((tv->tv_sec - tmp->last_ts.tv_sec) <= TAG_MAX_LAST_TIME_SEEN) {
prev = tmp;
tmp = tmp->next;
continue;
}
if (prev != NULL) {
prev->next = tmp->next;
tde = tmp;
tmp = tmp->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
} else {
tdl->header_entry = tmp->next;
tde = tmp;
tmp = tmp->next;
SCFree(tde);
SC_ATOMIC_SUB(num_tags, 1);
}
}
}
buck = next;
}
return;
}