mirror of https://github.com/OISF/suricata
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.
590 lines
16 KiB
C
590 lines
16 KiB
C
/* 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.
|
|
*/
|
|
|
|
/**
|
|
* \file
|
|
*
|
|
* \author Victor Julien <victor@inliniac.net>
|
|
*
|
|
* Pattern matcher utility Functions
|
|
*/
|
|
|
|
#include "suricata-common.h"
|
|
#include "util-mpm.h"
|
|
#include "util-debug.h"
|
|
|
|
/* include pattern matchers */
|
|
#include "util-mpm-ac.h"
|
|
#include "util-mpm-ac-bs.h"
|
|
#include "util-mpm-ac-ks.h"
|
|
#include "util-mpm-hs.h"
|
|
#include "util-hashlist.h"
|
|
|
|
#include "detect-engine.h"
|
|
#include "util-misc.h"
|
|
#include "conf.h"
|
|
#include "conf-yaml-loader.h"
|
|
#include "queue.h"
|
|
#include "util-unittest.h"
|
|
#include "util-memcpy.h"
|
|
#ifdef BUILD_HYPERSCAN
|
|
#include "hs.h"
|
|
#endif
|
|
|
|
MpmTableElmt mpm_table[MPM_TABLE_SIZE];
|
|
uint8_t mpm_default_matcher;
|
|
|
|
/**
|
|
* \brief Register a new Mpm Context.
|
|
*
|
|
* \param name A new profile to be registered to store this MpmCtx.
|
|
* \param sm_list sm_list for this name (might be variable with xforms)
|
|
* \param alproto app proto or ALPROTO_UNKNOWN if not for app-layer
|
|
*
|
|
* \retval id Return the id created for the new MpmCtx profile.
|
|
*/
|
|
int32_t MpmFactoryRegisterMpmCtxProfile(
|
|
DetectEngineCtx *de_ctx, const char *name, const int sm_list, const AppProto alproto)
|
|
{
|
|
/* the very first entry */
|
|
if (de_ctx->mpm_ctx_factory_container == NULL) {
|
|
de_ctx->mpm_ctx_factory_container = SCCalloc(1, sizeof(MpmCtxFactoryContainer));
|
|
if (de_ctx->mpm_ctx_factory_container == NULL) {
|
|
FatalError("Error allocating memory");
|
|
}
|
|
de_ctx->mpm_ctx_factory_container->max_id = ENGINE_SGH_MPM_FACTORY_CONTEXT_START_ID_RANGE;
|
|
}
|
|
|
|
MpmCtxFactoryItem *item = de_ctx->mpm_ctx_factory_container->items;
|
|
MpmCtxFactoryItem *pitem = NULL;
|
|
while (item) {
|
|
if (item->sm_list == sm_list && item->alproto == alproto && item->name != NULL &&
|
|
strcmp(item->name, name) == 0) {
|
|
return item->id;
|
|
}
|
|
pitem = item;
|
|
item = item->next;
|
|
}
|
|
|
|
MpmCtxFactoryItem *nitem = SCCalloc(1, sizeof(MpmCtxFactoryItem));
|
|
if (unlikely(nitem == NULL)) {
|
|
FatalError("Error allocating memory");
|
|
}
|
|
nitem->name = name;
|
|
nitem->sm_list = sm_list;
|
|
nitem->id = de_ctx->mpm_ctx_factory_container->max_id++;
|
|
nitem->alproto = alproto;
|
|
|
|
/* toserver */
|
|
nitem->mpm_ctx_ts = SCCalloc(1, sizeof(MpmCtx));
|
|
if (nitem->mpm_ctx_ts == NULL) {
|
|
FatalError("Error allocating memory");
|
|
}
|
|
nitem->mpm_ctx_ts->flags |= MPMCTX_FLAGS_GLOBAL;
|
|
|
|
/* toclient */
|
|
nitem->mpm_ctx_tc = SCCalloc(1, sizeof(MpmCtx));
|
|
if (nitem->mpm_ctx_tc == NULL) {
|
|
FatalError("Error allocating memory");
|
|
}
|
|
nitem->mpm_ctx_tc->flags |= MPMCTX_FLAGS_GLOBAL;
|
|
|
|
/* store the newly created item */
|
|
if (pitem == NULL)
|
|
de_ctx->mpm_ctx_factory_container->items = nitem;
|
|
else
|
|
pitem->next = nitem;
|
|
|
|
de_ctx->mpm_ctx_factory_container->no_of_items++;
|
|
return nitem->id;
|
|
}
|
|
|
|
int32_t MpmFactoryIsMpmCtxAvailable(const DetectEngineCtx *de_ctx, const MpmCtx *mpm_ctx)
|
|
{
|
|
if (mpm_ctx == NULL)
|
|
return 0;
|
|
|
|
if (de_ctx->mpm_ctx_factory_container == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
for (MpmCtxFactoryItem *i = de_ctx->mpm_ctx_factory_container->items; i != NULL; i = i->next) {
|
|
if (mpm_ctx == i->mpm_ctx_ts || mpm_ctx == i->mpm_ctx_tc) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
MpmCtx *MpmFactoryGetMpmCtxForProfile(const DetectEngineCtx *de_ctx, int32_t id, int direction)
|
|
{
|
|
if (id == MPM_CTX_FACTORY_UNIQUE_CONTEXT) {
|
|
MpmCtx *mpm_ctx = SCMalloc(sizeof(MpmCtx));
|
|
if (unlikely(mpm_ctx == NULL)) {
|
|
FatalError("Error allocating memory");
|
|
}
|
|
memset(mpm_ctx, 0, sizeof(MpmCtx));
|
|
return mpm_ctx;
|
|
} else if (id < -1) {
|
|
SCLogError("Invalid argument - %d\n", id);
|
|
return NULL;
|
|
} else if (id >= de_ctx->mpm_ctx_factory_container->max_id) {
|
|
/* this id does not exist */
|
|
return NULL;
|
|
} else {
|
|
for (MpmCtxFactoryItem *i = de_ctx->mpm_ctx_factory_container->items; i != NULL;
|
|
i = i->next) {
|
|
if (id == i->id) {
|
|
return (direction == 0) ? i->mpm_ctx_ts : i->mpm_ctx_tc;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void MpmFactoryReClaimMpmCtx(const DetectEngineCtx *de_ctx, MpmCtx *mpm_ctx)
|
|
{
|
|
if (mpm_ctx == NULL)
|
|
return;
|
|
|
|
if (!MpmFactoryIsMpmCtxAvailable(de_ctx, mpm_ctx)) {
|
|
if (mpm_ctx->mpm_type != MPM_NOTSET)
|
|
mpm_table[mpm_ctx->mpm_type].DestroyCtx(mpm_ctx);
|
|
SCFree(mpm_ctx);
|
|
}
|
|
}
|
|
|
|
void MpmFactoryDeRegisterAllMpmCtxProfiles(DetectEngineCtx *de_ctx)
|
|
{
|
|
if (de_ctx->mpm_ctx_factory_container == NULL)
|
|
return;
|
|
|
|
MpmCtxFactoryItem *item = de_ctx->mpm_ctx_factory_container->items;
|
|
while (item) {
|
|
if (item->mpm_ctx_ts != NULL) {
|
|
if (item->mpm_ctx_ts->mpm_type != MPM_NOTSET)
|
|
mpm_table[item->mpm_ctx_ts->mpm_type].DestroyCtx(item->mpm_ctx_ts);
|
|
SCFree(item->mpm_ctx_ts);
|
|
}
|
|
if (item->mpm_ctx_tc != NULL) {
|
|
if (item->mpm_ctx_tc->mpm_type != MPM_NOTSET)
|
|
mpm_table[item->mpm_ctx_tc->mpm_type].DestroyCtx(item->mpm_ctx_tc);
|
|
SCFree(item->mpm_ctx_tc);
|
|
}
|
|
|
|
MpmCtxFactoryItem *next = item->next;
|
|
SCFree(item);
|
|
item = next;
|
|
}
|
|
|
|
SCFree(de_ctx->mpm_ctx_factory_container);
|
|
de_ctx->mpm_ctx_factory_container = NULL;
|
|
}
|
|
|
|
void MpmInitThreadCtx(MpmThreadCtx *mpm_thread_ctx, uint16_t matcher)
|
|
{
|
|
mpm_table[matcher].InitThreadCtx(NULL, mpm_thread_ctx);
|
|
}
|
|
|
|
void MpmInitCtx(MpmCtx *mpm_ctx, uint8_t matcher)
|
|
{
|
|
mpm_ctx->mpm_type = matcher;
|
|
mpm_table[matcher].InitCtx(mpm_ctx);
|
|
}
|
|
|
|
/* MPM matcher to use by default, i.e. when "mpm-algo" is set to "auto".
|
|
* If Hyperscan is available, use it. Otherwise, use AC. */
|
|
#ifdef BUILD_HYPERSCAN
|
|
# define DEFAULT_MPM MPM_HS
|
|
# define DEFAULT_MPM_AC MPM_AC
|
|
#else
|
|
# define DEFAULT_MPM MPM_AC
|
|
#endif
|
|
|
|
void MpmTableSetup(void)
|
|
{
|
|
memset(mpm_table, 0, sizeof(mpm_table));
|
|
mpm_default_matcher = DEFAULT_MPM;
|
|
|
|
MpmACRegister();
|
|
MpmACBSRegister();
|
|
MpmACTileRegister();
|
|
#ifdef BUILD_HYPERSCAN
|
|
#ifdef HAVE_HS_VALID_PLATFORM
|
|
/* Enable runtime check for SSSE3. Do not use Hyperscan MPM matcher if
|
|
* check is not successful. */
|
|
if (hs_valid_platform() != HS_SUCCESS) {
|
|
SCLogInfo("SSSE3 support not detected, disabling Hyperscan for "
|
|
"MPM");
|
|
/* Fall back to best Aho-Corasick variant. */
|
|
mpm_default_matcher = DEFAULT_MPM_AC;
|
|
} else {
|
|
MpmHSRegister();
|
|
}
|
|
#else
|
|
MpmHSRegister();
|
|
#endif /* HAVE_HS_VALID_PLATFORM */
|
|
#endif /* BUILD_HYPERSCAN */
|
|
}
|
|
|
|
int MpmAddPatternCS(struct MpmCtx_ *mpm_ctx, uint8_t *pat, uint16_t patlen,
|
|
uint16_t offset, uint16_t depth,
|
|
uint32_t pid, SigIntId sid, uint8_t flags)
|
|
{
|
|
return mpm_table[mpm_ctx->mpm_type].AddPattern(mpm_ctx, pat, patlen,
|
|
offset, depth,
|
|
pid, sid, flags);
|
|
}
|
|
|
|
int MpmAddPatternCI(struct MpmCtx_ *mpm_ctx, uint8_t *pat, uint16_t patlen,
|
|
uint16_t offset, uint16_t depth,
|
|
uint32_t pid, SigIntId sid, uint8_t flags)
|
|
{
|
|
return mpm_table[mpm_ctx->mpm_type].AddPatternNocase(mpm_ctx, pat, patlen,
|
|
offset, depth,
|
|
pid, sid, flags);
|
|
}
|
|
|
|
|
|
/**
|
|
* \internal
|
|
* \brief Creates a hash of the pattern. We use it for the hashing process
|
|
* during the initial pattern insertion time, to cull duplicate sigs.
|
|
*
|
|
* \param pat Pointer to the pattern.
|
|
* \param patlen Pattern length.
|
|
*
|
|
* \retval hash A 32 bit unsigned hash.
|
|
*/
|
|
static inline uint32_t MpmInitHashRaw(uint8_t *pat, uint16_t patlen)
|
|
{
|
|
uint32_t hash = patlen * pat[0];
|
|
if (patlen > 1)
|
|
hash += pat[1];
|
|
|
|
return (hash % MPM_INIT_HASH_SIZE);
|
|
}
|
|
|
|
/**
|
|
* \internal
|
|
* \brief Looks up a pattern. We use it for the hashing process during the
|
|
* the initial pattern insertion time, to cull duplicate sigs.
|
|
*
|
|
* \param ctx Pointer to the AC ctx.
|
|
* \param pat Pointer to the pattern.
|
|
* \param patlen Pattern length.
|
|
* \param flags Flags. We don't need this.
|
|
*
|
|
* \retval hash A 32 bit unsigned hash.
|
|
*/
|
|
static inline MpmPattern *MpmInitHashLookup(MpmCtx *ctx,
|
|
uint8_t *pat, uint16_t patlen,
|
|
uint16_t offset, uint16_t depth,
|
|
uint8_t flags, uint32_t pid)
|
|
{
|
|
uint32_t hash = MpmInitHashRaw(pat, patlen);
|
|
|
|
if (ctx->init_hash == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
MpmPattern *t = ctx->init_hash[hash];
|
|
for ( ; t != NULL; t = t->next) {
|
|
if (!(flags & MPM_PATTERN_CTX_OWNS_ID)) {
|
|
if (t->id == pid)
|
|
return t;
|
|
} else {
|
|
if (t->len == patlen && t->offset == offset && t->depth == depth &&
|
|
memcmp(pat, t->original_pat, patlen) == 0 &&
|
|
t->flags == flags)
|
|
{
|
|
return t;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* \internal
|
|
* \brief Allocs a new pattern instance.
|
|
*
|
|
* \param mpm_ctx Pointer to the mpm context.
|
|
*
|
|
* \retval p Pointer to the newly created pattern.
|
|
*/
|
|
static inline MpmPattern *MpmAllocPattern(MpmCtx *mpm_ctx)
|
|
{
|
|
MpmPattern *p = SCMalloc(sizeof(MpmPattern));
|
|
if (unlikely(p == NULL)) {
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
memset(p, 0, sizeof(MpmPattern));
|
|
|
|
mpm_ctx->memory_cnt++;
|
|
mpm_ctx->memory_size += sizeof(MpmPattern);
|
|
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* \internal
|
|
* \brief Used to free MpmPattern instances.
|
|
*
|
|
* \param mpm_ctx Pointer to the mpm context.
|
|
* \param p Pointer to the MpmPattern instance to be freed.
|
|
*/
|
|
void MpmFreePattern(MpmCtx *mpm_ctx, MpmPattern *p)
|
|
{
|
|
if (p != NULL && p->cs != NULL && p->cs != p->ci) {
|
|
SCFree(p->cs);
|
|
mpm_ctx->memory_cnt--;
|
|
mpm_ctx->memory_size -= p->len;
|
|
}
|
|
|
|
if (p != NULL && p->ci != NULL) {
|
|
SCFree(p->ci);
|
|
mpm_ctx->memory_cnt--;
|
|
mpm_ctx->memory_size -= p->len;
|
|
}
|
|
|
|
if (p != NULL && p->original_pat != NULL) {
|
|
SCFree(p->original_pat);
|
|
mpm_ctx->memory_cnt--;
|
|
mpm_ctx->memory_size -= p->len;
|
|
}
|
|
|
|
if (p != NULL) {
|
|
SCFree(p);
|
|
mpm_ctx->memory_cnt--;
|
|
mpm_ctx->memory_size -= sizeof(MpmPattern);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static inline uint32_t MpmInitHash(MpmPattern *p)
|
|
{
|
|
uint32_t hash = p->len * p->original_pat[0];
|
|
if (p->len > 1)
|
|
hash += p->original_pat[1];
|
|
|
|
return (hash % MPM_INIT_HASH_SIZE);
|
|
}
|
|
|
|
static inline int MpmInitHashAdd(MpmCtx *ctx, MpmPattern *p)
|
|
{
|
|
uint32_t hash = MpmInitHash(p);
|
|
|
|
if (ctx->init_hash == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if (ctx->init_hash[hash] == NULL) {
|
|
ctx->init_hash[hash] = p;
|
|
return 0;
|
|
}
|
|
|
|
MpmPattern *tt = NULL;
|
|
MpmPattern *t = ctx->init_hash[hash];
|
|
|
|
/* get the list tail */
|
|
do {
|
|
tt = t;
|
|
t = t->next;
|
|
} while (t != NULL);
|
|
|
|
tt->next = p;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \internal
|
|
* \brief Add a pattern to the mpm-ac context.
|
|
*
|
|
* \param mpm_ctx Mpm context.
|
|
* \param pat Pointer to the pattern.
|
|
* \param patlen Length of the pattern.
|
|
* \param pid Pattern id
|
|
* \param sid Signature id (internal id).
|
|
* \param flags Pattern's MPM_PATTERN_* flags.
|
|
*
|
|
* \retval 0 On success.
|
|
* \retval -1 On failure.
|
|
*/
|
|
int MpmAddPattern(MpmCtx *mpm_ctx, uint8_t *pat, uint16_t patlen,
|
|
uint16_t offset, uint16_t depth, uint32_t pid,
|
|
SigIntId sid, uint8_t flags)
|
|
{
|
|
SCLogDebug("Adding pattern for ctx %p, patlen %"PRIu16" and pid %" PRIu32,
|
|
mpm_ctx, patlen, pid);
|
|
|
|
if (patlen == 0) {
|
|
SCLogWarning("pattern length 0");
|
|
return 0;
|
|
}
|
|
|
|
if (flags & MPM_PATTERN_CTX_OWNS_ID)
|
|
pid = UINT_MAX;
|
|
|
|
/* check if we have already inserted this pattern */
|
|
MpmPattern *p = MpmInitHashLookup(mpm_ctx, pat, patlen,
|
|
offset, depth, flags, pid);
|
|
if (p == NULL) {
|
|
SCLogDebug("Allocing new pattern");
|
|
|
|
/* p will never be NULL */
|
|
p = MpmAllocPattern(mpm_ctx);
|
|
|
|
p->len = patlen;
|
|
p->flags = flags;
|
|
p->offset = offset;
|
|
p->depth = depth;
|
|
if (flags & MPM_PATTERN_CTX_OWNS_ID)
|
|
p->id = mpm_ctx->max_pat_id++;
|
|
else
|
|
p->id = pid;
|
|
|
|
p->original_pat = SCMalloc(patlen);
|
|
if (p->original_pat == NULL)
|
|
goto error;
|
|
mpm_ctx->memory_cnt++;
|
|
mpm_ctx->memory_size += patlen;
|
|
memcpy(p->original_pat, pat, patlen);
|
|
|
|
p->ci = SCMalloc(patlen);
|
|
if (p->ci == NULL)
|
|
goto error;
|
|
mpm_ctx->memory_cnt++;
|
|
mpm_ctx->memory_size += patlen;
|
|
memcpy_tolower(p->ci, pat, patlen);
|
|
|
|
/* setup the case sensitive part of the pattern */
|
|
if (p->flags & MPM_PATTERN_FLAG_NOCASE) {
|
|
/* nocase means no difference between cs and ci */
|
|
p->cs = p->ci;
|
|
} else {
|
|
if (memcmp(p->ci, pat, p->len) == 0) {
|
|
/* no diff between cs and ci: pat is lowercase */
|
|
p->cs = p->ci;
|
|
} else {
|
|
p->cs = SCMalloc(patlen);
|
|
if (p->cs == NULL)
|
|
goto error;
|
|
mpm_ctx->memory_cnt++;
|
|
mpm_ctx->memory_size += patlen;
|
|
memcpy(p->cs, pat, patlen);
|
|
}
|
|
}
|
|
|
|
/* put in the pattern hash */
|
|
if (MpmInitHashAdd(mpm_ctx, p) != 0)
|
|
goto error;
|
|
|
|
mpm_ctx->pattern_cnt++;
|
|
|
|
if (!(mpm_ctx->flags & MPMCTX_FLAGS_NODEPTH)) {
|
|
if (depth) {
|
|
mpm_ctx->maxdepth = MAX(mpm_ctx->maxdepth, depth);
|
|
SCLogDebug("%p: depth %u max %u", mpm_ctx, depth, mpm_ctx->maxdepth);
|
|
} else {
|
|
mpm_ctx->flags |= MPMCTX_FLAGS_NODEPTH;
|
|
mpm_ctx->maxdepth = 0;
|
|
SCLogDebug("%p: alas, no depth for us", mpm_ctx);
|
|
}
|
|
}
|
|
|
|
if (mpm_ctx->maxlen < patlen)
|
|
mpm_ctx->maxlen = patlen;
|
|
|
|
if (mpm_ctx->minlen == 0) {
|
|
mpm_ctx->minlen = patlen;
|
|
} else {
|
|
if (mpm_ctx->minlen > patlen)
|
|
mpm_ctx->minlen = patlen;
|
|
}
|
|
|
|
/* we need the max pat id */
|
|
if (p->id > mpm_ctx->max_pat_id)
|
|
mpm_ctx->max_pat_id = p->id;
|
|
|
|
p->sids_size = 1;
|
|
p->sids = SCMalloc(p->sids_size * sizeof(SigIntId));
|
|
BUG_ON(p->sids == NULL);
|
|
p->sids[0] = sid;
|
|
} else {
|
|
/* we can be called multiple times for the same sid in the case
|
|
* of the 'single' modus. Here multiple rule groups share the
|
|
* same mpm ctx and might be adding the same pattern to the
|
|
* mpm_ctx */
|
|
int found = 0;
|
|
uint32_t x = 0;
|
|
for (x = 0; x < p->sids_size; x++) {
|
|
if (p->sids[x] == sid) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
SigIntId *sids = SCRealloc(p->sids, (sizeof(SigIntId) * (p->sids_size + 1)));
|
|
BUG_ON(sids == NULL);
|
|
p->sids = sids;
|
|
p->sids[p->sids_size] = sid;
|
|
p->sids_size++;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
MpmFreePattern(mpm_ctx, p);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/************************************Unittests*********************************/
|
|
|
|
#ifdef UNITTESTS
|
|
#endif /* UNITTESTS */
|
|
|
|
void MpmRegisterTests(void)
|
|
{
|
|
#ifdef UNITTESTS
|
|
uint16_t i;
|
|
|
|
for (i = 0; i < MPM_TABLE_SIZE; i++) {
|
|
if (i == MPM_NOTSET)
|
|
continue;
|
|
|
|
g_ut_modules++;
|
|
|
|
if (mpm_table[i].RegisterUnittests != NULL) {
|
|
g_ut_covered++;
|
|
mpm_table[i].RegisterUnittests();
|
|
} else {
|
|
if (coverage_unittests)
|
|
SCLogWarning("mpm module %s has no "
|
|
"unittest registration function.",
|
|
mpm_table[i].name);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
}
|