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-flowbits.c

1178 lines
38 KiB
C

/* Copyright (C) 2007-2022 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 <victor@inliniac.net>
* \author Breno Silva <breno.silva@gmail.com>
*
* Implements the flowbits keyword
*/
#include "suricata-common.h"
#include "decode.h"
#include "action-globals.h"
#include "detect.h"
#include "threads.h"
#include "flow.h"
#include "flow-bit.h"
#include "flow-util.h"
#include "detect-flowbits.h"
#include "util-spm.h"
#include "app-layer-parser.h"
#include "detect-parse.h"
#include "detect-engine.h"
#include "detect-engine-mpm.h"
#include "detect-engine-state.h"
#include "detect-engine-build.h"
#include "util-var-name.h"
#include "util-unittest.h"
#include "util-debug.h"
#include "util-conf.h"
#define PARSE_REGEX "^([a-z]+)(?:,\\s*(.*))?"
static DetectParseRegex parse_regex;
#define MAX_TOKENS 100
int DetectFlowbitMatch (DetectEngineThreadCtx *, Packet *,
const Signature *, const SigMatchCtx *);
static int DetectFlowbitSetup (DetectEngineCtx *, Signature *, const char *);
static int FlowbitOrAddData(DetectEngineCtx *, DetectFlowbitsData *, char *);
void DetectFlowbitFree (DetectEngineCtx *, void *);
#ifdef UNITTESTS
void FlowBitsRegisterTests(void);
#endif
void DetectFlowbitsRegister (void)
{
sigmatch_table[DETECT_FLOWBITS].name = "flowbits";
sigmatch_table[DETECT_FLOWBITS].desc = "operate on flow flag";
sigmatch_table[DETECT_FLOWBITS].url = "/rules/flow-keywords.html#flowbits";
sigmatch_table[DETECT_FLOWBITS].Match = DetectFlowbitMatch;
sigmatch_table[DETECT_FLOWBITS].Setup = DetectFlowbitSetup;
sigmatch_table[DETECT_FLOWBITS].Free = DetectFlowbitFree;
#ifdef UNITTESTS
sigmatch_table[DETECT_FLOWBITS].RegisterTests = FlowBitsRegisterTests;
#endif
/* this is compatible to ip-only signatures */
sigmatch_table[DETECT_FLOWBITS].flags |= SIGMATCH_IPONLY_COMPAT;
DetectSetupParseRegexes(PARSE_REGEX, &parse_regex);
}
static int FlowbitOrAddData(DetectEngineCtx *de_ctx, DetectFlowbitsData *cd, char *arrptr)
{
char *strarr[MAX_TOKENS];
char *token;
char *saveptr = NULL;
uint8_t i = 0;
while ((token = strtok_r(arrptr, "|", &saveptr))) {
// Check for leading/trailing spaces in the token
while(isspace((unsigned char)*token))
token++;
if (*token == 0)
goto next;
char *end = token + strlen(token) - 1;
while(end > token && isspace((unsigned char)*end))
*(end--) = '\0';
// Check for spaces in between the flowbit names
if (strchr(token, ' ') != NULL) {
SCLogError("Spaces are not allowed in flowbit names.");
return -1;
}
if (i == MAX_TOKENS) {
SCLogError("Number of flowbits exceeds "
"maximum allowed: %d.",
MAX_TOKENS);
return -1;
}
strarr[i++] = token;
next:
arrptr = NULL;
}
if (i == 0) {
SCLogError("No valid flowbits specified");
return -1;
}
cd->or_list_size = i;
cd->or_list = SCCalloc(cd->or_list_size, sizeof(uint32_t));
if (unlikely(cd->or_list == NULL))
return -1;
for (uint8_t j = 0; j < cd->or_list_size ; j++) {
var-names: reimplement var name handling Implement a new design for handling var name id's. The old logic was aware of detection engine versions and generally didn't work well for multi-tenancy cases. Other than memory leaks and crashes, logging of var names worked or failed based on which tenant was loaded last. This patch implements a new approach, where there is a global store of vars and their id's for the lifetime of the program. Overall Design: Base Store: "base" Used during keyword registration. Operates under lock. Base is shared between all detect engines, detect engine versions and tenants. Each variable name is ref counted. During the freeing of a detect engine / tenant, unregistration decreases the ref cnt. Base has both a string to id and a id to string hash table. String to id is used during parsing/registration. id to string during unregistration. Active Store Pointer (atomic) The "active" store atomic pointer points to the active lookup store. The call to `VarNameStoreActivate` will build a new lookup store and hot swap the pointer. Ensuring memory safety. During the hot swap, the pointer is replaced, so any new call to the lookup functions will automatically use the new store. This leaves the case of any lookup happening concurrently with the pointer swap. For this case we add the old store to a free list. It gets a timestamp before which it cannot be freed. Free List The free list contains old stores that are waiting to get removed. They contain a timestamp that is checked before they are freed. Bug: #6044. Bug: #6201.
2 years ago
cd->or_list[j] = VarNameStoreRegister(strarr[j], VAR_TYPE_FLOW_BIT);
de_ctx->max_fb_id = MAX(cd->or_list[j], de_ctx->max_fb_id);
}
return 1;
}
static int DetectFlowbitMatchToggle (Packet *p, const DetectFlowbitsData *fd)
{
if (p->flow == NULL)
return 0;
FlowBitToggle(p->flow,fd->idx);
return 1;
}
static int DetectFlowbitMatchUnset (Packet *p, const DetectFlowbitsData *fd)
{
if (p->flow == NULL)
return 0;
FlowBitUnset(p->flow,fd->idx);
return 1;
}
static int DetectFlowbitMatchSet (Packet *p, const DetectFlowbitsData *fd)
{
if (p->flow == NULL)
return 0;
FlowBitSet(p->flow,fd->idx);
return 1;
}
static int DetectFlowbitMatchIsset (Packet *p, const DetectFlowbitsData *fd)
{
if (p->flow == NULL)
return 0;
if (fd->or_list_size > 0) {
for (uint8_t i = 0; i < fd->or_list_size; i++) {
if (FlowBitIsset(p->flow, fd->or_list[i]) == 1)
return 1;
}
return 0;
}
return FlowBitIsset(p->flow,fd->idx);
}
static int DetectFlowbitMatchIsnotset (Packet *p, const DetectFlowbitsData *fd)
{
if (p->flow == NULL)
return 0;
if (fd->or_list_size > 0) {
for (uint8_t i = 0; i < fd->or_list_size; i++) {
if (FlowBitIsnotset(p->flow, fd->or_list[i]) == 1)
return 1;
}
return 0;
}
return FlowBitIsnotset(p->flow,fd->idx);
}
/*
* returns 0: no match
* 1: match
* -1: error
*/
int DetectFlowbitMatch (DetectEngineThreadCtx *det_ctx, Packet *p,
const Signature *s, const SigMatchCtx *ctx)
{
const DetectFlowbitsData *fd = (const DetectFlowbitsData *)ctx;
if (fd == NULL)
return 0;
switch (fd->cmd) {
case DETECT_FLOWBITS_CMD_ISSET:
return DetectFlowbitMatchIsset(p,fd);
case DETECT_FLOWBITS_CMD_ISNOTSET:
return DetectFlowbitMatchIsnotset(p,fd);
case DETECT_FLOWBITS_CMD_SET:
return DetectFlowbitMatchSet(p,fd);
case DETECT_FLOWBITS_CMD_UNSET:
return DetectFlowbitMatchUnset(p,fd);
case DETECT_FLOWBITS_CMD_TOGGLE:
return DetectFlowbitMatchToggle(p,fd);
default:
SCLogError("unknown cmd %" PRIu32 "", fd->cmd);
return 0;
}
return 0;
}
static int DetectFlowbitParse(const char *str, char *cmd, int cmd_len, char *name,
int name_len)
{
int rc;
size_t pcre2len;
pcre2_match_data *match = NULL;
int count = DetectParsePcreExec(&parse_regex, &match, str, 0, 0);
if (count != 2 && count != 3) {
SCLogError("\"%s\" is not a valid setting for flowbits.", str);
goto error;
}
pcre2len = cmd_len;
rc = pcre2_substring_copy_bynumber(match, 1, (PCRE2_UCHAR8 *)cmd, &pcre2len);
if (rc < 0) {
SCLogError("pcre2_substring_copy_bynumber failed");
goto error;
}
if (count == 3) {
pcre2len = name_len;
rc = pcre2_substring_copy_bynumber(match, 2, (PCRE2_UCHAR8 *)name, &pcre2len);
if (rc < 0) {
SCLogError("pcre2_substring_copy_bynumber failed");
goto error;
}
/* Trim trailing whitespace. */
while (strlen(name) > 0 && isblank(name[strlen(name) - 1])) {
name[strlen(name) - 1] = '\0';
}
if (strchr(name, '|') == NULL) {
/* Validate name, spaces are not allowed. */
for (size_t i = 0; i < strlen(name); i++) {
if (isblank(name[i])) {
SCLogError("spaces not allowed in flowbit names");
goto error;
}
}
}
}
pcre2_match_data_free(match);
return 1;
error:
if (match) {
pcre2_match_data_free(match);
}
return 0;
}
int DetectFlowbitSetup (DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
{
DetectFlowbitsData *cd = NULL;
uint8_t fb_cmd = 0;
char fb_cmd_str[16] = "", fb_name[256] = "";
if (!DetectFlowbitParse(rawstr, fb_cmd_str, sizeof(fb_cmd_str), fb_name,
sizeof(fb_name))) {
return -1;
}
if (strcmp(fb_cmd_str,"noalert") == 0) {
if (strlen(fb_name) != 0)
goto error;
s->action &= ~ACTION_ALERT;
return 0;
} else if (strcmp(fb_cmd_str,"isset") == 0) {
fb_cmd = DETECT_FLOWBITS_CMD_ISSET;
} else if (strcmp(fb_cmd_str,"isnotset") == 0) {
fb_cmd = DETECT_FLOWBITS_CMD_ISNOTSET;
} else if (strcmp(fb_cmd_str,"set") == 0) {
fb_cmd = DETECT_FLOWBITS_CMD_SET;
} else if (strcmp(fb_cmd_str,"unset") == 0) {
fb_cmd = DETECT_FLOWBITS_CMD_UNSET;
} else if (strcmp(fb_cmd_str,"toggle") == 0) {
fb_cmd = DETECT_FLOWBITS_CMD_TOGGLE;
} else {
SCLogError("ERROR: flowbits action \"%s\" is not supported.", fb_cmd_str);
goto error;
}
switch (fb_cmd) {
case DETECT_FLOWBITS_CMD_ISNOTSET:
case DETECT_FLOWBITS_CMD_ISSET:
case DETECT_FLOWBITS_CMD_SET:
case DETECT_FLOWBITS_CMD_UNSET:
case DETECT_FLOWBITS_CMD_TOGGLE:
default:
if (strlen(fb_name) == 0)
goto error;
break;
}
cd = SCCalloc(1, sizeof(DetectFlowbitsData));
if (unlikely(cd == NULL))
goto error;
if (strchr(fb_name, '|') != NULL) {
int retval = FlowbitOrAddData(de_ctx, cd, fb_name);
if (retval == -1) {
goto error;
}
cd->cmd = fb_cmd;
} else {
var-names: reimplement var name handling Implement a new design for handling var name id's. The old logic was aware of detection engine versions and generally didn't work well for multi-tenancy cases. Other than memory leaks and crashes, logging of var names worked or failed based on which tenant was loaded last. This patch implements a new approach, where there is a global store of vars and their id's for the lifetime of the program. Overall Design: Base Store: "base" Used during keyword registration. Operates under lock. Base is shared between all detect engines, detect engine versions and tenants. Each variable name is ref counted. During the freeing of a detect engine / tenant, unregistration decreases the ref cnt. Base has both a string to id and a id to string hash table. String to id is used during parsing/registration. id to string during unregistration. Active Store Pointer (atomic) The "active" store atomic pointer points to the active lookup store. The call to `VarNameStoreActivate` will build a new lookup store and hot swap the pointer. Ensuring memory safety. During the hot swap, the pointer is replaced, so any new call to the lookup functions will automatically use the new store. This leaves the case of any lookup happening concurrently with the pointer swap. For this case we add the old store to a free list. It gets a timestamp before which it cannot be freed. Free List The free list contains old stores that are waiting to get removed. They contain a timestamp that is checked before they are freed. Bug: #6044. Bug: #6201.
2 years ago
cd->idx = VarNameStoreRegister(fb_name, VAR_TYPE_FLOW_BIT);
de_ctx->max_fb_id = MAX(cd->idx, de_ctx->max_fb_id);
cd->cmd = fb_cmd;
cd->or_list_size = 0;
cd->or_list = NULL;
SCLogDebug("idx %" PRIu32 ", cmd %s, name %s",
cd->idx, fb_cmd_str, strlen(fb_name) ? fb_name : "(none)");
}
/* Okay so far so good, lets get this into a SigMatch
* and put it in the Signature. */
switch (fb_cmd) {
/* noalert can't happen here */
case DETECT_FLOWBITS_CMD_ISNOTSET:
case DETECT_FLOWBITS_CMD_ISSET:
/* checks, so packet list */
if (SigMatchAppendSMToList(de_ctx, s, DETECT_FLOWBITS, (SigMatchCtx *)cd,
DETECT_SM_LIST_MATCH) == NULL) {
goto error;
}
break;
case DETECT_FLOWBITS_CMD_SET:
case DETECT_FLOWBITS_CMD_UNSET:
case DETECT_FLOWBITS_CMD_TOGGLE:
/* modifiers, only run when entire sig has matched */
if (SigMatchAppendSMToList(de_ctx, s, DETECT_FLOWBITS, (SigMatchCtx *)cd,
DETECT_SM_LIST_POSTMATCH) == NULL) {
goto error;
}
break;
// suppress coverity warning as scan-build-7 warns w/o this.
// coverity[deadcode : FALSE]
default:
goto error;
}
return 0;
error:
if (cd != NULL)
DetectFlowbitFree(de_ctx, cd);
return -1;
}
void DetectFlowbitFree (DetectEngineCtx *de_ctx, void *ptr)
{
DetectFlowbitsData *fd = (DetectFlowbitsData *)ptr;
if (fd == NULL)
return;
var-names: reimplement var name handling Implement a new design for handling var name id's. The old logic was aware of detection engine versions and generally didn't work well for multi-tenancy cases. Other than memory leaks and crashes, logging of var names worked or failed based on which tenant was loaded last. This patch implements a new approach, where there is a global store of vars and their id's for the lifetime of the program. Overall Design: Base Store: "base" Used during keyword registration. Operates under lock. Base is shared between all detect engines, detect engine versions and tenants. Each variable name is ref counted. During the freeing of a detect engine / tenant, unregistration decreases the ref cnt. Base has both a string to id and a id to string hash table. String to id is used during parsing/registration. id to string during unregistration. Active Store Pointer (atomic) The "active" store atomic pointer points to the active lookup store. The call to `VarNameStoreActivate` will build a new lookup store and hot swap the pointer. Ensuring memory safety. During the hot swap, the pointer is replaced, so any new call to the lookup functions will automatically use the new store. This leaves the case of any lookup happening concurrently with the pointer swap. For this case we add the old store to a free list. It gets a timestamp before which it cannot be freed. Free List The free list contains old stores that are waiting to get removed. They contain a timestamp that is checked before they are freed. Bug: #6044. Bug: #6201.
2 years ago
VarNameStoreUnregister(fd->idx, VAR_TYPE_FLOW_BIT);
if (fd->or_list != NULL) {
for (uint8_t i = 0; i < fd->or_list_size; i++) {
VarNameStoreUnregister(fd->or_list[i], VAR_TYPE_FLOW_BIT);
}
SCFree(fd->or_list);
var-names: reimplement var name handling Implement a new design for handling var name id's. The old logic was aware of detection engine versions and generally didn't work well for multi-tenancy cases. Other than memory leaks and crashes, logging of var names worked or failed based on which tenant was loaded last. This patch implements a new approach, where there is a global store of vars and their id's for the lifetime of the program. Overall Design: Base Store: "base" Used during keyword registration. Operates under lock. Base is shared between all detect engines, detect engine versions and tenants. Each variable name is ref counted. During the freeing of a detect engine / tenant, unregistration decreases the ref cnt. Base has both a string to id and a id to string hash table. String to id is used during parsing/registration. id to string during unregistration. Active Store Pointer (atomic) The "active" store atomic pointer points to the active lookup store. The call to `VarNameStoreActivate` will build a new lookup store and hot swap the pointer. Ensuring memory safety. During the hot swap, the pointer is replaced, so any new call to the lookup functions will automatically use the new store. This leaves the case of any lookup happening concurrently with the pointer swap. For this case we add the old store to a free list. It gets a timestamp before which it cannot be freed. Free List The free list contains old stores that are waiting to get removed. They contain a timestamp that is checked before they are freed. Bug: #6044. Bug: #6201.
2 years ago
}
SCFree(fd);
}
struct FBAnalyze {
uint16_t cnts[DETECT_FLOWBITS_CMD_MAX];
uint16_t state_cnts[DETECT_FLOWBITS_CMD_MAX];
uint32_t *set_sids;
uint32_t set_sids_idx;
uint32_t set_sids_size;
uint32_t *isset_sids;
uint32_t isset_sids_idx;
uint32_t isset_sids_size;
uint32_t *isnotset_sids;
uint32_t isnotset_sids_idx;
uint32_t isnotset_sids_size;
uint32_t *unset_sids;
uint32_t unset_sids_idx;
uint32_t unset_sids_size;
uint32_t *toggle_sids;
uint32_t toggle_sids_idx;
uint32_t toggle_sids_size;
};
extern bool rule_engine_analysis_set;
static void DetectFlowbitsAnalyzeDump(const DetectEngineCtx *de_ctx,
struct FBAnalyze *array, uint32_t elements);
int DetectFlowbitsAnalyze(DetectEngineCtx *de_ctx)
{
const uint32_t max_fb_id = de_ctx->max_fb_id;
if (max_fb_id == 0)
return 0;
#define MAX_SIDS 8
uint32_t array_size = max_fb_id + 1;
struct FBAnalyze *array = SCCalloc(array_size, sizeof(struct FBAnalyze));
if (array == NULL) {
SCLogError("Unable to allocate flowbit analyze array");
return -1;
}
SCLogDebug("fb analyzer array size: %"PRIu64,
(uint64_t)(array_size * sizeof(struct FBAnalyze)));
/* fill flowbit array, updating counters per sig */
for (uint32_t i = 0; i < de_ctx->sig_array_len; i++) {
const Signature *s = de_ctx->sig_array[i];
/* see if the signature uses stateful matching */
detect: support multi buffer matching Multi buffer matching is implemented as a way for a rule to match on multiple buffers within the same transaction. Before this patch a rule like: dns.query; content:"example"; dns.query; content:".com"; would be equivalent to: dns.query; content:"example"; content:".com"; If a DNS query would request more than one name, e.g.: DNS: [example.net][something.com] Eeach would be inspected to have both patterns present. Otherwise, it would not be a match. So the rule above would not match, as neither example.net and somthing.com satisfy both conditions at the same time. This patch changes this behavior. Instead of the above, each time the sticky buffer is specified, it creates a separate detection unit. Each buffer is a "multi buffer" sticky buffer will now be evaluated against each "instance" of the sticky buffer. To continue with the above example: DNS: [example.net] <- matches 'dns.query; content:"example";' DNS: [something.com] <- matches 'dns.query; content:".com"' So this would now be a match. To make sure both patterns match in a single query string, the expression 'dns.query; content:"example"; content:".com";' still works for this. This patch doesn't yet enable the behavior for the keywords. That is done in a follow up patch. To be able to implement this the internal storage of parsed rules is changed. Until this patch and array of lists was used, where the index was the buffer id (e.g. http_uri, dns_query). Therefore there was only one list of matches per buffer id. As a side effect this array was always very sparsely populated as many buffers could not be mixed. This patch changes the internal representation. The new array is densely packed: dns.query; content:"1"; dns.query; bsize:1; content:"2"; [type: dns_query][list: content:"1";] [type: dns_query][list: bsize:1; content:"2";] The new scheme allows for multiple instances of the same buffer. These lists are then translated into multiple inspection engines during the final setup of the rule. Ticket: #5784.
2 years ago
bool has_state = (s->init_data->buffer_index != 0);
for (const SigMatch *sm = s->init_data->smlists[DETECT_SM_LIST_MATCH] ; sm != NULL; sm = sm->next) {
switch (sm->type) {
case DETECT_FLOWBITS:
{
/* figure out the flowbit action */
const DetectFlowbitsData *fb = (DetectFlowbitsData *)sm->ctx;
// Handle flowbit array in case of ORed flowbits
for (uint8_t k = 0; k < fb->or_list_size; k++) {
array[fb->or_list[k]].cnts[fb->cmd]++;
if (has_state)
array[fb->or_list[k]].state_cnts[fb->cmd]++;
if (fb->cmd == DETECT_FLOWBITS_CMD_ISSET) {
if (array[fb->or_list[k]].isset_sids_idx >= array[fb->or_list[k]].isset_sids_size) {
uint32_t old_size = array[fb->or_list[k]].isset_sids_size;
uint32_t new_size = MAX(2 * old_size, MAX_SIDS);
void *ptr = SCRealloc(array[fb->or_list[k]].isset_sids, new_size * sizeof(uint32_t));
if (ptr == NULL)
goto end;
array[fb->or_list[k]].isset_sids_size = new_size;
array[fb->or_list[k]].isset_sids = ptr;
}
array[fb->or_list[k]].isset_sids[array[fb->or_list[k]].isset_sids_idx] = s->num;
array[fb->or_list[k]].isset_sids_idx++;
} else if (fb->cmd == DETECT_FLOWBITS_CMD_ISNOTSET) {
if (array[fb->or_list[k]].isnotset_sids_idx >= array[fb->or_list[k]].isnotset_sids_size) {
uint32_t old_size = array[fb->or_list[k]].isnotset_sids_size;
uint32_t new_size = MAX(2 * old_size, MAX_SIDS);
void *ptr = SCRealloc(array[fb->or_list[k]].isnotset_sids, new_size * sizeof(uint32_t));
if (ptr == NULL)
goto end;
array[fb->or_list[k]].isnotset_sids_size = new_size;
array[fb->or_list[k]].isnotset_sids = ptr;
}
array[fb->or_list[k]].isnotset_sids[array[fb->or_list[k]].isnotset_sids_idx] = s->num;
array[fb->or_list[k]].isnotset_sids_idx++;
}
}
if (fb->or_list_size == 0) {
array[fb->idx].cnts[fb->cmd]++;
if (has_state)
array[fb->idx].state_cnts[fb->cmd]++;
if (fb->cmd == DETECT_FLOWBITS_CMD_ISSET) {
if (array[fb->idx].isset_sids_idx >= array[fb->idx].isset_sids_size) {
uint32_t old_size = array[fb->idx].isset_sids_size;
uint32_t new_size = MAX(2 * old_size, MAX_SIDS);
void *ptr = SCRealloc(array[fb->idx].isset_sids, new_size * sizeof(uint32_t));
if (ptr == NULL)
goto end;
array[fb->idx].isset_sids_size = new_size;
array[fb->idx].isset_sids = ptr;
}
array[fb->idx].isset_sids[array[fb->idx].isset_sids_idx] = s->num;
array[fb->idx].isset_sids_idx++;
} else if (fb->cmd == DETECT_FLOWBITS_CMD_ISNOTSET) {
if (array[fb->idx].isnotset_sids_idx >= array[fb->idx].isnotset_sids_size) {
uint32_t old_size = array[fb->idx].isnotset_sids_size;
uint32_t new_size = MAX(2 * old_size, MAX_SIDS);
void *ptr = SCRealloc(array[fb->idx].isnotset_sids, new_size * sizeof(uint32_t));
if (ptr == NULL)
goto end;
array[fb->idx].isnotset_sids_size = new_size;
array[fb->idx].isnotset_sids = ptr;
}
array[fb->idx].isnotset_sids[array[fb->idx].isnotset_sids_idx] = s->num;
array[fb->idx].isnotset_sids_idx++;
}
}
}
}
}
for (const SigMatch *sm = s->init_data->smlists[DETECT_SM_LIST_POSTMATCH] ; sm != NULL; sm = sm->next) {
switch (sm->type) {
case DETECT_FLOWBITS:
{
/* figure out what flowbit action */
const DetectFlowbitsData *fb = (DetectFlowbitsData *)sm->ctx;
array[fb->idx].cnts[fb->cmd]++;
if (has_state)
array[fb->idx].state_cnts[fb->cmd]++;
if (fb->cmd == DETECT_FLOWBITS_CMD_SET) {
if (array[fb->idx].set_sids_idx >= array[fb->idx].set_sids_size) {
uint32_t old_size = array[fb->idx].set_sids_size;
uint32_t new_size = MAX(2 * old_size, MAX_SIDS);
void *ptr = SCRealloc(array[fb->idx].set_sids, new_size * sizeof(uint32_t));
if (ptr == NULL)
goto end;
array[fb->idx].set_sids_size = new_size;
array[fb->idx].set_sids = ptr;
}
array[fb->idx].set_sids[array[fb->idx].set_sids_idx] = s->num;
array[fb->idx].set_sids_idx++;
}
else if (fb->cmd == DETECT_FLOWBITS_CMD_UNSET) {
if (array[fb->idx].unset_sids_idx >= array[fb->idx].unset_sids_size) {
uint32_t old_size = array[fb->idx].unset_sids_size;
uint32_t new_size = MAX(2 * old_size, MAX_SIDS);
void *ptr = SCRealloc(array[fb->idx].unset_sids, new_size * sizeof(uint32_t));
if (ptr == NULL)
goto end;
array[fb->idx].unset_sids_size = new_size;
array[fb->idx].unset_sids = ptr;
}
array[fb->idx].unset_sids[array[fb->idx].unset_sids_idx] = s->num;
array[fb->idx].unset_sids_idx++;
}
else if (fb->cmd == DETECT_FLOWBITS_CMD_TOGGLE) {
if (array[fb->idx].toggle_sids_idx >= array[fb->idx].toggle_sids_size) {
uint32_t old_size = array[fb->idx].toggle_sids_size;
uint32_t new_size = MAX(2 * old_size, MAX_SIDS);
void *ptr = SCRealloc(array[fb->idx].toggle_sids, new_size * sizeof(uint32_t));
if (ptr == NULL)
goto end;
array[fb->idx].toggle_sids_size = new_size;
array[fb->idx].toggle_sids = ptr;
}
array[fb->idx].toggle_sids[array[fb->idx].toggle_sids_idx] = s->num;
array[fb->idx].toggle_sids_idx++;
}
}
}
}
}
/* walk array to see if all bits make sense */
for (uint32_t i = 0; i < array_size; i++) {
var-names: reimplement var name handling Implement a new design for handling var name id's. The old logic was aware of detection engine versions and generally didn't work well for multi-tenancy cases. Other than memory leaks and crashes, logging of var names worked or failed based on which tenant was loaded last. This patch implements a new approach, where there is a global store of vars and their id's for the lifetime of the program. Overall Design: Base Store: "base" Used during keyword registration. Operates under lock. Base is shared between all detect engines, detect engine versions and tenants. Each variable name is ref counted. During the freeing of a detect engine / tenant, unregistration decreases the ref cnt. Base has both a string to id and a id to string hash table. String to id is used during parsing/registration. id to string during unregistration. Active Store Pointer (atomic) The "active" store atomic pointer points to the active lookup store. The call to `VarNameStoreActivate` will build a new lookup store and hot swap the pointer. Ensuring memory safety. During the hot swap, the pointer is replaced, so any new call to the lookup functions will automatically use the new store. This leaves the case of any lookup happening concurrently with the pointer swap. For this case we add the old store to a free list. It gets a timestamp before which it cannot be freed. Free List The free list contains old stores that are waiting to get removed. They contain a timestamp that is checked before they are freed. Bug: #6044. Bug: #6201.
2 years ago
const char *varname = VarNameStoreSetupLookup(i, VAR_TYPE_FLOW_BIT);
if (varname == NULL)
continue;
bool to_state = false;
if (array[i].cnts[DETECT_FLOWBITS_CMD_ISSET] &&
array[i].cnts[DETECT_FLOWBITS_CMD_TOGGLE] == 0 &&
array[i].cnts[DETECT_FLOWBITS_CMD_SET] == 0) {
const Signature *s = de_ctx->sig_array[array[i].isset_sids[0]];
SCLogWarning("flowbit '%s' is checked but not "
"set. Checked in %u and %u other sigs",
varname, s->id, array[i].isset_sids_idx - 1);
}
if (array[i].state_cnts[DETECT_FLOWBITS_CMD_ISSET] &&
array[i].state_cnts[DETECT_FLOWBITS_CMD_SET] == 0)
{
SCLogDebug("flowbit %s/%u: isset in state, set not in state", varname, i);
}
/* if signature depends on 'stateful' flowbits, then turn the
* sig into a stateful sig itself */
if (array[i].cnts[DETECT_FLOWBITS_CMD_ISSET] > 0 &&
array[i].state_cnts[DETECT_FLOWBITS_CMD_ISSET] == 0 &&
array[i].state_cnts[DETECT_FLOWBITS_CMD_SET])
{
SCLogDebug("flowbit %s/%u: isset not in state, set in state", varname, i);
to_state = true;
}
SCLogDebug("ALL flowbit %s/%u: sets %u toggles %u unsets %u isnotsets %u issets %u", varname, i,
array[i].cnts[DETECT_FLOWBITS_CMD_SET], array[i].cnts[DETECT_FLOWBITS_CMD_TOGGLE],
array[i].cnts[DETECT_FLOWBITS_CMD_UNSET], array[i].cnts[DETECT_FLOWBITS_CMD_ISNOTSET],
array[i].cnts[DETECT_FLOWBITS_CMD_ISSET]);
SCLogDebug("STATE flowbit %s/%u: sets %u toggles %u unsets %u isnotsets %u issets %u", varname, i,
array[i].state_cnts[DETECT_FLOWBITS_CMD_SET], array[i].state_cnts[DETECT_FLOWBITS_CMD_TOGGLE],
array[i].state_cnts[DETECT_FLOWBITS_CMD_UNSET], array[i].state_cnts[DETECT_FLOWBITS_CMD_ISNOTSET],
array[i].state_cnts[DETECT_FLOWBITS_CMD_ISSET]);
for (uint32_t x = 0; x < array[i].set_sids_idx; x++) {
SCLogDebug("SET flowbit %s/%u: SID %u", varname, i,
de_ctx->sig_array[array[i].set_sids[x]]->id);
}
for (uint32_t x = 0; x < array[i].isset_sids_idx; x++) {
Signature *s = de_ctx->sig_array[array[i].isset_sids[x]];
SCLogDebug("GET flowbit %s/%u: SID %u", varname, i, s->id);
if (to_state) {
s->init_data->init_flags |= SIG_FLAG_INIT_STATE_MATCH;
SCLogDebug("made SID %u stateful because it depends on "
"stateful rules that set flowbit %s", s->id, varname);
}
}
}
if (rule_engine_analysis_set) {
DetectFlowbitsAnalyzeDump(de_ctx, array, array_size);
}
end:
for (uint32_t i = 0; i < array_size; i++) {
SCFree(array[i].set_sids);
SCFree(array[i].unset_sids);
SCFree(array[i].isset_sids);
SCFree(array[i].isnotset_sids);
SCFree(array[i].toggle_sids);
}
SCFree(array);
return 0;
}
SCMutex g_flowbits_dump_write_m = SCMUTEX_INITIALIZER;
static void DetectFlowbitsAnalyzeDump(const DetectEngineCtx *de_ctx,
struct FBAnalyze *array, uint32_t elements)
{
JsonBuilder *js = jb_new_object();
if (js == NULL)
return;
jb_open_array(js, "flowbits");
for (uint32_t x = 0; x < elements; x++) {
var-names: reimplement var name handling Implement a new design for handling var name id's. The old logic was aware of detection engine versions and generally didn't work well for multi-tenancy cases. Other than memory leaks and crashes, logging of var names worked or failed based on which tenant was loaded last. This patch implements a new approach, where there is a global store of vars and their id's for the lifetime of the program. Overall Design: Base Store: "base" Used during keyword registration. Operates under lock. Base is shared between all detect engines, detect engine versions and tenants. Each variable name is ref counted. During the freeing of a detect engine / tenant, unregistration decreases the ref cnt. Base has both a string to id and a id to string hash table. String to id is used during parsing/registration. id to string during unregistration. Active Store Pointer (atomic) The "active" store atomic pointer points to the active lookup store. The call to `VarNameStoreActivate` will build a new lookup store and hot swap the pointer. Ensuring memory safety. During the hot swap, the pointer is replaced, so any new call to the lookup functions will automatically use the new store. This leaves the case of any lookup happening concurrently with the pointer swap. For this case we add the old store to a free list. It gets a timestamp before which it cannot be freed. Free List The free list contains old stores that are waiting to get removed. They contain a timestamp that is checked before they are freed. Bug: #6044. Bug: #6201.
2 years ago
const char *varname = VarNameStoreSetupLookup(x, VAR_TYPE_FLOW_BIT);
if (varname == NULL)
continue;
const struct FBAnalyze *e = &array[x];
jb_start_object(js);
jb_set_string(js, "name", varname);
jb_set_uint(js, "internal_id", x);
jb_set_uint(js, "set_cnt", e->cnts[DETECT_FLOWBITS_CMD_SET]);
jb_set_uint(js, "unset_cnt", e->cnts[DETECT_FLOWBITS_CMD_UNSET]);
jb_set_uint(js, "toggle_cnt", e->cnts[DETECT_FLOWBITS_CMD_TOGGLE]);
jb_set_uint(js, "isset_cnt", e->cnts[DETECT_FLOWBITS_CMD_ISSET]);
jb_set_uint(js, "isnotset_cnt", e->cnts[DETECT_FLOWBITS_CMD_ISNOTSET]);
// sets
if (e->cnts[DETECT_FLOWBITS_CMD_SET]) {
jb_open_array(js, "sets");
for (uint32_t i = 0; i < e->set_sids_idx; i++) {
const Signature *s = de_ctx->sig_array[e->set_sids[i]];
jb_append_uint(js, s->id);
}
jb_close(js);
}
// gets
if (e->cnts[DETECT_FLOWBITS_CMD_ISSET]) {
jb_open_array(js, "isset");
for (uint32_t i = 0; i < e->isset_sids_idx; i++) {
const Signature *s = de_ctx->sig_array[e->isset_sids[i]];
jb_append_uint(js, s->id);
}
jb_close(js);
}
// isnotset
if (e->cnts[DETECT_FLOWBITS_CMD_ISNOTSET]) {
jb_open_array(js, "isnotset");
for (uint32_t i = 0; i < e->isnotset_sids_idx; i++) {
const Signature *s = de_ctx->sig_array[e->isnotset_sids[i]];
jb_append_uint(js, s->id);
}
jb_close(js);
}
// unset
if (e->cnts[DETECT_FLOWBITS_CMD_UNSET]) {
jb_open_array(js, "unset");
for (uint32_t i = 0; i < e->unset_sids_idx; i++) {
const Signature *s = de_ctx->sig_array[e->unset_sids[i]];
jb_append_uint(js, s->id);
}
jb_close(js);
}
// toggle
if (e->cnts[DETECT_FLOWBITS_CMD_TOGGLE]) {
jb_open_array(js, "toggle");
for (uint32_t i = 0; i < e->toggle_sids_idx; i++) {
const Signature *s = de_ctx->sig_array[e->toggle_sids[i]];
jb_append_uint(js, s->id);
}
jb_close(js);
}
jb_close(js);
}
jb_close(js); // array
jb_close(js); // object
const char *filename = "flowbits.json";
const char *log_dir = ConfigGetLogDirectory();
char log_path[PATH_MAX] = "";
snprintf(log_path, sizeof(log_path), "%s/%s", log_dir, filename);
SCMutexLock(&g_flowbits_dump_write_m);
FILE *fp = fopen(log_path, "w");
if (fp != NULL) {
fwrite(jb_ptr(js), jb_len(js), 1, fp);
fprintf(fp, "\n");
fclose(fp);
}
SCMutexUnlock(&g_flowbits_dump_write_m);
jb_free(js);
}
#ifdef UNITTESTS
static int FlowBitsTestParse01(void)
{
char command[16] = "", name[16] = "";
/* Single argument version. */
FAIL_IF(!DetectFlowbitParse("noalert", command, sizeof(command), name,
sizeof(name)));
FAIL_IF(strcmp(command, "noalert") != 0);
/* No leading or trailing spaces. */
FAIL_IF(!DetectFlowbitParse("set,flowbit", command, sizeof(command), name,
sizeof(name)));
FAIL_IF(strcmp(command, "set") != 0);
FAIL_IF(strcmp(name, "flowbit") != 0);
/* Leading space. */
FAIL_IF(!DetectFlowbitParse("set, flowbit", command, sizeof(command), name,
sizeof(name)));
FAIL_IF(strcmp(command, "set") != 0);
FAIL_IF(strcmp(name, "flowbit") != 0);
/* Trailing space. */
FAIL_IF(!DetectFlowbitParse("set,flowbit ", command, sizeof(command), name,
sizeof(name)));
FAIL_IF(strcmp(command, "set") != 0);
FAIL_IF(strcmp(name, "flowbit") != 0);
/* Leading and trailing space. */
FAIL_IF(!DetectFlowbitParse("set, flowbit ", command, sizeof(command), name,
sizeof(name)));
FAIL_IF(strcmp(command, "set") != 0);
FAIL_IF(strcmp(name, "flowbit") != 0);
/* Spaces are not allowed in the name. */
FAIL_IF(DetectFlowbitParse("set,namewith space", command, sizeof(command),
name, sizeof(name)));
PASS;
}
/**
* \test FlowBitsTestSig01 is a test for a valid noalert flowbits option
*
2 years ago
* \retval 1 on success
* \retval 0 on failure
*/
static int FlowBitsTestSig01(void)
{
Signature *s = NULL;
DetectEngineCtx *de_ctx = NULL;
de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"Noalert\"; flowbits:noalert,wrongusage; content:\"GET \"; sid:1;)");
FAIL_IF_NOT_NULL(s);
SigGroupBuild(de_ctx);
DetectEngineCtxFree(de_ctx);
PASS;
}
/**
* \test FlowBitsTestSig02 is a test for a valid isset,set,isnotset,unset,toggle flowbits options
*
2 years ago
* \retval 1 on success
* \retval 0 on failure
*/
static int FlowBitsTestSig02(void)
{
Signature *s = NULL;
ThreadVars th_v;
DetectEngineCtx *de_ctx = NULL;
memset(&th_v, 0, sizeof(th_v));
de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"isset rule need an option\"; flowbits:isset; content:\"GET \"; sid:1;)");
FAIL_IF_NOT_NULL(s);
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"isnotset rule need an option\"; flowbits:isnotset; content:\"GET \"; sid:2;)");
FAIL_IF_NOT_NULL(s);
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"set rule need an option\"; flowbits:set; content:\"GET \"; sid:3;)");
FAIL_IF_NOT_NULL(s);
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"unset rule need an option\"; flowbits:unset; content:\"GET \"; sid:4;)");
FAIL_IF_NOT_NULL(s);
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"toggle rule need an option\"; flowbits:toggle; content:\"GET \"; sid:5;)");
FAIL_IF_NOT_NULL(s);
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"!set is not an option\"; flowbits:!set,myerr; content:\"GET \"; sid:6;)");
FAIL_IF_NOT_NULL(s);
SigGroupBuild(de_ctx);
DetectEngineCtxFree(de_ctx);
PASS;
}
/**
* \test FlowBitsTestSig03 is a test for a invalid flowbits option
*
2 years ago
* \retval 1 on success
* \retval 0 on failure
*/
static int FlowBitsTestSig03(void)
{
Signature *s = NULL;
DetectEngineCtx *de_ctx = NULL;
de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"Unknown cmd\"; flowbits:wrongcmd; content:\"GET \"; sid:1;)");
FAIL_IF_NOT_NULL(s);
SigGroupBuild(de_ctx);
DetectEngineCtxFree(de_ctx);
PASS;
}
/**
* \test FlowBitsTestSig04 is a test check idx value
*
2 years ago
* \retval 1 on success
* \retval 0 on failure
*/
static int FlowBitsTestSig04(void)
{
Signature *s = NULL;
DetectEngineCtx *de_ctx = NULL;
int idx = 0;
de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"isset option\"; flowbits:isset,fbt; content:\"GET \"; sid:1;)");
FAIL_IF_NULL(s);
var-names: reimplement var name handling Implement a new design for handling var name id's. The old logic was aware of detection engine versions and generally didn't work well for multi-tenancy cases. Other than memory leaks and crashes, logging of var names worked or failed based on which tenant was loaded last. This patch implements a new approach, where there is a global store of vars and their id's for the lifetime of the program. Overall Design: Base Store: "base" Used during keyword registration. Operates under lock. Base is shared between all detect engines, detect engine versions and tenants. Each variable name is ref counted. During the freeing of a detect engine / tenant, unregistration decreases the ref cnt. Base has both a string to id and a id to string hash table. String to id is used during parsing/registration. id to string during unregistration. Active Store Pointer (atomic) The "active" store atomic pointer points to the active lookup store. The call to `VarNameStoreActivate` will build a new lookup store and hot swap the pointer. Ensuring memory safety. During the hot swap, the pointer is replaced, so any new call to the lookup functions will automatically use the new store. This leaves the case of any lookup happening concurrently with the pointer swap. For this case we add the old store to a free list. It gets a timestamp before which it cannot be freed. Free List The free list contains old stores that are waiting to get removed. They contain a timestamp that is checked before they are freed. Bug: #6044. Bug: #6201.
2 years ago
idx = VarNameStoreRegister("fbt", VAR_TYPE_FLOW_BIT);
FAIL_IF(idx == 0);
SigGroupBuild(de_ctx);
DetectEngineCtxFree(de_ctx);
PASS;
}
/**
* \test FlowBitsTestSig05 is a test check noalert flag
*
2 years ago
* \retval 1 on success
* \retval 0 on failure
*/
static int FlowBitsTestSig05(void)
{
Signature *s = NULL;
DetectEngineCtx *de_ctx = NULL;
de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"Noalert\"; flowbits:noalert; content:\"GET \"; sid:1;)");
FAIL_IF_NULL(s);
FAIL_IF((s->action & ACTION_ALERT) != 0);
SigGroupBuild(de_ctx);
DetectEngineCtxFree(de_ctx);
PASS;
}
/**
* \test FlowBitsTestSig06 is a test set flowbits option
*
2 years ago
* \retval 1 on success
* \retval 0 on failure
*/
static int FlowBitsTestSig06(void)
{
uint8_t *buf = (uint8_t *)
"GET /one/ HTTP/1.1\r\n"
"Host: one.example.org\r\n"
"\r\n";
uint16_t buflen = strlen((char *)buf);
Packet *p = PacketGetFromAlloc();
FAIL_IF_NULL(p);
Signature *s = NULL;
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
DetectEngineCtx *de_ctx = NULL;
Flow f;
GenericVar flowvar, *gv = NULL;
int result = 0;
uint32_t idx = 0;
memset(&th_v, 0, sizeof(th_v));
memset(&f, 0, sizeof(Flow));
memset(&flowvar, 0, sizeof(GenericVar));
FLOW_INITIALIZE(&f);
p->flow = &f;
p->flow->flowvar = &flowvar;
p->src.family = AF_INET;
p->dst.family = AF_INET;
p->payload = buf;
p->payload_len = buflen;
p->proto = IPPROTO_TCP;
p->flags |= PKT_HAS_FLOW;
p->flowflags |= FLOW_PKT_TOSERVER;
de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"Flowbit set\"; flowbits:set,myflow; sid:10;)");
FAIL_IF_NULL(s);
var-names: reimplement var name handling Implement a new design for handling var name id's. The old logic was aware of detection engine versions and generally didn't work well for multi-tenancy cases. Other than memory leaks and crashes, logging of var names worked or failed based on which tenant was loaded last. This patch implements a new approach, where there is a global store of vars and their id's for the lifetime of the program. Overall Design: Base Store: "base" Used during keyword registration. Operates under lock. Base is shared between all detect engines, detect engine versions and tenants. Each variable name is ref counted. During the freeing of a detect engine / tenant, unregistration decreases the ref cnt. Base has both a string to id and a id to string hash table. String to id is used during parsing/registration. id to string during unregistration. Active Store Pointer (atomic) The "active" store atomic pointer points to the active lookup store. The call to `VarNameStoreActivate` will build a new lookup store and hot swap the pointer. Ensuring memory safety. During the hot swap, the pointer is replaced, so any new call to the lookup functions will automatically use the new store. This leaves the case of any lookup happening concurrently with the pointer swap. For this case we add the old store to a free list. It gets a timestamp before which it cannot be freed. Free List The free list contains old stores that are waiting to get removed. They contain a timestamp that is checked before they are freed. Bug: #6044. Bug: #6201.
2 years ago
idx = VarNameStoreRegister("myflow", VAR_TYPE_FLOW_BIT);
SigGroupBuild(de_ctx);
DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
gv = p->flow->flowvar;
FAIL_IF_NULL(gv);
for ( ; gv != NULL; gv = gv->next) {
if (gv->type == DETECT_FLOWBITS && gv->idx == idx) {
result = 1;
}
}
FAIL_IF_NOT(result);
DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
DetectEngineCtxFree(de_ctx);
FLOW_DESTROY(&f);
SCFree(p);
PASS;
}
/**
* \test FlowBitsTestSig07 is a test unset flowbits option
*
2 years ago
* \retval 1 on success
* \retval 0 on failure
*/
static int FlowBitsTestSig07(void)
{
uint8_t *buf = (uint8_t *)
"GET /one/ HTTP/1.1\r\n"
"Host: one.example.org\r\n"
"\r\n";
uint16_t buflen = strlen((char *)buf);
Packet *p = PacketGetFromAlloc();
FAIL_IF_NULL(p);
Signature *s = NULL;
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
DetectEngineCtx *de_ctx = NULL;
Flow f;
GenericVar flowvar, *gv = NULL;
int result = 0;
uint32_t idx = 0;
memset(&th_v, 0, sizeof(th_v));
memset(&f, 0, sizeof(Flow));
memset(&flowvar, 0, sizeof(GenericVar));
FLOW_INITIALIZE(&f);
p->flow = &f;
p->flow->flowvar = &flowvar;
p->src.family = AF_INET;
p->dst.family = AF_INET;
p->payload = buf;
p->payload_len = buflen;
p->proto = IPPROTO_TCP;
de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"Flowbit set\"; flowbits:set,myflow2; sid:10;)");
FAIL_IF_NULL(s);
s = s->next = SigInit(de_ctx,"alert ip any any -> any any (msg:\"Flowbit unset\"; flowbits:unset,myflow2; sid:11;)");
FAIL_IF_NULL(s);
var-names: reimplement var name handling Implement a new design for handling var name id's. The old logic was aware of detection engine versions and generally didn't work well for multi-tenancy cases. Other than memory leaks and crashes, logging of var names worked or failed based on which tenant was loaded last. This patch implements a new approach, where there is a global store of vars and their id's for the lifetime of the program. Overall Design: Base Store: "base" Used during keyword registration. Operates under lock. Base is shared between all detect engines, detect engine versions and tenants. Each variable name is ref counted. During the freeing of a detect engine / tenant, unregistration decreases the ref cnt. Base has both a string to id and a id to string hash table. String to id is used during parsing/registration. id to string during unregistration. Active Store Pointer (atomic) The "active" store atomic pointer points to the active lookup store. The call to `VarNameStoreActivate` will build a new lookup store and hot swap the pointer. Ensuring memory safety. During the hot swap, the pointer is replaced, so any new call to the lookup functions will automatically use the new store. This leaves the case of any lookup happening concurrently with the pointer swap. For this case we add the old store to a free list. It gets a timestamp before which it cannot be freed. Free List The free list contains old stores that are waiting to get removed. They contain a timestamp that is checked before they are freed. Bug: #6044. Bug: #6201.
2 years ago
idx = VarNameStoreRegister("myflow", VAR_TYPE_FLOW_BIT);
SigGroupBuild(de_ctx);
DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
gv = p->flow->flowvar;
FAIL_IF_NULL(gv);
for ( ; gv != NULL; gv = gv->next) {
if (gv->type == DETECT_FLOWBITS && gv->idx == idx) {
result = 1;
}
}
FAIL_IF(result);
DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
DetectEngineCtxFree(de_ctx);
FLOW_DESTROY(&f);
SCFree(p);
PASS;
}
/**
2 years ago
* \test FlowBitsTestSig08 is a test toggle flowbits option
*
2 years ago
* \retval 1 on success
* \retval 0 on failure
*/
static int FlowBitsTestSig08(void)
{
uint8_t *buf = (uint8_t *)
"GET /one/ HTTP/1.1\r\n"
"Host: one.example.org\r\n"
"\r\n";
uint16_t buflen = strlen((char *)buf);
Packet *p = PacketGetFromAlloc();
if (unlikely(p == NULL))
return 0;
Signature *s = NULL;
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
DetectEngineCtx *de_ctx = NULL;
Flow f;
GenericVar flowvar, *gv = NULL;
int result = 0;
uint32_t idx = 0;
memset(&th_v, 0, sizeof(th_v));
memset(&f, 0, sizeof(Flow));
memset(&flowvar, 0, sizeof(GenericVar));
FLOW_INITIALIZE(&f);
p->flow = &f;
p->flow->flowvar = &flowvar;
p->src.family = AF_INET;
p->dst.family = AF_INET;
p->payload = buf;
p->payload_len = buflen;
p->proto = IPPROTO_TCP;
de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
s = de_ctx->sig_list = SigInit(de_ctx,"alert ip any any -> any any (msg:\"Flowbit set\"; flowbits:set,myflow2; sid:10;)");
FAIL_IF_NULL(s);
s = s->next = SigInit(de_ctx,"alert ip any any -> any any (msg:\"Flowbit unset\"; flowbits:toggle,myflow2; sid:11;)");
FAIL_IF_NULL(s);
var-names: reimplement var name handling Implement a new design for handling var name id's. The old logic was aware of detection engine versions and generally didn't work well for multi-tenancy cases. Other than memory leaks and crashes, logging of var names worked or failed based on which tenant was loaded last. This patch implements a new approach, where there is a global store of vars and their id's for the lifetime of the program. Overall Design: Base Store: "base" Used during keyword registration. Operates under lock. Base is shared between all detect engines, detect engine versions and tenants. Each variable name is ref counted. During the freeing of a detect engine / tenant, unregistration decreases the ref cnt. Base has both a string to id and a id to string hash table. String to id is used during parsing/registration. id to string during unregistration. Active Store Pointer (atomic) The "active" store atomic pointer points to the active lookup store. The call to `VarNameStoreActivate` will build a new lookup store and hot swap the pointer. Ensuring memory safety. During the hot swap, the pointer is replaced, so any new call to the lookup functions will automatically use the new store. This leaves the case of any lookup happening concurrently with the pointer swap. For this case we add the old store to a free list. It gets a timestamp before which it cannot be freed. Free List The free list contains old stores that are waiting to get removed. They contain a timestamp that is checked before they are freed. Bug: #6044. Bug: #6201.
2 years ago
idx = VarNameStoreRegister("myflow", VAR_TYPE_FLOW_BIT);
SigGroupBuild(de_ctx);
DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
gv = p->flow->flowvar;
FAIL_IF_NULL(gv);
for ( ; gv != NULL; gv = gv->next) {
if (gv->type == DETECT_FLOWBITS && gv->idx == idx) {
result = 1;
}
}
FAIL_IF(result);
DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
DetectEngineCtxFree(de_ctx);
FLOW_DESTROY(&f);
SCFree(p);
PASS;
}
/**
* \brief this function registers unit tests for FlowBits
*/
void FlowBitsRegisterTests(void)
{
UtRegisterTest("FlowBitsTestParse01", FlowBitsTestParse01);
UtRegisterTest("FlowBitsTestSig01", FlowBitsTestSig01);
UtRegisterTest("FlowBitsTestSig02", FlowBitsTestSig02);
UtRegisterTest("FlowBitsTestSig03", FlowBitsTestSig03);
UtRegisterTest("FlowBitsTestSig04", FlowBitsTestSig04);
UtRegisterTest("FlowBitsTestSig05", FlowBitsTestSig05);
UtRegisterTest("FlowBitsTestSig06", FlowBitsTestSig06);
UtRegisterTest("FlowBitsTestSig07", FlowBitsTestSig07);
UtRegisterTest("FlowBitsTestSig08", FlowBitsTestSig08);
}
#endif /* UNITTESTS */