/* 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 * * 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 }