/* 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 */ #include "suricata-common.h" #include "suricata.h" #include "detect.h" #include "flow.h" #include "flow-private.h" #include "flow-util.h" #include "flow-worker.h" #include "conf.h" #include "conf-yaml-loader.h" #include "datasets.h" #include "app-layer-parser.h" #include "app-layer-events.h" #include "app-layer-htp.h" #include "detect-parse.h" #include "detect-engine-sigorder.h" #include "detect-engine-build.h" #include "detect-engine-siggroup.h" #include "detect-engine-address.h" #include "detect-engine-port.h" #include "detect-engine-prefilter.h" #include "detect-engine-mpm.h" #include "detect-engine-iponly.h" #include "detect-engine-tag.h" #include "detect-engine-frame.h" #include "detect-engine-file.h" #include "detect-engine.h" #include "detect-engine-state.h" #include "detect-engine-payload.h" #include "detect-fast-pattern.h" #include "detect-byte-extract.h" #include "detect-content.h" #include "detect-uricontent.h" #include "detect-tcphdr.h" #include "detect-engine-threshold.h" #include "detect-engine-content-inspection.h" #include "detect-engine-loader.h" #include "detect-engine-alert.h" #include "util-classification-config.h" #include "util-reference-config.h" #include "util-threshold-config.h" #include "util-error.h" #include "util-hash.h" #include "util-byte.h" #include "util-debug.h" #include "util-unittest.h" #include "util-action.h" #include "util-magic.h" #include "util-signal.h" #include "util-spm.h" #include "util-device-private.h" #include "util-var-name.h" #include "util-path.h" #include "util-profiling.h" #include "util-validate.h" #include "util-hash-string.h" #include "util-enum.h" #include "util-conf.h" #include "tm-threads.h" #include "runmodes.h" #include "reputation.h" #define DETECT_ENGINE_DEFAULT_INSPECTION_RECURSION_LIMIT 3000 static int DetectEngineCtxLoadConf(DetectEngineCtx *); static DetectEngineMasterCtx g_master_de_ctx = { SCMUTEX_INITIALIZER, 0, 99, NULL, NULL, TENANT_SELECTOR_UNKNOWN, NULL, NULL, 0}; static uint32_t TenantIdHash(HashTable *h, void *data, uint16_t data_len); static char TenantIdCompare(void *d1, uint16_t d1_len, void *d2, uint16_t d2_len); static void TenantIdFree(void *d); static uint32_t DetectEngineTenantGetIdFromLivedev(const void *ctx, const Packet *p); static uint32_t DetectEngineTenantGetIdFromVlanId(const void *ctx, const Packet *p); static uint32_t DetectEngineTenantGetIdFromPcap(const void *ctx, const Packet *p); static DetectEngineAppInspectionEngine *g_app_inspect_engines = NULL; static DetectEnginePktInspectionEngine *g_pkt_inspect_engines = NULL; static DetectEngineFrameInspectionEngine *g_frame_inspect_engines = NULL; // clang-format off // rule types documentation tag start: SignatureProperties const struct SignatureProperties signature_properties[SIG_TYPE_MAX] = { /* SIG_TYPE_NOT_SET */ { SIG_PROP_FLOW_ACTION_PACKET, }, /* SIG_TYPE_IPONLY */ { SIG_PROP_FLOW_ACTION_FLOW, }, /* SIG_TYPE_LIKE_IPONLY */ { SIG_PROP_FLOW_ACTION_FLOW, }, /* SIG_TYPE_PDONLY */ { SIG_PROP_FLOW_ACTION_FLOW, }, /* SIG_TYPE_DEONLY */ { SIG_PROP_FLOW_ACTION_PACKET, }, /* SIG_TYPE_PKT */ { SIG_PROP_FLOW_ACTION_PACKET, }, /* SIG_TYPE_PKT_STREAM */ { SIG_PROP_FLOW_ACTION_FLOW_IF_STATEFUL, }, /* SIG_TYPE_STREAM */ { SIG_PROP_FLOW_ACTION_FLOW_IF_STATEFUL, }, /* SIG_TYPE_APPLAYER */ { SIG_PROP_FLOW_ACTION_FLOW, }, /* SIG_TYPE_APP_TX */ { SIG_PROP_FLOW_ACTION_FLOW, }, }; // rule types documentation tag end: SignatureProperties // clang-format on const char *DetectTableToString(enum DetectTable table) { switch (table) { case DETECT_TABLE_NOT_SET: return "not_set"; case DETECT_TABLE_PACKET_PRE_FLOW: return "pre_flow"; case DETECT_TABLE_PACKET_PRE_STREAM: return "pre_stream"; case DETECT_TABLE_PACKET_FILTER: return "packet_filter"; case DETECT_TABLE_PACKET_TD: return "packet_td"; case DETECT_TABLE_APP_FILTER: return "app_filter"; case DETECT_TABLE_APP_TD: return "app_td"; default: return "unknown"; } } /** \brief register inspect engine at start up time * * \note errors are fatal */ void DetectPktInspectEngineRegister(const char *name, InspectionBufferGetPktDataPtr GetPktData, InspectionBufferPktInspectFunc Callback) { DetectBufferTypeRegister(name); const int sm_list = DetectBufferTypeGetByName(name); if (sm_list == -1) { FatalError("failed to register inspect engine %s", name); } if ((sm_list < DETECT_SM_LIST_MATCH) || (sm_list >= SHRT_MAX) || (Callback == NULL)) { SCLogError("Invalid arguments"); BUG_ON(1); } DetectEnginePktInspectionEngine *new_engine = SCCalloc(1, sizeof(*new_engine)); if (unlikely(new_engine == NULL)) { FatalError("failed to register inspect engine %s: %s", name, strerror(errno)); } new_engine->sm_list = (uint16_t)sm_list; new_engine->sm_list_base = (uint16_t)sm_list; new_engine->v1.Callback = Callback; new_engine->v1.GetData = GetPktData; if (g_pkt_inspect_engines == NULL) { g_pkt_inspect_engines = new_engine; } else { DetectEnginePktInspectionEngine *t = g_pkt_inspect_engines; while (t->next != NULL) { t = t->next; } t->next = new_engine; } } /** \brief register inspect engine at start up time * * \note errors are fatal */ static void AppLayerInspectEngineRegisterInternal(const char *name, AppProto alproto, uint32_t dir, int progress, InspectEngineFuncPtr Callback, InspectionBufferGetDataPtr GetData, InspectionSingleBufferGetDataPtr GetDataSingle, InspectionMultiBufferGetDataPtr GetMultiData) { BUG_ON(progress >= 48); DetectBufferTypeRegister(name); const int sm_list = DetectBufferTypeGetByName(name); if (sm_list == -1) { FatalError("failed to register inspect engine %s", name); } SCLogDebug("name %s id %d", name, sm_list); if ((alproto == ALPROTO_FAILED) || (!(dir == SIG_FLAG_TOSERVER || dir == SIG_FLAG_TOCLIENT)) || (sm_list < DETECT_SM_LIST_MATCH) || (sm_list >= SHRT_MAX) || (progress < 0 || progress >= SHRT_MAX) || (Callback == NULL)) { SCLogError("Invalid arguments"); BUG_ON(1); } else if (Callback == DetectEngineInspectBufferGeneric && GetData == NULL) { SCLogError("Invalid arguments: must register " "GetData with DetectEngineInspectBufferGeneric"); BUG_ON(1); } else if (Callback == DetectEngineInspectBufferSingle && GetDataSingle == NULL) { SCLogError("Invalid arguments: must register " "GetData with DetectEngineInspectBufferGeneric"); BUG_ON(1); } else if (Callback == DetectEngineInspectMultiBufferGeneric && GetMultiData == NULL) { SCLogError("Invalid arguments: must register " "GetData with DetectEngineInspectMultiBufferGeneric"); BUG_ON(1); } uint8_t direction; if (dir == SIG_FLAG_TOSERVER) { direction = 0; } else { direction = 1; } // every DNS or HTTP2 can be accessed from DOH2 if (alproto == ALPROTO_HTTP2 || alproto == ALPROTO_DNS) { AppLayerInspectEngineRegisterInternal( name, ALPROTO_DOH2, dir, progress, Callback, GetData, GetDataSingle, GetMultiData); } DetectEngineAppInspectionEngine *new_engine = SCCalloc(1, sizeof(DetectEngineAppInspectionEngine)); if (unlikely(new_engine == NULL)) { exit(EXIT_FAILURE); } new_engine->alproto = alproto; new_engine->dir = direction; new_engine->sm_list = (uint16_t)sm_list; new_engine->sm_list_base = (uint16_t)sm_list; new_engine->progress = (int16_t)progress; new_engine->v2.Callback = Callback; if (Callback == DetectEngineInspectBufferGeneric) { new_engine->v2.GetData = GetData; } else if (Callback == DetectEngineInspectBufferSingle) { new_engine->v2.GetDataSingle = GetDataSingle; } else if (Callback == DetectEngineInspectMultiBufferGeneric) { new_engine->v2.GetMultiData = GetMultiData; } if (g_app_inspect_engines == NULL) { g_app_inspect_engines = new_engine; } else { DetectEngineAppInspectionEngine *t = g_app_inspect_engines; while (t->next != NULL) { t = t->next; } t->next = new_engine; } } void DetectAppLayerInspectEngineRegister(const char *name, AppProto alproto, uint32_t dir, int progress, InspectEngineFuncPtr Callback, InspectionBufferGetDataPtr GetData) { /* before adding, check that we don't add a duplicate entry, which will * propegate all the way into the packet runtime if allowed. */ DetectEngineAppInspectionEngine *t = g_app_inspect_engines; while (t != NULL) { const uint32_t t_direction = t->dir == 0 ? SIG_FLAG_TOSERVER : SIG_FLAG_TOCLIENT; const int sm_list = DetectBufferTypeGetByName(name); if (t->sm_list == sm_list && t->alproto == alproto && t_direction == dir && t->progress == progress && t->v2.Callback == Callback && t->v2.GetData == GetData) { DEBUG_VALIDATE_BUG_ON(1); return; } t = t->next; } AppLayerInspectEngineRegisterInternal( name, alproto, dir, progress, Callback, GetData, NULL, NULL); } void DetectAppLayerInspectEngineRegisterSingle(const char *name, AppProto alproto, uint32_t dir, int progress, InspectEngineFuncPtr Callback, InspectionSingleBufferGetDataPtr GetData) { /* before adding, check that we don't add a duplicate entry, which will * propegate all the way into the packet runtime if allowed. */ DetectEngineAppInspectionEngine *t = g_app_inspect_engines; while (t != NULL) { const uint32_t t_direction = t->dir == 0 ? SIG_FLAG_TOSERVER : SIG_FLAG_TOCLIENT; const int sm_list = DetectBufferTypeGetByName(name); if (t->sm_list == sm_list && t->alproto == alproto && t_direction == dir && t->progress == progress && t->v2.Callback == Callback && t->v2.GetDataSingle == GetData) { DEBUG_VALIDATE_BUG_ON(1); return; } t = t->next; } AppLayerInspectEngineRegisterInternal( name, alproto, dir, progress, Callback, NULL, GetData, NULL); } /* copy an inspect engine with transforms to a new list id. */ static void DetectAppLayerInspectEngineCopy( DetectEngineCtx *de_ctx, int sm_list, int new_list, const DetectEngineTransforms *transforms) { const DetectEngineAppInspectionEngine *t = g_app_inspect_engines; while (t) { if (t->sm_list == sm_list) { DetectEngineAppInspectionEngine *new_engine = SCCalloc(1, sizeof(DetectEngineAppInspectionEngine)); if (unlikely(new_engine == NULL)) { exit(EXIT_FAILURE); } new_engine->alproto = t->alproto; new_engine->dir = t->dir; DEBUG_VALIDATE_BUG_ON(new_list < 0 || new_list > UINT16_MAX); new_engine->sm_list = (uint16_t)new_list; /* use new list id */ DEBUG_VALIDATE_BUG_ON(sm_list < 0 || sm_list > UINT16_MAX); new_engine->sm_list_base = (uint16_t)sm_list; new_engine->progress = t->progress; new_engine->v2 = t->v2; new_engine->v2.transforms = transforms; /* assign transforms */ if (de_ctx->app_inspect_engines == NULL) { de_ctx->app_inspect_engines = new_engine; } else { DetectEngineAppInspectionEngine *list = de_ctx->app_inspect_engines; while (list->next != NULL) { list = list->next; } list->next = new_engine; } } t = t->next; } } /* copy inspect engines from global registrations to de_ctx list */ static void DetectAppLayerInspectEngineCopyListToDetectCtx(DetectEngineCtx *de_ctx) { const DetectEngineAppInspectionEngine *t = g_app_inspect_engines; DetectEngineAppInspectionEngine *list = de_ctx->app_inspect_engines; while (t) { DetectEngineAppInspectionEngine *new_engine = SCCalloc(1, sizeof(DetectEngineAppInspectionEngine)); if (unlikely(new_engine == NULL)) { exit(EXIT_FAILURE); } new_engine->alproto = t->alproto; new_engine->dir = t->dir; new_engine->sm_list = t->sm_list; new_engine->sm_list_base = t->sm_list; new_engine->progress = t->progress; new_engine->v2 = t->v2; if (list == NULL) { de_ctx->app_inspect_engines = new_engine; } else { list->next = new_engine; } list = new_engine; t = t->next; } } /* copy an inspect engine with transforms to a new list id. */ static void DetectPktInspectEngineCopy( DetectEngineCtx *de_ctx, int sm_list, int new_list, const DetectEngineTransforms *transforms) { const DetectEnginePktInspectionEngine *t = g_pkt_inspect_engines; while (t) { if (t->sm_list == sm_list) { DetectEnginePktInspectionEngine *new_engine = SCCalloc(1, sizeof(DetectEnginePktInspectionEngine)); if (unlikely(new_engine == NULL)) { exit(EXIT_FAILURE); } DEBUG_VALIDATE_BUG_ON(new_list < 0 || new_list > UINT16_MAX); new_engine->sm_list = (uint16_t)new_list; /* use new list id */ DEBUG_VALIDATE_BUG_ON(sm_list < 0 || sm_list > UINT16_MAX); new_engine->sm_list_base = (uint16_t)sm_list; new_engine->v1 = t->v1; new_engine->v1.transforms = transforms; /* assign transforms */ if (de_ctx->pkt_inspect_engines == NULL) { de_ctx->pkt_inspect_engines = new_engine; } else { DetectEnginePktInspectionEngine *list = de_ctx->pkt_inspect_engines; while (list->next != NULL) { list = list->next; } list->next = new_engine; } } t = t->next; } } /* copy inspect engines from global registrations to de_ctx list */ static void DetectPktInspectEngineCopyListToDetectCtx(DetectEngineCtx *de_ctx) { const DetectEnginePktInspectionEngine *t = g_pkt_inspect_engines; while (t) { SCLogDebug("engine %p", t); DetectEnginePktInspectionEngine *new_engine = SCCalloc(1, sizeof(DetectEnginePktInspectionEngine)); if (unlikely(new_engine == NULL)) { exit(EXIT_FAILURE); } new_engine->sm_list = t->sm_list; new_engine->sm_list_base = t->sm_list; new_engine->v1 = t->v1; if (de_ctx->pkt_inspect_engines == NULL) { de_ctx->pkt_inspect_engines = new_engine; } else { DetectEnginePktInspectionEngine *list = de_ctx->pkt_inspect_engines; while (list->next != NULL) { list = list->next; } list->next = new_engine; } t = t->next; } } /** \brief register inspect engine at start up time * * \note errors are fatal */ void DetectEngineFrameInspectEngineRegister(DetectEngineCtx *de_ctx, const char *name, int dir, InspectionBufferFrameInspectFunc Callback, AppProto alproto, uint8_t type) { const int sm_list = DetectEngineBufferTypeRegister(de_ctx, name); if (sm_list < 0) { FatalError("failed to register inspect engine %s", name); } if ((sm_list < DETECT_SM_LIST_MATCH) || (sm_list >= SHRT_MAX) || (Callback == NULL)) { SCLogError("Invalid arguments"); BUG_ON(1); } uint8_t direction; if (dir == SIG_FLAG_TOSERVER) { direction = 0; } else { direction = 1; } DetectEngineFrameInspectionEngine *new_engine = SCCalloc(1, sizeof(*new_engine)); if (unlikely(new_engine == NULL)) { FatalError("failed to register inspect engine %s: %s", name, strerror(errno)); } new_engine->sm_list = (uint16_t)sm_list; new_engine->sm_list_base = (uint16_t)sm_list; new_engine->dir = direction; new_engine->v1.Callback = Callback; new_engine->alproto = alproto; new_engine->type = type; if (de_ctx->frame_inspect_engines == NULL) { de_ctx->frame_inspect_engines = new_engine; } else { DetectEngineFrameInspectionEngine *list = de_ctx->frame_inspect_engines; while (list->next != NULL) { list = list->next; } list->next = new_engine; } } /* copy an inspect engine with transforms to a new list id. */ static void DetectFrameInspectEngineCopy(DetectEngineCtx *de_ctx, int sm_list, int new_list, const DetectEngineTransforms *transforms) { /* take the list from the detect engine as the buffers can be registered * dynamically. */ DetectEngineFrameInspectionEngine *t = de_ctx->frame_inspect_engines; while (t) { if (t->sm_list == sm_list) { DetectEngineFrameInspectionEngine *new_engine = SCCalloc(1, sizeof(DetectEngineFrameInspectionEngine)); if (unlikely(new_engine == NULL)) { exit(EXIT_FAILURE); } DEBUG_VALIDATE_BUG_ON(new_list < 0 || new_list > UINT16_MAX); new_engine->sm_list = (uint16_t)new_list; /* use new list id */ DEBUG_VALIDATE_BUG_ON(sm_list < 0 || sm_list > UINT16_MAX); new_engine->sm_list_base = (uint16_t)sm_list; new_engine->dir = t->dir; new_engine->alproto = t->alproto; new_engine->type = t->type; new_engine->v1 = t->v1; new_engine->v1.transforms = transforms; /* assign transforms */ /* append to the list */ DetectEngineFrameInspectionEngine *list = t; while (list->next != NULL) { list = list->next; } list->next = new_engine; } t = t->next; } } /* copy inspect engines from global registrations to de_ctx list */ static void DetectFrameInspectEngineCopyListToDetectCtx(DetectEngineCtx *de_ctx) { const DetectEngineFrameInspectionEngine *t = g_frame_inspect_engines; while (t) { SCLogDebug("engine %p", t); DetectEngineFrameInspectionEngine *new_engine = SCCalloc(1, sizeof(DetectEngineFrameInspectionEngine)); if (unlikely(new_engine == NULL)) { exit(EXIT_FAILURE); } new_engine->sm_list = t->sm_list; new_engine->sm_list_base = t->sm_list; new_engine->dir = t->dir; new_engine->alproto = t->alproto; new_engine->type = t->type; new_engine->v1 = t->v1; if (de_ctx->frame_inspect_engines == NULL) { de_ctx->frame_inspect_engines = new_engine; } else { DetectEngineFrameInspectionEngine *list = de_ctx->frame_inspect_engines; while (list->next != NULL) { list = list->next; } list->next = new_engine; } t = t->next; } } /** \internal * \brief append the stream inspection * * If stream inspection is MPM, then prepend it. */ static void AppendStreamInspectEngine( Signature *s, SigMatchData *stream, uint8_t direction, uint8_t id) { bool prepend = false; DetectEngineAppInspectionEngine *new_engine = SCCalloc(1, sizeof(DetectEngineAppInspectionEngine)); if (unlikely(new_engine == NULL)) { exit(EXIT_FAILURE); } if (s->init_data->mpm_sm_list == DETECT_SM_LIST_PMATCH) { SCLogDebug("stream is mpm"); prepend = true; new_engine->mpm = true; } new_engine->alproto = ALPROTO_UNKNOWN; /* all */ new_engine->dir = direction; new_engine->stream = true; new_engine->sm_list = DETECT_SM_LIST_PMATCH; new_engine->sm_list_base = DETECT_SM_LIST_PMATCH; new_engine->smd = stream; new_engine->v2.Callback = DetectEngineInspectStream; new_engine->progress = 0; /* append */ if (s->app_inspect == NULL) { s->app_inspect = new_engine; new_engine->id = DE_STATE_FLAG_BASE; /* id is used as flag in stateful detect */ } else if (prepend) { new_engine->next = s->app_inspect; s->app_inspect = new_engine; new_engine->id = id; } else { DetectEngineAppInspectionEngine *a = s->app_inspect; while (a->next != NULL) { a = a->next; } a->next = new_engine; new_engine->id = id; } SCLogDebug("sid %u: engine %p/%u added", s->id, new_engine, new_engine->id); } static void AppendFrameInspectEngine(DetectEngineCtx *de_ctx, const DetectEngineFrameInspectionEngine *u, Signature *s, SigMatchData *smd, const int mpm_list) { bool prepend = false; if (u->alproto == ALPROTO_UNKNOWN) { /* special case, inspect engine applies to all protocols */ } else if (s->alproto != ALPROTO_UNKNOWN && !AppProtoEquals(s->alproto, u->alproto)) return; if (s->flags & SIG_FLAG_TOSERVER && !(s->flags & SIG_FLAG_TOCLIENT)) { if (u->dir == 1) return; } else if (s->flags & SIG_FLAG_TOCLIENT && !(s->flags & SIG_FLAG_TOSERVER)) { if (u->dir == 0) return; } DetectEngineFrameInspectionEngine *new_engine = SCCalloc(1, sizeof(DetectEngineFrameInspectionEngine)); if (unlikely(new_engine == NULL)) { exit(EXIT_FAILURE); } if (mpm_list == u->sm_list) { SCLogDebug("%s is mpm", DetectEngineBufferTypeGetNameById(de_ctx, u->sm_list)); prepend = true; new_engine->mpm = true; } new_engine->type = u->type; new_engine->sm_list = u->sm_list; new_engine->sm_list_base = u->sm_list_base; new_engine->smd = smd; new_engine->v1 = u->v1; SCLogDebug("sm_list %d new_engine->v1 %p/%p", new_engine->sm_list, new_engine->v1.Callback, new_engine->v1.transforms); if (s->frame_inspect == NULL) { s->frame_inspect = new_engine; } else if (prepend) { new_engine->next = s->frame_inspect; s->frame_inspect = new_engine; } else { DetectEngineFrameInspectionEngine *a = s->frame_inspect; while (a->next != NULL) { a = a->next; } new_engine->next = a->next; a->next = new_engine; } } static void AppendPacketInspectEngine(DetectEngineCtx *de_ctx, const DetectEnginePktInspectionEngine *e, Signature *s, SigMatchData *smd, const int mpm_list) { bool prepend = false; DetectEnginePktInspectionEngine *new_engine = SCCalloc(1, sizeof(DetectEnginePktInspectionEngine)); if (unlikely(new_engine == NULL)) { exit(EXIT_FAILURE); } if (mpm_list == e->sm_list) { SCLogDebug("%s is mpm", DetectEngineBufferTypeGetNameById(de_ctx, e->sm_list)); prepend = true; new_engine->mpm = true; } new_engine->sm_list = e->sm_list; new_engine->sm_list_base = e->sm_list_base; new_engine->smd = smd; new_engine->v1 = e->v1; SCLogDebug("sm_list %d new_engine->v1 %p/%p/%p", new_engine->sm_list, new_engine->v1.Callback, new_engine->v1.GetData, new_engine->v1.transforms); if (s->pkt_inspect == NULL) { s->pkt_inspect = new_engine; } else if (prepend) { new_engine->next = s->pkt_inspect; s->pkt_inspect = new_engine; } else { DetectEnginePktInspectionEngine *a = s->pkt_inspect; while (a->next != NULL) { a = a->next; } new_engine->next = a->next; a->next = new_engine; } } static void AppendAppInspectEngine(DetectEngineCtx *de_ctx, const DetectEngineAppInspectionEngine *t, Signature *s, SigMatchData *smd, const int mpm_list, const int files_id, uint8_t *last_id, bool *head_is_mpm) { if (t->alproto == ALPROTO_UNKNOWN) { /* special case, inspect engine applies to all protocols */ } else if (s->alproto != ALPROTO_UNKNOWN && !AppProtoEquals(s->alproto, t->alproto)) return; if (s->flags & SIG_FLAG_TOSERVER && !(s->flags & SIG_FLAG_TOCLIENT)) { if (t->dir == 1) return; } else if (s->flags & SIG_FLAG_TOCLIENT && !(s->flags & SIG_FLAG_TOSERVER)) { if (t->dir == 0) return; } SCLogDebug("app engine: t %p t->id %u => alproto:%s files:%s", t, t->id, AppProtoToString(t->alproto), BOOL2STR(t->sm_list == files_id)); DetectEngineAppInspectionEngine *new_engine = SCCalloc(1, sizeof(DetectEngineAppInspectionEngine)); if (unlikely(new_engine == NULL)) { exit(EXIT_FAILURE); } bool prepend = false; if (mpm_list == t->sm_list) { SCLogDebug("%s is mpm", DetectEngineBufferTypeGetNameById(de_ctx, t->sm_list)); prepend = true; *head_is_mpm = true; new_engine->mpm = true; } new_engine->alproto = t->alproto; new_engine->dir = t->dir; new_engine->sm_list = t->sm_list; new_engine->sm_list_base = t->sm_list_base; new_engine->smd = smd; new_engine->match_on_null = smd ? DetectContentInspectionMatchOnAbsentBuffer(smd) : false; new_engine->progress = t->progress; new_engine->v2 = t->v2; SCLogDebug("sm_list %d new_engine->v2 %p/%p/%p", new_engine->sm_list, new_engine->v2.Callback, new_engine->v2.GetData, new_engine->v2.transforms); if (s->app_inspect == NULL) { s->app_inspect = new_engine; if (new_engine->sm_list == files_id) { new_engine->id = DE_STATE_ID_FILE_INSPECT; SCLogDebug("sid %u: engine %p/%u is FILE ENGINE", s->id, new_engine, new_engine->id); } else { new_engine->id = DE_STATE_FLAG_BASE; /* id is used as flag in stateful detect */ SCLogDebug("sid %u: engine %p/%u %s", s->id, new_engine, new_engine->id, DetectEngineBufferTypeGetNameById(de_ctx, new_engine->sm_list)); } /* prepend engine if forced or if our engine has a lower progress. */ } else if (prepend || (!(*head_is_mpm) && s->app_inspect->progress > new_engine->progress)) { new_engine->next = s->app_inspect; s->app_inspect = new_engine; if (new_engine->sm_list == files_id) { new_engine->id = DE_STATE_ID_FILE_INSPECT; SCLogDebug("sid %u: engine %p/%u is FILE ENGINE", s->id, new_engine, new_engine->id); } else { new_engine->id = ++(*last_id); SCLogDebug("sid %u: engine %p/%u %s", s->id, new_engine, new_engine->id, DetectEngineBufferTypeGetNameById(de_ctx, new_engine->sm_list)); } } else { DetectEngineAppInspectionEngine *a = s->app_inspect; while (a->next != NULL) { if (a->next && a->next->progress > new_engine->progress) { break; } a = a->next; } new_engine->next = a->next; a->next = new_engine; if (new_engine->sm_list == files_id) { new_engine->id = DE_STATE_ID_FILE_INSPECT; SCLogDebug("sid %u: engine %p/%u is FILE ENGINE", s->id, new_engine, new_engine->id); } else { new_engine->id = ++(*last_id); SCLogDebug("sid %u: engine %p/%u %s", s->id, new_engine, new_engine->id, DetectEngineBufferTypeGetNameById(de_ctx, new_engine->sm_list)); } } SCLogDebug("sid %u: engine %p/%u added", s->id, new_engine, new_engine->id); s->init_data->init_flags |= SIG_FLAG_INIT_STATE_MATCH; } /** * \note for the file inspect engine, the id DE_STATE_ID_FILE_INSPECT * is assigned. */ int DetectEngineAppInspectionEngine2Signature(DetectEngineCtx *de_ctx, Signature *s) { const int mpm_list = s->init_data->mpm_sm ? s->init_data->mpm_sm_list : -1; const int files_id = DetectBufferTypeGetByName("files"); bool head_is_mpm = false; uint8_t last_id = DE_STATE_FLAG_BASE; SCLogDebug("%u: setup app inspect engines. %u buffers", s->id, s->init_data->buffer_index); for (uint32_t x = 0; x < s->init_data->buffer_index; x++) { SigMatchData *smd = SigMatchList2DataArray(s->init_data->buffers[x].head); SCLogDebug("smd %p, id %u", smd, s->init_data->buffers[x].id); const DetectBufferType *b = DetectEngineBufferTypeGetById(de_ctx, s->init_data->buffers[x].id); if (b == NULL) FatalError("unknown buffer"); if (b->frame) { for (const DetectEngineFrameInspectionEngine *u = de_ctx->frame_inspect_engines; u != NULL; u = u->next) { if (u->sm_list == s->init_data->buffers[x].id) { AppendFrameInspectEngine(de_ctx, u, s, smd, mpm_list); } } } else if (b->packet) { /* set up pkt inspect engines */ for (const DetectEnginePktInspectionEngine *e = de_ctx->pkt_inspect_engines; e != NULL; e = e->next) { SCLogDebug("e %p sm_list %u", e, e->sm_list); if (e->sm_list == s->init_data->buffers[x].id) { AppendPacketInspectEngine(de_ctx, e, s, smd, mpm_list); } } } else { SCLogDebug("app %s id %u parent %u rule %u xforms %u", b->name, b->id, b->parent_id, s->init_data->buffers[x].id, b->transforms.cnt); for (const DetectEngineAppInspectionEngine *t = de_ctx->app_inspect_engines; t != NULL; t = t->next) { if (t->sm_list == s->init_data->buffers[x].id) { if (s->flags & SIG_FLAG_TXBOTHDIR) { // ambiguous keywords have app engines in both directions // so we skip the wrong direction for this buffer if (s->init_data->buffers[x].only_tc && t->dir == 0) { continue; } else if (s->init_data->buffers[x].only_ts && t->dir == 1) { continue; } } AppendAppInspectEngine( de_ctx, t, s, smd, mpm_list, files_id, &last_id, &head_is_mpm); } } } } /* handle rules that have an app-layer hook w/o bringing their own app inspect engine, * e.g. `alert dns:request_complete ... (sid:1;)` * * Here we use a minimal stub inspect engine in which we set: * - alproto * - progress * - sm_list/sm_list_base to get the mapping to the hook name * - dir based on sig direction * * The inspect engine has no callback and is thus considered a straight match. */ if (s->init_data->buffer_index == 0 && s->init_data->hook.type == SIGNATURE_HOOK_TYPE_APP) { uint8_t dir = 0; if ((s->flags & (SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT)) == (SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT)) abort(); if ((s->flags & (SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT)) == 0) abort(); if (s->flags & SIG_FLAG_TOSERVER) dir = 0; else if (s->flags & SIG_FLAG_TOCLIENT) dir = 1; DetectEngineAppInspectionEngine t = { .alproto = s->init_data->hook.t.app.alproto, .progress = (uint16_t)s->init_data->hook.t.app.app_progress, .sm_list = (uint16_t)s->init_data->hook.sm_list, .sm_list_base = (uint16_t)s->init_data->hook.sm_list, .dir = dir, }; AppendAppInspectEngine(de_ctx, &t, s, NULL, mpm_list, files_id, &last_id, &head_is_mpm); } if ((s->init_data->init_flags & SIG_FLAG_INIT_STATE_MATCH) && s->init_data->smlists[DETECT_SM_LIST_PMATCH] != NULL) { /* if engine is added multiple times, we pass it the same list */ SigMatchData *stream = SigMatchList2DataArray(s->init_data->smlists[DETECT_SM_LIST_PMATCH]); BUG_ON(stream == NULL); if (s->flags & SIG_FLAG_TOSERVER && !(s->flags & SIG_FLAG_TOCLIENT)) { AppendStreamInspectEngine(s, stream, 0, last_id + 1); } else if (s->flags & SIG_FLAG_TOCLIENT && !(s->flags & SIG_FLAG_TOSERVER)) { AppendStreamInspectEngine(s, stream, 1, last_id + 1); } else { AppendStreamInspectEngine(s, stream, 0, last_id + 1); AppendStreamInspectEngine(s, stream, 1, last_id + 1); } if (s->init_data->init_flags & SIG_FLAG_INIT_NEED_FLUSH) { SCLogDebug("set SIG_FLAG_FLUSH on %u", s->id); s->flags |= SIG_FLAG_FLUSH; } } #ifdef DEBUG const DetectEngineAppInspectionEngine *iter = s->app_inspect; while (iter) { SCLogDebug("%u: engine %s id %u progress %d %s", s->id, DetectEngineBufferTypeGetNameById(de_ctx, iter->sm_list), iter->id, iter->progress, iter->sm_list == mpm_list ? "MPM" : ""); iter = iter->next; } #endif return 0; } /** \brief free app inspect engines for a signature * * For lists that are registered multiple times, like http_header and * http_cookie, making the engines owner of the lists is complicated. * Multiple engines in a sig may be pointing to the same list. To * address this the 'free' code needs to be extra careful about not * double freeing, so it takes an approach to first fill an array * of the to-free pointers before freeing them. */ void DetectEngineAppInspectionEngineSignatureFree(DetectEngineCtx *de_ctx, Signature *s) { int engines = 0; DetectEngineAppInspectionEngine *ie = s->app_inspect; while (ie) { ie = ie->next; engines++; } DetectEnginePktInspectionEngine *e = s->pkt_inspect; while (e) { e = e->next; engines++; } DetectEngineFrameInspectionEngine *u = s->frame_inspect; while (u) { u = u->next; engines++; } if (engines == 0) { BUG_ON(s->pkt_inspect); BUG_ON(s->frame_inspect); return; } SigMatchData *bufs[engines]; memset(&bufs, 0, (engines * sizeof(SigMatchData *))); int arrays = 0; /* free engines and put smd in the array */ ie = s->app_inspect; while (ie) { DetectEngineAppInspectionEngine *next = ie->next; bool skip = false; for (int i = 0; i < arrays; i++) { if (bufs[i] == ie->smd) { skip = true; break; } } if (!skip) { bufs[arrays++] = ie->smd; } SCFree(ie); ie = next; } e = s->pkt_inspect; while (e) { DetectEnginePktInspectionEngine *next = e->next; bool skip = false; for (int i = 0; i < arrays; i++) { if (bufs[i] == e->smd) { skip = true; break; } } if (!skip) { bufs[arrays++] = e->smd; } SCFree(e); e = next; } u = s->frame_inspect; while (u) { DetectEngineFrameInspectionEngine *next = u->next; bool skip = false; for (int i = 0; i < arrays; i++) { if (bufs[i] == u->smd) { skip = true; break; } } if (!skip) { bufs[arrays++] = u->smd; } SCFree(u); u = next; } for (int i = 0; i < engines; i++) { if (bufs[i] == NULL) continue; SigMatchData *smd = bufs[i]; while (1) { if (sigmatch_table[smd->type].Free != NULL) { sigmatch_table[smd->type].Free(de_ctx, smd->ctx); } if (smd->is_last) break; smd++; } SCFree(bufs[i]); } } /* code for registering buffers */ #include "util-hash-lookup3.h" static HashListTable *g_buffer_type_hash = NULL; static int g_buffer_type_id = DETECT_SM_LIST_DYNAMIC_START; static int g_buffer_type_reg_closed = 0; int DetectBufferTypeMaxId(void) { return g_buffer_type_id; } static void DetectBufferAddTransformData(DetectBufferType *map) { for (int i = 0; i < map->transforms.cnt; i++) { const TransformData *t = &map->transforms.transforms[i]; if (sigmatch_table[t->transform].TransformId) { sigmatch_table[t->transform].TransformId( &map->xform_id[i].id_data, &map->xform_id[i].id_data_len, t->options); SCLogDebug("transform identity data: [%p] \"%s\" [%d]", map->xform_id[i].id_data, (char *)map->xform_id[i].id_data, map->xform_id[i].id_data_len); } } } static uint32_t DetectBufferTypeHashNameFunc(HashListTable *ht, void *data, uint16_t datalen) { const DetectBufferType *map = (DetectBufferType *)data; uint32_t hash = hashlittle_safe(map->name, strlen(map->name), 0); // Add the transform data // - Collect transform id and position // - Collect identity data, if any hash += hashlittle_safe((uint8_t *)&map->transforms.cnt, sizeof(map->transforms.cnt), 0); for (int i = 0; i < map->transforms.cnt; i++) { const TransformData *t = &map->transforms.transforms[i]; int tval = t->transform; hash += hashlittle_safe((uint8_t *)&tval, sizeof(tval), 0); if (map->xform_id[i].id_data) { hash += hashlittle_safe( &map->xform_id[i].id_data_len, sizeof(map->xform_id[i].id_data_len), 0); hash += hashlittle_safe(map->xform_id[i].id_data, map->xform_id[i].id_data_len, 0); } } hash %= ht->array_size; SCLogDebug("map->name %s, hash %d", map->name, hash); return hash; } static uint32_t DetectBufferTypeHashIdFunc(HashListTable *ht, void *data, uint16_t datalen) { const DetectBufferType *map = (DetectBufferType *)data; uint32_t hash = map->id; hash %= ht->array_size; return hash; } static char DetectBufferTypeCompareNameFunc(void *data1, uint16_t len1, void *data2, uint16_t len2) { DetectBufferType *map1 = (DetectBufferType *)data1; DetectBufferType *map2 = (DetectBufferType *)data2; char r = (strcmp(map1->name, map2->name) == 0); // Compare the transforms // the transform supports identity, that data will also be added. r &= map1->transforms.cnt == map2->transforms.cnt; if (r && map1->transforms.cnt) { for (int i = 0; i < map1->transforms.cnt; i++) { if (map1->transforms.transforms[i].transform != map2->transforms.transforms[i].transform) { r = 0; break; } SCLogDebug("%s: transform ids match; checking specialized data", map1->name); // Checks // - Both NULL: --> ok, continue // - One NULL: --> no match, break? // - identity data lengths match: --> ok, continue // - identity data matches: ok // Stop if only one is NULL if ((map1->xform_id[i].id_data == NULL) ^ (map2->xform_id[i].id_data == NULL)) { SCLogDebug("identity data: only one is null"); r = 0; break; } else if (map1->xform_id[i].id_data == NULL) { /* continue when both are null */ SCLogDebug("identity data: both null"); r = 1; continue; } else if (map1->xform_id[i].id_data_len != map2->xform_id[i].id_data_len) { // Stop when id data lengths aren't equal SCLogDebug("id data: unequal lengths"); r = 0; break; } // stop if the identity data is different r &= memcmp(map1->xform_id[i].id_data, map2->xform_id[i].id_data, map1->xform_id[i].id_data_len) == 0; if (r == 0) break; SCLogDebug("identity data: data matches"); } } return r; } static char DetectBufferTypeCompareIdFunc(void *data1, uint16_t len1, void *data2, uint16_t len2) { DetectBufferType *map1 = (DetectBufferType *)data1; DetectBufferType *map2 = (DetectBufferType *)data2; return map1->id == map2->id; } static void DetectBufferTypeFreeFunc(void *data) { DetectBufferType *map = (DetectBufferType *)data; if (map == NULL) { return; } /* Release transformation option memory, if any */ for (int i = 0; i < map->transforms.cnt; i++) { if (map->transforms.transforms[i].options == NULL) continue; if (sigmatch_table[map->transforms.transforms[i].transform].Free == NULL) { SCLogError("%s allocates transform option memory but has no free routine", sigmatch_table[map->transforms.transforms[i].transform].name); continue; } sigmatch_table[map->transforms.transforms[i].transform].Free(NULL, map->transforms.transforms[i].options); } SCFree(map); } static int DetectBufferTypeInit(void) { BUG_ON(g_buffer_type_hash); g_buffer_type_hash = HashListTableInit(256, DetectBufferTypeHashNameFunc, DetectBufferTypeCompareNameFunc, DetectBufferTypeFreeFunc); if (g_buffer_type_hash == NULL) return -1; return 0; } #if 0 static void DetectBufferTypeFree(void) { if (g_buffer_type_hash == NULL) return; HashListTableFree(g_buffer_type_hash); g_buffer_type_hash = NULL; } #endif static int DetectBufferTypeAdd(const char *string) { BUG_ON(string == NULL || strlen(string) >= 64); DetectBufferType *map = SCCalloc(1, sizeof(*map)); if (map == NULL) return -1; strlcpy(map->name, string, sizeof(map->name)); map->id = g_buffer_type_id++; BUG_ON(HashListTableAdd(g_buffer_type_hash, (void *)map, 0) != 0); SCLogDebug("buffer %s registered with id %d", map->name, map->id); return map->id; } static DetectBufferType *DetectBufferTypeLookupByName(const char *string) { DetectBufferType map; memset(&map, 0, sizeof(map)); strlcpy(map.name, string, sizeof(map.name)); DetectBufferType *res = HashListTableLookup(g_buffer_type_hash, &map, 0); return res; } int DetectBufferTypeRegister(const char *name) { BUG_ON(g_buffer_type_reg_closed); if (g_buffer_type_hash == NULL) DetectBufferTypeInit(); DetectBufferType *exists = DetectBufferTypeLookupByName(name); if (!exists) { return DetectBufferTypeAdd(name); } else { return exists->id; } } void DetectBufferTypeSupportsMultiInstance(const char *name) { BUG_ON(g_buffer_type_reg_closed); DetectBufferTypeRegister(name); DetectBufferType *exists = DetectBufferTypeLookupByName(name); BUG_ON(!exists); exists->multi_instance = true; SCLogDebug("%p %s -- %d supports multi instance", exists, name, exists->id); } void DetectBufferTypeSupportsFrames(const char *name) { BUG_ON(g_buffer_type_reg_closed); DetectBufferTypeRegister(name); DetectBufferType *exists = DetectBufferTypeLookupByName(name); BUG_ON(!exists); exists->frame = true; SCLogDebug("%p %s -- %d supports frame inspection", exists, name, exists->id); } void DetectBufferTypeSupportsPacket(const char *name) { BUG_ON(g_buffer_type_reg_closed); DetectBufferTypeRegister(name); DetectBufferType *exists = DetectBufferTypeLookupByName(name); BUG_ON(!exists); exists->packet = true; SCLogDebug("%p %s -- %d supports packet inspection", exists, name, exists->id); } void DetectBufferTypeSupportsMpm(const char *name) { BUG_ON(g_buffer_type_reg_closed); DetectBufferTypeRegister(name); DetectBufferType *exists = DetectBufferTypeLookupByName(name); BUG_ON(!exists); exists->mpm = true; SCLogDebug("%p %s -- %d supports mpm", exists, name, exists->id); } void DetectBufferTypeSupportsTransformations(const char *name) { BUG_ON(g_buffer_type_reg_closed); DetectBufferTypeRegister(name); DetectBufferType *exists = DetectBufferTypeLookupByName(name); BUG_ON(!exists); exists->supports_transforms = true; SCLogDebug("%p %s -- %d supports transformations", exists, name, exists->id); } int DetectBufferTypeGetByName(const char *name) { DetectBufferType *exists = DetectBufferTypeLookupByName(name); if (!exists) { return -1; } return exists->id; } static DetectBufferType *DetectEngineBufferTypeLookupByName( const DetectEngineCtx *de_ctx, const char *string) { DetectBufferType map; memset(&map, 0, sizeof(map)); strlcpy(map.name, string, sizeof(map.name)); DetectBufferType *res = HashListTableLookup(de_ctx->buffer_type_hash_name, &map, 0); return res; } const DetectBufferType *DetectEngineBufferTypeGetById(const DetectEngineCtx *de_ctx, const int id) { DetectBufferType lookup; memset(&lookup, 0, sizeof(lookup)); lookup.id = id; const DetectBufferType *res = HashListTableLookup(de_ctx->buffer_type_hash_id, (void *)&lookup, 0); return res; } const char *DetectEngineBufferTypeGetNameById(const DetectEngineCtx *de_ctx, const int id) { const DetectBufferType *res = DetectEngineBufferTypeGetById(de_ctx, id); return res ? res->name : NULL; } static int DetectEngineBufferTypeAdd(DetectEngineCtx *de_ctx, const char *string) { BUG_ON(string == NULL || strlen(string) >= 32); DetectBufferType *map = SCCalloc(1, sizeof(*map)); if (map == NULL) return -1; strlcpy(map->name, string, sizeof(map->name)); map->id = de_ctx->buffer_type_id++; BUG_ON(HashListTableAdd(de_ctx->buffer_type_hash_name, (void *)map, 0) != 0); BUG_ON(HashListTableAdd(de_ctx->buffer_type_hash_id, (void *)map, 0) != 0); SCLogDebug("buffer %s registered with id %d", map->name, map->id); return map->id; } int DetectEngineBufferTypeRegisterWithFrameEngines(DetectEngineCtx *de_ctx, const char *name, const int direction, const AppProto alproto, const uint8_t frame_type) { DetectBufferType *exists = DetectEngineBufferTypeLookupByName(de_ctx, name); if (exists) { return exists->id; } const int buffer_id = DetectEngineBufferTypeAdd(de_ctx, name); if (buffer_id < 0) { return -1; } /* TODO hack we need the map to get the name. Should we return the map at reg? */ const DetectBufferType *map = DetectEngineBufferTypeGetById(de_ctx, buffer_id); BUG_ON(!map); /* register MPM/inspect engines */ if (direction & SIG_FLAG_TOSERVER) { DetectEngineFrameMpmRegister(de_ctx, map->name, SIG_FLAG_TOSERVER, 2, PrefilterGenericMpmFrameRegister, alproto, frame_type); DetectEngineFrameInspectEngineRegister(de_ctx, map->name, SIG_FLAG_TOSERVER, DetectEngineInspectFrameBufferGeneric, alproto, frame_type); } if (direction & SIG_FLAG_TOCLIENT) { DetectEngineFrameMpmRegister(de_ctx, map->name, SIG_FLAG_TOCLIENT, 2, PrefilterGenericMpmFrameRegister, alproto, frame_type); DetectEngineFrameInspectEngineRegister(de_ctx, map->name, SIG_FLAG_TOCLIENT, DetectEngineInspectFrameBufferGeneric, alproto, frame_type); } return buffer_id; } int DetectEngineBufferTypeRegister(DetectEngineCtx *de_ctx, const char *name) { DetectBufferType *exists = DetectEngineBufferTypeLookupByName(de_ctx, name); if (!exists) { return DetectEngineBufferTypeAdd(de_ctx, name); } else { return exists->id; } } void DetectBufferTypeSetDescriptionByName(const char *name, const char *desc) { BUG_ON(desc == NULL || strlen(desc) >= 128); DetectBufferType *exists = DetectBufferTypeLookupByName(name); if (!exists) { return; } strlcpy(exists->description, desc, sizeof(exists->description)); } const char *DetectEngineBufferTypeGetDescriptionById(const DetectEngineCtx *de_ctx, const int id) { const DetectBufferType *exists = DetectEngineBufferTypeGetById(de_ctx, id); if (!exists) { return NULL; } return exists->description; } const char *DetectBufferTypeGetDescriptionByName(const char *name) { const DetectBufferType *exists = DetectBufferTypeLookupByName(name); if (!exists) { return NULL; } return exists->description; } void DetectEngineBufferTypeSupportsFrames(DetectEngineCtx *de_ctx, const char *name) { DetectBufferType *exists = DetectEngineBufferTypeLookupByName(de_ctx, name); BUG_ON(!exists); exists->frame = true; SCLogDebug("%p %s -- %d supports frame inspection", exists, name, exists->id); } void DetectEngineBufferTypeSupportsPacket(DetectEngineCtx *de_ctx, const char *name) { DetectBufferType *exists = DetectEngineBufferTypeLookupByName(de_ctx, name); BUG_ON(!exists); exists->packet = true; SCLogDebug("%p %s -- %d supports packet inspection", exists, name, exists->id); } void DetectEngineBufferTypeSupportsMpm(DetectEngineCtx *de_ctx, const char *name) { DetectBufferType *exists = DetectEngineBufferTypeLookupByName(de_ctx, name); BUG_ON(!exists); exists->mpm = true; SCLogDebug("%p %s -- %d supports mpm", exists, name, exists->id); } void DetectEngineBufferTypeSupportsTransformations(DetectEngineCtx *de_ctx, const char *name) { DetectBufferType *exists = DetectEngineBufferTypeLookupByName(de_ctx, name); BUG_ON(!exists); exists->supports_transforms = true; SCLogDebug("%p %s -- %d supports transformations", exists, name, exists->id); } bool DetectEngineBufferTypeSupportsMultiInstanceGetById(const DetectEngineCtx *de_ctx, const int id) { const DetectBufferType *map = DetectEngineBufferTypeGetById(de_ctx, id); if (map == NULL) return false; SCLogDebug("map %p id %d multi_instance? %s", map, id, BOOL2STR(map->multi_instance)); return map->multi_instance; } bool DetectEngineBufferTypeSupportsPacketGetById(const DetectEngineCtx *de_ctx, const int id) { const DetectBufferType *map = DetectEngineBufferTypeGetById(de_ctx, id); if (map == NULL) return false; SCLogDebug("map %p id %d packet? %d", map, id, map->packet); return map->packet; } bool DetectEngineBufferTypeSupportsMpmGetById(const DetectEngineCtx *de_ctx, const int id) { const DetectBufferType *map = DetectEngineBufferTypeGetById(de_ctx, id); if (map == NULL) return false; SCLogDebug("map %p id %d mpm? %d", map, id, map->mpm); return map->mpm; } bool DetectEngineBufferTypeSupportsFramesGetById(const DetectEngineCtx *de_ctx, const int id) { const DetectBufferType *map = DetectEngineBufferTypeGetById(de_ctx, id); if (map == NULL) return false; SCLogDebug("map %p id %d frame? %d", map, id, map->frame); return map->frame; } void DetectBufferTypeRegisterSetupCallback(const char *name, void (*SetupCallback)(const DetectEngineCtx *, Signature *)) { BUG_ON(g_buffer_type_reg_closed); DetectBufferTypeRegister(name); DetectBufferType *exists = DetectBufferTypeLookupByName(name); BUG_ON(!exists); exists->SetupCallback = SetupCallback; } void DetectEngineBufferRunSetupCallback(const DetectEngineCtx *de_ctx, const int id, Signature *s) { const DetectBufferType *map = DetectEngineBufferTypeGetById(de_ctx, id); if (map && map->SetupCallback) { map->SetupCallback(de_ctx, s); } } void DetectBufferTypeRegisterValidateCallback( const char *name, bool (*ValidateCallback)(const Signature *, const char **sigerror, const DetectBufferType *)) { BUG_ON(g_buffer_type_reg_closed); DetectBufferTypeRegister(name); DetectBufferType *exists = DetectBufferTypeLookupByName(name); BUG_ON(!exists); exists->ValidateCallback = ValidateCallback; } bool DetectEngineBufferRunValidateCallback( const DetectEngineCtx *de_ctx, const int id, const Signature *s, const char **sigerror) { const DetectBufferType *map = DetectEngineBufferTypeGetById(de_ctx, id); // only run validation if the buffer is not transformed if (map && map->ValidateCallback && map->transforms.cnt == 0) { return map->ValidateCallback(s, sigerror, map); } return true; } bool DetectBufferIsPresent(const Signature *s, const uint32_t buf_id) { for (uint32_t i = 0; i < s->init_data->buffer_index; i++) { if (buf_id == s->init_data->buffers[i].id) { return true; } } return false; } /** \brief Check content byte array compatibility with transforms * * The "content" array is presented to the transforms so that each * transform may validate that it's compatible with the transform. * * When a transform indicates the byte array is incompatible, none of the * subsequent transforms, if any, are invoked. This means the first validation * failure terminates the loop. * * \param de_ctx Detection engine context. * \param sm_list The SM list id. * \param content The byte array being validated * \param namestr returns the name of the transform that is incompatible with * content. * * \retval true (false) If any of the transforms indicate the byte array is * (is not) compatible. **/ bool DetectEngineBufferTypeValidateTransform(DetectEngineCtx *de_ctx, int sm_list, const uint8_t *content, uint16_t content_len, const char **namestr) { const DetectBufferType *dbt = DetectEngineBufferTypeGetById(de_ctx, sm_list); BUG_ON(dbt == NULL); for (int i = 0; i < dbt->transforms.cnt; i++) { const TransformData *t = &dbt->transforms.transforms[i]; if (!sigmatch_table[t->transform].TransformValidate) continue; if (sigmatch_table[t->transform].TransformValidate(content, content_len, t->options)) { continue; } if (namestr) { *namestr = sigmatch_table[t->transform].name; } return false; } return true; } static void DetectBufferTypeSetupDetectEngine(DetectEngineCtx *de_ctx) { const int size = g_buffer_type_id; BUG_ON(!(size > 0)); de_ctx->buffer_type_hash_name = HashListTableInit(256, DetectBufferTypeHashNameFunc, DetectBufferTypeCompareNameFunc, DetectBufferTypeFreeFunc); BUG_ON(de_ctx->buffer_type_hash_name == NULL); de_ctx->buffer_type_hash_id = HashListTableInit(256, DetectBufferTypeHashIdFunc, DetectBufferTypeCompareIdFunc, NULL); // entries owned by buffer_type_hash_name BUG_ON(de_ctx->buffer_type_hash_id == NULL); de_ctx->buffer_type_id = g_buffer_type_id; SCLogDebug("DETECT_SM_LIST_DYNAMIC_START %u", DETECT_SM_LIST_DYNAMIC_START); HashListTableBucket *b = HashListTableGetListHead(g_buffer_type_hash); while (b) { DetectBufferType *map = HashListTableGetListData(b); DetectBufferType *copy = SCCalloc(1, sizeof(*copy)); BUG_ON(!copy); memcpy(copy, map, sizeof(*copy)); int r = HashListTableAdd(de_ctx->buffer_type_hash_name, (void *)copy, 0); BUG_ON(r != 0); r = HashListTableAdd(de_ctx->buffer_type_hash_id, (void *)copy, 0); BUG_ON(r != 0); SCLogDebug("name %s id %d mpm %s packet %s -- %s. " "Callbacks: Setup %p Validate %p", map->name, map->id, map->mpm ? "true" : "false", map->packet ? "true" : "false", map->description, map->SetupCallback, map->ValidateCallback); b = HashListTableGetListNext(b); } PrefilterInit(de_ctx); DetectMpmInitializeAppMpms(de_ctx); DetectAppLayerInspectEngineCopyListToDetectCtx(de_ctx); DetectMpmInitializeFrameMpms(de_ctx); DetectFrameInspectEngineCopyListToDetectCtx(de_ctx); DetectMpmInitializePktMpms(de_ctx); DetectPktInspectEngineCopyListToDetectCtx(de_ctx); } static void DetectBufferTypeFreeDetectEngine(DetectEngineCtx *de_ctx) { if (de_ctx) { if (de_ctx->buffer_type_hash_name) HashListTableFree(de_ctx->buffer_type_hash_name); if (de_ctx->buffer_type_hash_id) HashListTableFree(de_ctx->buffer_type_hash_id); DetectEngineAppInspectionEngine *ilist = de_ctx->app_inspect_engines; while (ilist) { DetectEngineAppInspectionEngine *next = ilist->next; SCFree(ilist); ilist = next; } DetectBufferMpmRegistry *mlist = de_ctx->app_mpms_list; while (mlist) { DetectBufferMpmRegistry *next = mlist->next; SCFree(mlist); mlist = next; } DetectEnginePktInspectionEngine *plist = de_ctx->pkt_inspect_engines; while (plist) { DetectEnginePktInspectionEngine *next = plist->next; SCFree(plist); plist = next; } DetectBufferMpmRegistry *pmlist = de_ctx->pkt_mpms_list; while (pmlist) { DetectBufferMpmRegistry *next = pmlist->next; SCFree(pmlist); pmlist = next; } DetectEngineFrameInspectionEngine *framelist = de_ctx->frame_inspect_engines; while (framelist) { DetectEngineFrameInspectionEngine *next = framelist->next; SCFree(framelist); framelist = next; } DetectBufferMpmRegistry *framemlist = de_ctx->frame_mpms_list; while (framemlist) { DetectBufferMpmRegistry *next = framemlist->next; SCFree(framemlist); framemlist = next; } PrefilterDeinit(de_ctx); } } void DetectBufferTypeCloseRegistration(void) { BUG_ON(g_buffer_type_hash == NULL); g_buffer_type_reg_closed = 1; } int DetectEngineBufferTypeGetByIdTransforms( DetectEngineCtx *de_ctx, const int id, TransformData *transforms, int transform_cnt) { const DetectBufferType *base_map = DetectEngineBufferTypeGetById(de_ctx, id); if (!base_map) { return -1; } if (!base_map->supports_transforms) { SCLogError("buffer '%s' does not support transformations", base_map->name); return -1; } SCLogDebug("base_map %s", base_map->name); DetectEngineTransforms t; memset(&t, 0, sizeof(t)); for (int i = 0; i < transform_cnt; i++) { t.transforms[i] = transforms[i]; } t.cnt = transform_cnt; DetectBufferType lookup_map; memset(&lookup_map, 0, sizeof(lookup_map)); strlcpy(lookup_map.name, base_map->name, sizeof(lookup_map.name)); lookup_map.transforms = t; /* Add transform identity data from transforms */ if (t.cnt) { DetectBufferAddTransformData(&lookup_map); } DetectBufferType *res = HashListTableLookup(de_ctx->buffer_type_hash_name, &lookup_map, 0); SCLogDebug("res %p", res); if (res != NULL) { return res->id; } DetectBufferType *map = SCCalloc(1, sizeof(*map)); if (map == NULL) return -1; strlcpy(map->name, base_map->name, sizeof(map->name)); map->id = de_ctx->buffer_type_id++; map->parent_id = base_map->id; map->transforms = t; map->mpm = base_map->mpm; map->packet = base_map->packet; map->frame = base_map->frame; map->SetupCallback = base_map->SetupCallback; map->ValidateCallback = base_map->ValidateCallback; if (map->frame) { DetectFrameMpmRegisterByParentId(de_ctx, map->id, map->parent_id, &map->transforms); } else if (map->packet) { DetectPktMpmRegisterByParentId(de_ctx, map->id, map->parent_id, &map->transforms); } else { DetectAppLayerMpmRegisterByParentId(de_ctx, map->id, map->parent_id, &map->transforms); } BUG_ON(HashListTableAdd(de_ctx->buffer_type_hash_name, (void *)map, 0) != 0); BUG_ON(HashListTableAdd(de_ctx->buffer_type_hash_id, (void *)map, 0) != 0); SCLogDebug("buffer %s registered with id %d, parent %d", map->name, map->id, map->parent_id); if (map->frame) { DetectFrameInspectEngineCopy(de_ctx, map->parent_id, map->id, &map->transforms); } else if (map->packet) { DetectPktInspectEngineCopy(de_ctx, map->parent_id, map->id, &map->transforms); } else { DetectAppLayerInspectEngineCopy(de_ctx, map->parent_id, map->id, &map->transforms); } return map->id; } /* returns false if no match, true if match */ static int DetectEngineInspectRulePacketMatches( DetectEngineThreadCtx *det_ctx, const DetectEnginePktInspectionEngine *engine, const Signature *s, Packet *p, uint8_t *_alert_flags) { SCEnter(); /* run the packet match functions */ KEYWORD_PROFILING_SET_LIST(det_ctx, DETECT_SM_LIST_MATCH); const SigMatchData *smd = s->sm_arrays[DETECT_SM_LIST_MATCH]; SCLogDebug("running match functions, sm %p", smd); while (1) { KEYWORD_PROFILING_START; if (sigmatch_table[smd->type].Match(det_ctx, p, s, smd->ctx) <= 0) { KEYWORD_PROFILING_END(det_ctx, smd->type, 0); SCLogDebug("no match"); return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; } KEYWORD_PROFILING_END(det_ctx, smd->type, 1); if (smd->is_last) { SCLogDebug("match and is_last"); break; } smd++; } return DETECT_ENGINE_INSPECT_SIG_MATCH; } static int DetectEngineInspectRulePayloadMatches( DetectEngineThreadCtx *det_ctx, const DetectEnginePktInspectionEngine *engine, const Signature *s, Packet *p, uint8_t *alert_flags) { SCEnter(); DetectEngineCtx *de_ctx = det_ctx->de_ctx; KEYWORD_PROFILING_SET_LIST(det_ctx, DETECT_SM_LIST_PMATCH); /* if we have stream msgs, inspect against those first, * but not for a "dsize" signature */ if (s->flags & SIG_FLAG_REQUIRE_STREAM) { int pmatch = 0; if (p->flags & PKT_DETECT_HAS_STREAMDATA) { pmatch = DetectEngineInspectStreamPayload(de_ctx, det_ctx, s, p->flow, p); if (pmatch) { *alert_flags |= PACKET_ALERT_FLAG_STREAM_MATCH; } } /* no match? then inspect packet payload */ if (pmatch == 0) { SCLogDebug("no match in stream, fall back to packet payload"); /* skip if we don't have to inspect the packet and segment was * added to stream */ if (!(s->flags & SIG_FLAG_REQUIRE_PACKET) && (p->flags & PKT_STREAM_ADD)) { return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; } if (s->flags & SIG_FLAG_REQUIRE_STREAM_ONLY) { SCLogDebug("SIG_FLAG_REQUIRE_STREAM_ONLY, so no match"); return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; } if (DetectEngineInspectPacketPayload(de_ctx, det_ctx, s, p->flow, p) != 1) { return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; } } } else { if (DetectEngineInspectPacketPayload(de_ctx, det_ctx, s, p->flow, p) != 1) { return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; } } return DETECT_ENGINE_INSPECT_SIG_MATCH; } bool DetectEnginePktInspectionRun(ThreadVars *tv, DetectEngineThreadCtx *det_ctx, const Signature *s, Flow *f, Packet *p, uint8_t *alert_flags) { SCEnter(); for (DetectEnginePktInspectionEngine *e = s->pkt_inspect; e != NULL; e = e->next) { if (e->v1.Callback(det_ctx, e, s, p, alert_flags) != DETECT_ENGINE_INSPECT_SIG_MATCH) { SCLogDebug("sid %u: e %p Callback returned no match", s->id, e); return false; } SCLogDebug("sid %u: e %p Callback returned true", s->id, e); } SCLogDebug("sid %u: returning true", s->id); return true; } /** * \param data pointer to SigMatchData. Allowed to be NULL. */ static int DetectEnginePktInspectionAppend(Signature *s, InspectionBufferPktInspectFunc Callback, SigMatchData *data, const int list_id) { DetectEnginePktInspectionEngine *e = SCCalloc(1, sizeof(*e)); if (e == NULL) return -1; e->mpm = s->init_data->mpm_sm_list == list_id; DEBUG_VALIDATE_BUG_ON(list_id < 0 || list_id > UINT16_MAX); e->sm_list = (uint16_t)list_id; e->sm_list_base = (uint16_t)list_id; e->v1.Callback = Callback; e->smd = data; if (s->pkt_inspect == NULL) { s->pkt_inspect = e; } else { DetectEnginePktInspectionEngine *a = s->pkt_inspect; while (a->next != NULL) { a = a->next; } a->next = e; } return 0; } int DetectEnginePktInspectionSetup(Signature *s) { /* only handle PMATCH here if we're not an app inspect rule */ if (s->sm_arrays[DETECT_SM_LIST_PMATCH] && (s->init_data->init_flags & SIG_FLAG_INIT_STATE_MATCH) == 0) { if (DetectEnginePktInspectionAppend( s, DetectEngineInspectRulePayloadMatches, NULL, DETECT_SM_LIST_PMATCH) < 0) return -1; SCLogDebug("sid %u: DetectEngineInspectRulePayloadMatches appended", s->id); } if (s->sm_arrays[DETECT_SM_LIST_MATCH]) { if (DetectEnginePktInspectionAppend( s, DetectEngineInspectRulePacketMatches, NULL, DETECT_SM_LIST_MATCH) < 0) return -1; SCLogDebug("sid %u: DetectEngineInspectRulePacketMatches appended", s->id); } return 0; } /* code to control the main thread to do a reload */ enum DetectEngineSyncState { IDLE, /**< ready to start a reload */ RELOAD, /**< command main thread to do the reload */ }; typedef struct DetectEngineSyncer_ { SCMutex m; enum DetectEngineSyncState state; } DetectEngineSyncer; static DetectEngineSyncer detect_sync = { SCMUTEX_INITIALIZER, IDLE }; /* tell main to start reloading */ int DetectEngineReloadStart(void) { int r = 0; SCMutexLock(&detect_sync.m); if (detect_sync.state == IDLE) { detect_sync.state = RELOAD; } else { r = -1; } SCMutexUnlock(&detect_sync.m); return r; } /* main thread checks this to see if it should start */ int DetectEngineReloadIsStart(void) { int r = 0; SCMutexLock(&detect_sync.m); if (detect_sync.state == RELOAD) { r = 1; } SCMutexUnlock(&detect_sync.m); return r; } /* main thread sets done when it's done */ void DetectEngineReloadSetIdle(void) { SCMutexLock(&detect_sync.m); detect_sync.state = IDLE; SCMutexUnlock(&detect_sync.m); } /* caller loops this until it returns 1 */ int DetectEngineReloadIsIdle(void) { int r = 0; SCMutexLock(&detect_sync.m); if (detect_sync.state == IDLE) { r = 1; } SCMutexUnlock(&detect_sync.m); return r; } /** \brief Do the content inspection & validation for a signature * * \param de_ctx Detection engine context * \param det_ctx Detection engine thread context * \param s Signature to inspect * \param sm SigMatch to inspect * \param f Flow * \param flags app layer flags * \param state App layer state * * \retval 0 no match * \retval 1 match */ uint8_t DetectEngineInspectGenericList(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id) { SigMatchData *smd = engine->smd; SCLogDebug("running match functions, sm %p", smd); if (smd != NULL) { while (1) { int match = 0; KEYWORD_PROFILING_START; match = sigmatch_table[smd->type]. AppLayerTxMatch(det_ctx, f, flags, alstate, txv, s, smd->ctx); KEYWORD_PROFILING_END(det_ctx, smd->type, (match == 1)); if (match == 0) return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; if (match == 2) { return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH; } if (smd->is_last) break; smd++; } } return DETECT_ENGINE_INSPECT_SIG_MATCH; } /** * \brief Do the content inspection & validation for a signature * * \param de_ctx Detection engine context * \param det_ctx Detection engine thread context * \param s Signature to inspect * \param f Flow * \param flags app layer flags * \param state App layer state * * \retval 0 no match. * \retval 1 match. * \retval 2 Sig can't match. */ uint8_t DetectEngineInspectBufferSingle(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, const DetectEngineAppInspectionEngine *engine, const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id) { const int list_id = engine->sm_list; SCLogDebug("running inspect on %d", list_id); const bool eof = (AppLayerParserGetStateProgress(f->proto, f->alproto, txv, flags) > engine->progress); SCLogDebug("list %d mpm? %s transforms %p", engine->sm_list, engine->mpm ? "true" : "false", engine->v2.transforms); /* if prefilter didn't already run, we need to consider transformations */ const DetectEngineTransforms *transforms = NULL; if (!engine->mpm) { transforms = engine->v2.transforms; } const InspectionBuffer *buffer = DetectGetSingleData( det_ctx, transforms, f, flags, txv, list_id, engine->v2.GetDataSingle); if (unlikely(buffer == NULL)) { if (eof && engine->match_on_null) { return DETECT_ENGINE_INSPECT_SIG_MATCH; } return eof ? DETECT_ENGINE_INSPECT_SIG_CANT_MATCH : DETECT_ENGINE_INSPECT_SIG_NO_MATCH; } const uint32_t data_len = buffer->inspect_len; const uint8_t *data = buffer->inspect; const uint64_t offset = buffer->inspect_offset; uint8_t ci_flags = eof ? DETECT_CI_FLAGS_END : 0; ci_flags |= (offset == 0 ? DETECT_CI_FLAGS_START : 0); ci_flags |= buffer->flags; /* Inspect all the uricontents fetched on each * transaction at the app layer */ const bool match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd, NULL, f, data, data_len, offset, ci_flags, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE); if (match) { return DETECT_ENGINE_INSPECT_SIG_MATCH; } else { return eof ? DETECT_ENGINE_INSPECT_SIG_CANT_MATCH : DETECT_ENGINE_INSPECT_SIG_NO_MATCH; } } /** * \brief Do the content inspection & validation for a signature * * \param de_ctx Detection engine context * \param det_ctx Detection engine thread context * \param s Signature to inspect * \param f Flow * \param flags app layer flags * \param state App layer state * * \retval 0 no match. * \retval 1 match. * \retval 2 Sig can't match. */ uint8_t DetectEngineInspectBufferGeneric(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, const DetectEngineAppInspectionEngine *engine, const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id) { const int list_id = engine->sm_list; SCLogDebug("running inspect on %d", list_id); const bool eof = (AppLayerParserGetStateProgress(f->proto, f->alproto, txv, flags) > engine->progress); SCLogDebug("list %d mpm? %s transforms %p", engine->sm_list, engine->mpm ? "true" : "false", engine->v2.transforms); /* if prefilter didn't already run, we need to consider transformations */ const DetectEngineTransforms *transforms = NULL; if (!engine->mpm) { transforms = engine->v2.transforms; } const InspectionBuffer *buffer = engine->v2.GetData(det_ctx, transforms, f, flags, txv, list_id); if (unlikely(buffer == NULL)) { if (eof && engine->match_on_null) { return DETECT_ENGINE_INSPECT_SIG_MATCH; } return eof ? DETECT_ENGINE_INSPECT_SIG_CANT_MATCH : DETECT_ENGINE_INSPECT_SIG_NO_MATCH; } const uint32_t data_len = buffer->inspect_len; const uint8_t *data = buffer->inspect; const uint64_t offset = buffer->inspect_offset; uint8_t ci_flags = eof ? DETECT_CI_FLAGS_END : 0; ci_flags |= (offset == 0 ? DETECT_CI_FLAGS_START : 0); ci_flags |= buffer->flags; /* Inspect all the uricontents fetched on each * transaction at the app layer */ const bool match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd, NULL, f, data, data_len, offset, ci_flags, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE); if (match) { return DETECT_ENGINE_INSPECT_SIG_MATCH; } else { return eof ? DETECT_ENGINE_INSPECT_SIG_CANT_MATCH : DETECT_ENGINE_INSPECT_SIG_NO_MATCH; } } // wrapper for both DetectAppLayerInspectEngineRegister and DetectAppLayerMpmRegister // with cast of callback function void DetectAppLayerMultiRegister(const char *name, AppProto alproto, uint32_t dir, int progress, InspectionMultiBufferGetDataPtr GetData, int priority) { AppLayerInspectEngineRegisterInternal(name, alproto, dir, progress, DetectEngineInspectMultiBufferGeneric, NULL, NULL, GetData); DetectAppLayerMpmMultiRegister( name, dir, priority, PrefilterMultiGenericMpmRegister, GetData, alproto, progress); } InspectionBuffer *DetectGetSingleData(struct DetectEngineThreadCtx_ *det_ctx, const DetectEngineTransforms *transforms, Flow *f, const uint8_t flow_flags, void *txv, const int list_id, InspectionSingleBufferGetDataPtr GetBuf) { InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id); if (buffer->inspect == NULL) { const uint8_t *b = NULL; uint32_t b_len = 0; if (!GetBuf(txv, flow_flags, &b, &b_len)) return NULL; InspectionBufferSetupAndApplyTransforms(det_ctx, list_id, buffer, b, b_len, transforms); } return buffer; } InspectionBuffer *DetectGetMultiData(struct DetectEngineThreadCtx_ *det_ctx, const DetectEngineTransforms *transforms, Flow *f, const uint8_t flow_flags, void *txv, const int list_id, uint32_t index, InspectionMultiBufferGetDataPtr GetBuf) { InspectionBuffer *buffer = InspectionBufferMultipleForListGet(det_ctx, list_id, index); if (buffer == NULL) { return NULL; } if (buffer->initialized) { return buffer; } const uint8_t *data = NULL; uint32_t data_len = 0; if (!GetBuf(det_ctx, txv, flow_flags, index, &data, &data_len)) { InspectionBufferSetupMultiEmpty(buffer); return NULL; } InspectionBufferSetupMulti(det_ctx, buffer, transforms, data, data_len); buffer->flags = DETECT_CI_FLAGS_SINGLE; return buffer; } uint8_t DetectEngineInspectMultiBufferGeneric(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, const DetectEngineAppInspectionEngine *engine, const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id) { uint32_t local_id = 0; const DetectEngineTransforms *transforms = NULL; if (!engine->mpm) { transforms = engine->v2.transforms; } do { InspectionBuffer *buffer = DetectGetMultiData(det_ctx, transforms, f, flags, txv, engine->sm_list, local_id, engine->v2.GetMultiData); if (buffer == NULL || buffer->inspect == NULL) break; // The GetData functions set buffer->flags to DETECT_CI_FLAGS_SINGLE // This is not meant for streaming buffers const bool match = DetectEngineContentInspectionBuffer(de_ctx, det_ctx, s, engine->smd, NULL, f, buffer, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE); if (match) { return DETECT_ENGINE_INSPECT_SIG_MATCH; } local_id++; } while (1); if (local_id == 0) { // That means we did not get even one buffer value from the multi-buffer const bool eof = (AppLayerParserGetStateProgress(f->proto, f->alproto, txv, flags) > engine->progress); if (eof && engine->match_on_null) { return DETECT_ENGINE_INSPECT_SIG_MATCH; } } return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; } /** * \brief Do the content inspection & validation for a signature * * \param de_ctx Detection engine context * \param det_ctx Detection engine thread context * \param s Signature to inspect * \param p Packet * * \retval 0 no match. * \retval 1 match. */ int DetectEngineInspectPktBufferGeneric( DetectEngineThreadCtx *det_ctx, const DetectEnginePktInspectionEngine *engine, const Signature *s, Packet *p, uint8_t *_alert_flags) { const int list_id = engine->sm_list; SCLogDebug("running inspect on %d", list_id); SCLogDebug("list %d transforms %p", engine->sm_list, engine->v1.transforms); /* if prefilter didn't already run, we need to consider transformations */ const DetectEngineTransforms *transforms = NULL; if (!engine->mpm) { transforms = engine->v1.transforms; } const InspectionBuffer *buffer = engine->v1.GetData(det_ctx, transforms, p, list_id); if (unlikely(buffer == NULL)) { return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; } uint8_t ci_flags = DETECT_CI_FLAGS_START|DETECT_CI_FLAGS_END; ci_flags |= buffer->flags; /* Inspect all the uricontents fetched on each * transaction at the app layer */ const bool match = DetectEngineContentInspection(det_ctx->de_ctx, det_ctx, s, engine->smd, p, p->flow, buffer->inspect, buffer->inspect_len, 0, ci_flags, DETECT_ENGINE_CONTENT_INSPECTION_MODE_HEADER); if (match) { return DETECT_ENGINE_INSPECT_SIG_MATCH; } else { return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; } } /** \internal * \brief inject a pseudo packet into each detect thread * if the thread should flush its output logs. */ void InjectPacketsForFlush(ThreadVars **detect_tvs, int no_of_detect_tvs) { /* inject a fake packet if the detect thread that needs it. This function * is called when a heartbeat log-flush request has been made * and it should process a pseudo packet and flush its output logs * to speed the process. */ #if DEBUG int count = 0; #endif for (int i = 0; i < no_of_detect_tvs; i++) { if (detect_tvs[i]) { // && detect_tvs[i]->inq != NULL) { Packet *p = PacketGetFromAlloc(); if (p != NULL) { SCLogDebug("Injecting pkt for tv %s[i=%d] %d", detect_tvs[i]->name, i, count++); p->flags |= PKT_PSEUDO_STREAM_END; p->flags |= PKT_PSEUDO_LOG_FLUSH; PKT_SET_SRC(p, PKT_SRC_DETECT_RELOAD_FLUSH); PacketQueue *q = detect_tvs[i]->stream_pq; SCMutexLock(&q->mutex_q); PacketEnqueue(q, p); SCCondSignal(&q->cond_q); SCMutexUnlock(&q->mutex_q); } } } SCLogDebug("leaving: thread notification count = %d", count); } /** \internal * \brief inject a pseudo packet into each detect thread * -that doesn't use the new det_ctx yet * -*or*, if the thread should flush its output logs. */ static void InjectPackets( ThreadVars **detect_tvs, DetectEngineThreadCtx **new_det_ctx, int no_of_detect_tvs) { /* inject a fake packet if the detect thread that needs it. This function * is called if * - A thread isn't using a DE ctx and should * - Or, it should process a pseudo packet and flush its output logs. * to speed the process. */ for (int i = 0; i < no_of_detect_tvs; i++) { if (SC_ATOMIC_GET(new_det_ctx[i]->so_far_used_by_detect) != 1) { if (detect_tvs[i]->inq != NULL) { Packet *p = PacketGetFromAlloc(); if (p != NULL) { p->flags |= PKT_PSEUDO_STREAM_END; PKT_SET_SRC(p, PKT_SRC_DETECT_RELOAD_FLUSH); PacketQueue *q = detect_tvs[i]->inq->pq; SCMutexLock(&q->mutex_q); PacketEnqueue(q, p); SCCondSignal(&q->cond_q); SCMutexUnlock(&q->mutex_q); } } } } } /** \internal * \brief Update detect threads with new detect engine * * Atomically update each detect thread with a new thread context * that is associated to the new detection engine(s). * * If called in unix socket mode, it's possible that we don't have * detect threads yet. * * \retval -1 error * \retval 0 no detection threads * \retval 1 successful reload */ static int DetectEngineReloadThreads(DetectEngineCtx *new_de_ctx) { SCEnter(); uint32_t i = 0; /* count detect threads in use */ uint32_t no_of_detect_tvs = TmThreadCountThreadsByTmmFlags(TM_FLAG_FLOWWORKER_TM); /* can be zero in unix socket mode */ if (no_of_detect_tvs == 0) { return 0; } /* prepare swap structures */ DetectEngineThreadCtx *old_det_ctx[no_of_detect_tvs]; DetectEngineThreadCtx *new_det_ctx[no_of_detect_tvs]; ThreadVars *detect_tvs[no_of_detect_tvs]; memset(old_det_ctx, 0x00, (no_of_detect_tvs * sizeof(DetectEngineThreadCtx *))); memset(new_det_ctx, 0x00, (no_of_detect_tvs * sizeof(DetectEngineThreadCtx *))); memset(detect_tvs, 0x00, (no_of_detect_tvs * sizeof(ThreadVars *))); /* start the process of swapping detect threads ctxs */ /* get reference to tv's and setup new_det_ctx array */ SCMutexLock(&tv_root_lock); for (ThreadVars *tv = tv_root[TVT_PPT]; tv != NULL; tv = tv->next) { if ((tv->tmm_flags & TM_FLAG_FLOWWORKER_TM) == 0) { continue; } for (TmSlot *s = tv->tm_slots; s != NULL; s = s->slot_next) { TmModule *tm = TmModuleGetById(s->tm_id); if (!(tm->flags & TM_FLAG_FLOWWORKER_TM)) { continue; } if (suricata_ctl_flags != 0) { SCMutexUnlock(&tv_root_lock); goto error; } old_det_ctx[i] = FlowWorkerGetDetectCtxPtr(SC_ATOMIC_GET(s->slot_data)); detect_tvs[i] = tv; new_det_ctx[i] = DetectEngineThreadCtxInitForReload(tv, new_de_ctx, 1); if (new_det_ctx[i] == NULL) { SCLogError("Detect engine thread init " "failure in live rule swap. Let's get out of here"); SCMutexUnlock(&tv_root_lock); goto error; } SCLogDebug("live rule swap created new det_ctx - %p and de_ctx " "- %p\n", new_det_ctx[i], new_de_ctx); i++; break; } } BUG_ON(i != no_of_detect_tvs); /* atomically replace the det_ctx data */ i = 0; for (ThreadVars *tv = tv_root[TVT_PPT]; tv != NULL; tv = tv->next) { if ((tv->tmm_flags & TM_FLAG_FLOWWORKER_TM) == 0) { continue; } for (TmSlot *s = tv->tm_slots; s != NULL; s = s->slot_next) { TmModule *tm = TmModuleGetById(s->tm_id); if (!(tm->flags & TM_FLAG_FLOWWORKER_TM)) { continue; } SCLogDebug("swapping new det_ctx - %p with older one - %p", new_det_ctx[i], SC_ATOMIC_GET(s->slot_data)); FlowWorkerReplaceDetectCtx(SC_ATOMIC_GET(s->slot_data), new_det_ctx[i++]); break; } } SCMutexUnlock(&tv_root_lock); /* threads now all have new data, however they may not have started using * it and may still use the old data */ SCLogDebug("Live rule swap has swapped %d old det_ctx's with new ones, " "along with the new de_ctx", no_of_detect_tvs); InjectPackets(detect_tvs, new_det_ctx, no_of_detect_tvs); /* loop waiting for detect threads to switch to the new det_ctx. Try to * wake up capture if needed (break loop). */ uint32_t threads_done = 0; retry: for (i = 0; i < no_of_detect_tvs; i++) { if (suricata_ctl_flags != 0) { threads_done = no_of_detect_tvs; break; } SleepMsec(1); if (SC_ATOMIC_GET(new_det_ctx[i]->so_far_used_by_detect) == 1) { SCLogDebug("new_det_ctx - %p used by detect engine", new_det_ctx[i]); threads_done++; } else { TmThreadsCaptureBreakLoop(detect_tvs[i]); } } if (threads_done < no_of_detect_tvs) { threads_done = 0; SleepMsec(250); goto retry; } /* this is to make sure that if someone initiated shutdown during a live * rule swap, the live rule swap won't clean up the old det_ctx and * de_ctx, till all detect threads have stopped working and sitting * silently after setting RUNNING_DONE flag and while waiting for * THV_DEINIT flag */ if (i != no_of_detect_tvs) { // not all threads we swapped for (ThreadVars *tv = tv_root[TVT_PPT]; tv != NULL; tv = tv->next) { if ((tv->tmm_flags & TM_FLAG_FLOWWORKER_TM) == 0) { continue; } while (!TmThreadsCheckFlag(tv, THV_RUNNING_DONE)) { SleepUsec(100); } } } /* free all the ctxs */ for (i = 0; i < no_of_detect_tvs; i++) { SCLogDebug("Freeing old_det_ctx - %p used by detect", old_det_ctx[i]); DetectEngineThreadCtxDeinit(NULL, old_det_ctx[i]); } SRepReloadComplete(); return 1; error: for (i = 0; i < no_of_detect_tvs; i++) { if (new_det_ctx[i] != NULL) DetectEngineThreadCtxDeinit(NULL, new_det_ctx[i]); } return -1; } bool DetectEngineMpmCachingEnabled(void) { int sgh_mpm_caching = 0; if (SCConfGetBool("detect.sgh-mpm-caching", &sgh_mpm_caching) != 1) { return false; } return (bool)sgh_mpm_caching; } const char *DetectEngineMpmCachingGetPath(void) { if (DetectEngineMpmCachingEnabled() == false) { return NULL; } char yamlpath[] = "detect.sgh-mpm-caching-path"; const char *strval = NULL; if (SCConfGet(yamlpath, &strval) == 1 && strval != NULL) { return strval; } static bool notified = false; if (!notified) { SCLogInfo("%s has no path specified, using %s", yamlpath, SGH_CACHE_DIR); notified = true; } return SGH_CACHE_DIR; } static DetectEngineCtx *DetectEngineCtxInitReal( enum DetectEngineType type, const char *prefix, uint32_t tenant_id) { DetectEngineCtx *de_ctx = SCCalloc(1, sizeof(DetectEngineCtx)); if (unlikely(de_ctx == NULL)) goto error; memset(&de_ctx->sig_stat, 0, sizeof(SigFileLoaderStat)); TAILQ_INIT(&de_ctx->sig_stat.failed_sigs); de_ctx->sigerror = NULL; de_ctx->type = type; de_ctx->filemagic_thread_ctx_id = -1; de_ctx->tenant_id = tenant_id; if (type == DETECT_ENGINE_TYPE_DD_STUB || type == DETECT_ENGINE_TYPE_MT_STUB) { de_ctx->version = DetectEngineGetVersion(); SCLogDebug("stub %u with version %u", type, de_ctx->version); return de_ctx; } if (prefix != NULL) { strlcpy(de_ctx->config_prefix, prefix, sizeof(de_ctx->config_prefix)); } int failure_fatal = 0; if (SCConfGetBool("engine.init-failure-fatal", (int *)&failure_fatal) != 1) { SCLogDebug("ConfGetBool could not load the value."); } de_ctx->failure_fatal = (failure_fatal == 1); de_ctx->mpm_matcher = PatternMatchDefaultMatcher(); de_ctx->spm_matcher = SinglePatternMatchDefaultMatcher(); SCLogConfig("pattern matchers: MPM: %s, SPM: %s", mpm_table[de_ctx->mpm_matcher].name, spm_table[de_ctx->spm_matcher].name); if (mpm_table[de_ctx->mpm_matcher].ConfigInit) { de_ctx->mpm_cfg = mpm_table[de_ctx->mpm_matcher].ConfigInit(); if (de_ctx->mpm_cfg == NULL) { goto error; } } if (DetectEngineMpmCachingEnabled() && mpm_table[de_ctx->mpm_matcher].ConfigCacheDirSet) { mpm_table[de_ctx->mpm_matcher].ConfigCacheDirSet( de_ctx->mpm_cfg, DetectEngineMpmCachingGetPath()); } de_ctx->spm_global_thread_ctx = SpmInitGlobalThreadCtx(de_ctx->spm_matcher); if (de_ctx->spm_global_thread_ctx == NULL) { SCLogDebug("Unable to alloc SpmGlobalThreadCtx."); goto error; } de_ctx->sm_types_prefilter = SCCalloc(DETECT_TBLSIZE, sizeof(bool)); if (de_ctx->sm_types_prefilter == NULL) { goto error; } de_ctx->sm_types_silent_error = SCCalloc(DETECT_TBLSIZE, sizeof(bool)); if (de_ctx->sm_types_silent_error == NULL) { goto error; } if (DetectEngineCtxLoadConf(de_ctx) == -1) { goto error; } SigGroupHeadHashInit(de_ctx); MpmStoreInit(de_ctx); DetectParseDupSigHashInit(de_ctx); DetectAddressMapInit(de_ctx); DetectMetadataHashInit(de_ctx); DetectBufferTypeSetupDetectEngine(de_ctx); DetectEngineInitializeFastPatternList(de_ctx); /* init iprep... ignore errors for now */ (void)SRepInit(de_ctx); SCClassSCConfInit(de_ctx); if (!SCClassConfLoadClassificationConfigFile(de_ctx, NULL)) { if (SCRunmodeGet() == RUNMODE_CONF_TEST) goto error; } if (ActionInitConfig() < 0) { goto error; } SCReferenceSCConfInit(de_ctx); if (SCRConfLoadReferenceConfigFile(de_ctx, NULL) < 0) { if (SCRunmodeGet() == RUNMODE_CONF_TEST) goto error; } de_ctx->version = DetectEngineGetVersion(); SCLogDebug("dectx with version %u", de_ctx->version); return de_ctx; error: if (de_ctx != NULL) { DetectEngineCtxFree(de_ctx); } return NULL; } DetectEngineCtx *DetectEngineCtxInitStubForMT(void) { return DetectEngineCtxInitReal(DETECT_ENGINE_TYPE_MT_STUB, NULL, 0); } DetectEngineCtx *DetectEngineCtxInitStubForDD(void) { return DetectEngineCtxInitReal(DETECT_ENGINE_TYPE_DD_STUB, NULL, 0); } DetectEngineCtx *DetectEngineCtxInit(void) { return DetectEngineCtxInitReal(DETECT_ENGINE_TYPE_NORMAL, NULL, 0); } DetectEngineCtx *DetectEngineCtxInitWithPrefix(const char *prefix, uint32_t tenant_id) { if (prefix == NULL || strlen(prefix) == 0) return DetectEngineCtxInit(); else return DetectEngineCtxInitReal(DETECT_ENGINE_TYPE_NORMAL, prefix, tenant_id); } static void DetectEngineCtxFreeThreadKeywordData(DetectEngineCtx *de_ctx) { HashListTableFree(de_ctx->keyword_hash); } static void DetectEngineCtxFreeFailedSigs(DetectEngineCtx *de_ctx) { SigString *item = NULL; SigString *sitem; TAILQ_FOREACH_SAFE(item, &de_ctx->sig_stat.failed_sigs, next, sitem) { SCFree(item->filename); SCFree(item->sig_str); if (item->sig_error) { SCFree(item->sig_error); } TAILQ_REMOVE(&de_ctx->sig_stat.failed_sigs, item, next); SCFree(item); } } /** * \brief Free a DetectEngineCtx:: * * \param de_ctx DetectEngineCtx:: to be freed */ void DetectEngineCtxFree(DetectEngineCtx *de_ctx) { if (de_ctx == NULL) return; #ifdef PROFILE_RULES if (de_ctx->profile_ctx != NULL) { SCProfilingRuleDestroyCtx(de_ctx->profile_ctx); de_ctx->profile_ctx = NULL; } #endif #ifdef PROFILING if (de_ctx->profile_keyword_ctx != NULL) { SCProfilingKeywordDestroyCtx(de_ctx);//->profile_keyword_ctx); // de_ctx->profile_keyword_ctx = NULL; } if (de_ctx->profile_sgh_ctx != NULL) { SCProfilingSghDestroyCtx(de_ctx); } SCProfilingPrefilterDestroyCtx(de_ctx); #endif if (mpm_table[de_ctx->mpm_matcher].ConfigDeinit) { mpm_table[de_ctx->mpm_matcher].ConfigDeinit(&de_ctx->mpm_cfg); } /* Normally the hashes are freed elsewhere, but * to be sure look at them again here. */ SigGroupHeadHashFree(de_ctx); MpmStoreFree(de_ctx); DetectParseDupSigHashFree(de_ctx); SCSigSignatureOrderingModuleCleanup(de_ctx); SigCleanSignatures(de_ctx); if (de_ctx->sig_array) SCFree(de_ctx->sig_array); if (de_ctx->filedata_config) SCFree(de_ctx->filedata_config); DetectEngineFreeFastPatternList(de_ctx); SCClassConfDeInitContext(de_ctx); SCRConfDeInitContext(de_ctx); SigGroupCleanup(de_ctx); SpmDestroyGlobalThreadCtx(de_ctx->spm_global_thread_ctx); SCFree(de_ctx->sm_types_prefilter); SCFree(de_ctx->sm_types_silent_error); MpmFactoryDeRegisterAllMpmCtxProfiles(de_ctx); DetectEngineCtxFreeThreadKeywordData(de_ctx); SRepDestroy(de_ctx); DetectEngineCtxFreeFailedSigs(de_ctx); DetectAddressMapFree(de_ctx); DetectMetadataHashFree(de_ctx); /* if we have a config prefix, remove the config from the tree */ if (strlen(de_ctx->config_prefix) > 0) { /* remove config */ SCConfNode *node = SCConfGetNode(de_ctx->config_prefix); if (node != NULL) { SCConfNodeRemove(node); /* frees node */ } #if 0 SCConfDump(); #endif } DetectPortCleanupList(de_ctx, de_ctx->tcp_priorityports); DetectPortCleanupList(de_ctx, de_ctx->udp_priorityports); DetectBufferTypeFreeDetectEngine(de_ctx); SCClassConfDeinit(de_ctx); SCReferenceConfDeinit(de_ctx); if (de_ctx->tenant_path) { SCFree(de_ctx->tenant_path); } if (de_ctx->requirements) { SCDetectRequiresStatusFree(de_ctx->requirements); } if (de_ctx->non_pf_engine_names) { HashTableFree(de_ctx->non_pf_engine_names); } SCFree(de_ctx); //DetectAddressGroupPrintMemory(); //DetectSigGroupPrintMemory(); //DetectPortPrintMemory(); } /** \brief Function that load DetectEngineCtx config for grouping sigs * used by the engine * \retval 0 if no config provided, 1 if config was provided * and loaded successfully */ static int DetectEngineCtxLoadConf(DetectEngineCtx *de_ctx) { uint8_t profile = ENGINE_PROFILE_MEDIUM; const char *max_uniq_toclient_groups_str = NULL; const char *max_uniq_toserver_groups_str = NULL; const char *sgh_mpm_context = NULL; const char *de_ctx_profile = NULL; (void)SCConfGet("detect.profile", &de_ctx_profile); (void)SCConfGet("detect.sgh-mpm-context", &sgh_mpm_context); SCConfNode *de_ctx_custom = SCConfGetNode("detect-engine"); SCConfNode *opt = NULL; if (de_ctx_custom != NULL) { TAILQ_FOREACH(opt, &de_ctx_custom->head, next) { if (de_ctx_profile == NULL) { if (opt->val && strcmp(opt->val, "profile") == 0) { de_ctx_profile = opt->head.tqh_first->val; } } if (sgh_mpm_context == NULL) { if (opt->val && strcmp(opt->val, "sgh-mpm-context") == 0) { sgh_mpm_context = opt->head.tqh_first->val; } } } } if (de_ctx_profile != NULL) { if (strcmp(de_ctx_profile, "low") == 0 || strcmp(de_ctx_profile, "lowest") == 0) { // legacy profile = ENGINE_PROFILE_LOW; } else if (strcmp(de_ctx_profile, "medium") == 0) { profile = ENGINE_PROFILE_MEDIUM; } else if (strcmp(de_ctx_profile, "high") == 0 || strcmp(de_ctx_profile, "highest") == 0) { // legacy profile = ENGINE_PROFILE_HIGH; } else if (strcmp(de_ctx_profile, "custom") == 0) { profile = ENGINE_PROFILE_CUSTOM; } else { SCLogError("invalid value for detect.profile: '%s'. " "Valid options: low, medium, high and custom.", de_ctx_profile); return -1; } SCLogDebug("Profile for detection engine groups is \"%s\"", de_ctx_profile); } else { SCLogDebug("Profile for detection engine groups not provided " "at suricata.yaml. Using default (\"medium\")."); } /* detect-engine.sgh-mpm-context option parsing */ if (sgh_mpm_context == NULL || strcmp(sgh_mpm_context, "auto") == 0) { /* for now, since we still haven't implemented any intelligence into * understanding the patterns and distributing mpm_ctx across sgh */ if (de_ctx->mpm_matcher == MPM_AC || de_ctx->mpm_matcher == MPM_AC_KS || de_ctx->mpm_matcher == MPM_HS) { de_ctx->sgh_mpm_ctx_cnf = ENGINE_SGH_MPM_FACTORY_CONTEXT_SINGLE; } else { de_ctx->sgh_mpm_ctx_cnf = ENGINE_SGH_MPM_FACTORY_CONTEXT_FULL; } } else { if (strcmp(sgh_mpm_context, "single") == 0) { de_ctx->sgh_mpm_ctx_cnf = ENGINE_SGH_MPM_FACTORY_CONTEXT_SINGLE; } else if (strcmp(sgh_mpm_context, "full") == 0) { de_ctx->sgh_mpm_ctx_cnf = ENGINE_SGH_MPM_FACTORY_CONTEXT_FULL; } else { SCLogError("You have supplied an " "invalid conf value for detect-engine.sgh-mpm-context-" "%s", sgh_mpm_context); exit(EXIT_FAILURE); } } if (RunmodeIsUnittests()) { de_ctx->sgh_mpm_ctx_cnf = ENGINE_SGH_MPM_FACTORY_CONTEXT_FULL; } /* parse profile custom-values */ opt = NULL; switch (profile) { case ENGINE_PROFILE_LOW: de_ctx->max_uniq_toclient_groups = 15; de_ctx->max_uniq_toserver_groups = 25; break; case ENGINE_PROFILE_HIGH: de_ctx->max_uniq_toclient_groups = 75; de_ctx->max_uniq_toserver_groups = 75; break; case ENGINE_PROFILE_CUSTOM: (void)SCConfGet("detect.custom-values.toclient-groups", &max_uniq_toclient_groups_str); (void)SCConfGet("detect.custom-values.toserver-groups", &max_uniq_toserver_groups_str); if (de_ctx_custom != NULL) { TAILQ_FOREACH(opt, &de_ctx_custom->head, next) { if (opt->val && strcmp(opt->val, "custom-values") == 0) { if (max_uniq_toclient_groups_str == NULL) { max_uniq_toclient_groups_str = (char *)SCConfNodeLookupChildValue( opt->head.tqh_first, "toclient-sp-groups"); } if (max_uniq_toclient_groups_str == NULL) { max_uniq_toclient_groups_str = (char *)SCConfNodeLookupChildValue( opt->head.tqh_first, "toclient-groups"); } if (max_uniq_toserver_groups_str == NULL) { max_uniq_toserver_groups_str = (char *)SCConfNodeLookupChildValue( opt->head.tqh_first, "toserver-dp-groups"); } if (max_uniq_toserver_groups_str == NULL) { max_uniq_toserver_groups_str = (char *)SCConfNodeLookupChildValue( opt->head.tqh_first, "toserver-groups"); } } } } if (max_uniq_toclient_groups_str != NULL) { if (StringParseUint16(&de_ctx->max_uniq_toclient_groups, 10, (uint16_t)strlen(max_uniq_toclient_groups_str), (const char *)max_uniq_toclient_groups_str) <= 0) { de_ctx->max_uniq_toclient_groups = 20; SCLogWarning("parsing '%s' for " "toclient-groups failed, using %u", max_uniq_toclient_groups_str, de_ctx->max_uniq_toclient_groups); } } else { de_ctx->max_uniq_toclient_groups = 20; } SCLogConfig("toclient-groups %u", de_ctx->max_uniq_toclient_groups); if (max_uniq_toserver_groups_str != NULL) { if (StringParseUint16(&de_ctx->max_uniq_toserver_groups, 10, (uint16_t)strlen(max_uniq_toserver_groups_str), (const char *)max_uniq_toserver_groups_str) <= 0) { de_ctx->max_uniq_toserver_groups = 40; SCLogWarning("parsing '%s' for " "toserver-groups failed, using %u", max_uniq_toserver_groups_str, de_ctx->max_uniq_toserver_groups); } } else { de_ctx->max_uniq_toserver_groups = 40; } SCLogConfig("toserver-groups %u", de_ctx->max_uniq_toserver_groups); break; /* Default (or no config provided) is profile medium */ case ENGINE_PROFILE_MEDIUM: case ENGINE_PROFILE_UNKNOWN: default: de_ctx->max_uniq_toclient_groups = 20; de_ctx->max_uniq_toserver_groups = 40; break; } intmax_t value = 0; de_ctx->inspection_recursion_limit = DETECT_ENGINE_DEFAULT_INSPECTION_RECURSION_LIMIT; if (SCConfGetInt("detect.inspection-recursion-limit", &value) == 1) { if (value >= 0 && value <= INT_MAX) { de_ctx->inspection_recursion_limit = (int)value; } /* fall back to old config parsing */ } else { SCConfNode *insp_recursion_limit_node = NULL; char *insp_recursion_limit = NULL; if (de_ctx_custom != NULL) { opt = NULL; TAILQ_FOREACH(opt, &de_ctx_custom->head, next) { if (opt->val && strcmp(opt->val, "inspection-recursion-limit") != 0) continue; insp_recursion_limit_node = SCConfNodeLookupChild(opt, opt->val); if (insp_recursion_limit_node == NULL) { SCLogError("Error retrieving conf " "entry for detect-engine:inspection-recursion-limit"); break; } insp_recursion_limit = insp_recursion_limit_node->val; SCLogDebug("Found detect-engine.inspection-recursion-limit - %s:%s", insp_recursion_limit_node->name, insp_recursion_limit_node->val); break; } if (insp_recursion_limit != NULL) { if (StringParseInt32(&de_ctx->inspection_recursion_limit, 10, 0, (const char *)insp_recursion_limit) < 0) { SCLogWarning("Invalid value for " "detect-engine.inspection-recursion-limit: %s " "resetting to %d", insp_recursion_limit, DETECT_ENGINE_DEFAULT_INSPECTION_RECURSION_LIMIT); de_ctx->inspection_recursion_limit = DETECT_ENGINE_DEFAULT_INSPECTION_RECURSION_LIMIT; } } } } if (de_ctx->inspection_recursion_limit == 0) de_ctx->inspection_recursion_limit = -1; SCLogDebug("de_ctx->inspection_recursion_limit: %d", de_ctx->inspection_recursion_limit); // default value is 4 de_ctx->guess_applayer_log_limit = 4; if (SCConfGetInt("detect.stream-tx-log-limit", &value) == 1) { if (value >= 0 && value <= UINT8_MAX) { de_ctx->guess_applayer_log_limit = (uint8_t)value; } else { SCLogWarning("Invalid value for detect-engine.stream-tx-log-limit: must be between 0 " "and 255, will default to 4"); } } int guess_applayer = 0; if ((SCConfGetBool("detect.guess-applayer-tx", &guess_applayer)) == 1) { if (guess_applayer == 1) { de_ctx->guess_applayer = true; } } /* parse port grouping priority settings */ const char *ports = NULL; (void)SCConfGet("detect.grouping.tcp-priority-ports", &ports); if (ports) { SCLogConfig("grouping: tcp-priority-ports %s", ports); } else { (void)SCConfGet("detect.grouping.tcp-whitelist", &ports); if (ports) { SCLogConfig( "grouping: tcp-priority-ports from legacy 'tcp-whitelist' setting: %s", ports); } else { ports = "53, 80, 139, 443, 445, 1433, 3306, 3389, 6666, 6667, 8080"; SCLogConfig("grouping: tcp-priority-ports (default) %s", ports); } } if (DetectPortParse(de_ctx, &de_ctx->tcp_priorityports, ports) != 0) { SCLogWarning("'%s' is not a valid value " "for detect.grouping.tcp-priority-ports", ports); } DetectPort *x = de_ctx->tcp_priorityports; for ( ; x != NULL; x = x->next) { if (x->port != x->port2) { SCLogWarning("'%s' is not a valid value " "for detect.grouping.tcp-priority-ports: only single ports allowed", ports); DetectPortCleanupList(de_ctx, de_ctx->tcp_priorityports); de_ctx->tcp_priorityports = NULL; break; } } ports = NULL; (void)SCConfGet("detect.grouping.udp-priority-ports", &ports); if (ports) { SCLogConfig("grouping: udp-priority-ports %s", ports); } else { (void)SCConfGet("detect.grouping.udp-whitelist", &ports); if (ports) { SCLogConfig( "grouping: udp-priority-ports from legacy 'udp-whitelist' setting: %s", ports); } else { ports = "53, 135, 5060"; SCLogConfig("grouping: udp-priority-ports (default) %s", ports); } } if (DetectPortParse(de_ctx, &de_ctx->udp_priorityports, ports) != 0) { SCLogWarning("'%s' is not a valid value " "for detect.grouping.udp-priority-ports", ports); } for (x = de_ctx->udp_priorityports; x != NULL; x = x->next) { if (x->port != x->port2) { SCLogWarning("'%s' is not a valid value " "for detect.grouping.udp-priority-ports: only single ports allowed", ports); DetectPortCleanupList(de_ctx, de_ctx->udp_priorityports); de_ctx->udp_priorityports = NULL; break; } } de_ctx->prefilter_setting = DETECT_PREFILTER_MPM; const char *pf_setting = NULL; if (SCConfGet("detect.prefilter.default", &pf_setting) == 1 && pf_setting) { if (strcasecmp(pf_setting, "mpm") == 0) { de_ctx->prefilter_setting = DETECT_PREFILTER_MPM; } else if (strcasecmp(pf_setting, "auto") == 0) { de_ctx->prefilter_setting = DETECT_PREFILTER_AUTO; } } switch (de_ctx->prefilter_setting) { case DETECT_PREFILTER_MPM: SCLogConfig("prefilter engines: MPM"); break; case DETECT_PREFILTER_AUTO: SCLogConfig("prefilter engines: MPM and keywords"); break; } return 0; } void DetectEngineResetMaxSigId(DetectEngineCtx *de_ctx) { de_ctx->signum = 0; } static int DetectEngineThreadCtxInitGlobalKeywords(DetectEngineThreadCtx *det_ctx) { const DetectEngineMasterCtx *master = &g_master_de_ctx; if (master->keyword_id > 0) { // coverity[suspicious_sizeof : FALSE] det_ctx->global_keyword_ctxs_array = (void **)SCCalloc(master->keyword_id, sizeof(void *)); if (det_ctx->global_keyword_ctxs_array == NULL) { SCLogError("setting up thread local detect ctx"); return TM_ECODE_FAILED; } det_ctx->global_keyword_ctxs_size = master->keyword_id; const DetectEngineThreadKeywordCtxItem *item = master->keyword_list; while (item) { det_ctx->global_keyword_ctxs_array[item->id] = item->InitFunc(item->data); if (det_ctx->global_keyword_ctxs_array[item->id] == NULL) { SCLogError("setting up thread local detect ctx " "for keyword \"%s\" failed", item->name); return TM_ECODE_FAILED; } item = item->next; } } return TM_ECODE_OK; } static void DetectEngineThreadCtxDeinitGlobalKeywords(DetectEngineThreadCtx *det_ctx) { if (det_ctx->global_keyword_ctxs_array == NULL || det_ctx->global_keyword_ctxs_size == 0) { return; } const DetectEngineMasterCtx *master = &g_master_de_ctx; if (master->keyword_id > 0) { const DetectEngineThreadKeywordCtxItem *item = master->keyword_list; while (item) { if (det_ctx->global_keyword_ctxs_array[item->id] != NULL) item->FreeFunc(det_ctx->global_keyword_ctxs_array[item->id]); item = item->next; } det_ctx->global_keyword_ctxs_size = 0; SCFree(det_ctx->global_keyword_ctxs_array); det_ctx->global_keyword_ctxs_array = NULL; } } static int DetectEngineThreadCtxInitKeywords(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx) { if (de_ctx->keyword_id > 0) { // coverity[suspicious_sizeof : FALSE] det_ctx->keyword_ctxs_array = SCCalloc(de_ctx->keyword_id, sizeof(void *)); if (det_ctx->keyword_ctxs_array == NULL) { SCLogError("setting up thread local detect ctx"); return TM_ECODE_FAILED; } det_ctx->keyword_ctxs_size = de_ctx->keyword_id; HashListTableBucket *hb = HashListTableGetListHead(de_ctx->keyword_hash); for (; hb != NULL; hb = HashListTableGetListNext(hb)) { DetectEngineThreadKeywordCtxItem *item = HashListTableGetListData(hb); det_ctx->keyword_ctxs_array[item->id] = item->InitFunc(item->data); if (det_ctx->keyword_ctxs_array[item->id] == NULL) { SCLogError("setting up thread local detect ctx " "for keyword \"%s\" failed", item->name); return TM_ECODE_FAILED; } } } return TM_ECODE_OK; } static void DetectEngineThreadCtxDeinitKeywords(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx) { if (de_ctx->keyword_id > 0) { HashListTableBucket *hb = HashListTableGetListHead(de_ctx->keyword_hash); for (; hb != NULL; hb = HashListTableGetListNext(hb)) { DetectEngineThreadKeywordCtxItem *item = HashListTableGetListData(hb); if (det_ctx->keyword_ctxs_array[item->id] != NULL) item->FreeFunc(det_ctx->keyword_ctxs_array[item->id]); } det_ctx->keyword_ctxs_size = 0; SCFree(det_ctx->keyword_ctxs_array); det_ctx->keyword_ctxs_array = NULL; } } /** NOTE: master MUST be locked before calling this */ static TmEcode DetectEngineThreadCtxInitForMT(ThreadVars *tv, DetectEngineThreadCtx *det_ctx) { DetectEngineMasterCtx *master = &g_master_de_ctx; SCMutexLock(&master->lock); DetectEngineTenantMapping *map_array = NULL; uint32_t map_array_size = 0; uint32_t map_cnt = 0; uint32_t max_tenant_id = 0; DetectEngineCtx *list = master->list; if (master->tenant_selector == TENANT_SELECTOR_UNKNOWN) { SCLogError("no tenant selector set: " "set using multi-detect.selector"); SCMutexUnlock(&master->lock); return TM_ECODE_FAILED; } uint32_t tcnt = 0; while (list) { if (list->tenant_id > max_tenant_id) max_tenant_id = list->tenant_id; list = list->next; tcnt++; } HashTable *mt_det_ctxs_hash = HashTableInit(tcnt * 2, TenantIdHash, TenantIdCompare, TenantIdFree); if (mt_det_ctxs_hash == NULL) { goto error; } if (tcnt == 0) { SCLogInfo("no tenants left, or none registered yet"); } else { max_tenant_id++; DetectEngineTenantMapping *map = master->tenant_mapping_list; while (map) { map_cnt++; map = map->next; } if (map_cnt > 0) { map_array_size = map_cnt + 1; map_array = SCCalloc(map_array_size, sizeof(*map_array)); if (map_array == NULL) goto error; /* fill the array */ map_cnt = 0; map = master->tenant_mapping_list; while (map) { if (map_cnt >= map_array_size) { goto error; } map_array[map_cnt].traffic_id = map->traffic_id; map_array[map_cnt].tenant_id = map->tenant_id; map_cnt++; map = map->next; } } /* set up hash for tenant lookup */ list = master->list; while (list) { SCLogDebug("tenant-id %u", list->tenant_id); if (list->tenant_id != 0) { DetectEngineThreadCtx *mt_det_ctx = DetectEngineThreadCtxInitForReload(tv, list, 0); if (mt_det_ctx == NULL) goto error; if (HashTableAdd(mt_det_ctxs_hash, mt_det_ctx, 0) != 0) { goto error; } } list = list->next; } } det_ctx->mt_det_ctxs_hash = mt_det_ctxs_hash; mt_det_ctxs_hash = NULL; det_ctx->mt_det_ctxs_cnt = max_tenant_id; det_ctx->tenant_array = map_array; det_ctx->tenant_array_size = map_array_size; switch (master->tenant_selector) { case TENANT_SELECTOR_UNKNOWN: SCLogDebug("TENANT_SELECTOR_UNKNOWN"); break; case TENANT_SELECTOR_VLAN: det_ctx->TenantGetId = DetectEngineTenantGetIdFromVlanId; SCLogDebug("TENANT_SELECTOR_VLAN"); break; case TENANT_SELECTOR_LIVEDEV: det_ctx->TenantGetId = DetectEngineTenantGetIdFromLivedev; SCLogDebug("TENANT_SELECTOR_LIVEDEV"); break; case TENANT_SELECTOR_DIRECT: det_ctx->TenantGetId = DetectEngineTenantGetIdFromPcap; SCLogDebug("TENANT_SELECTOR_DIRECT"); break; } SCMutexUnlock(&master->lock); return TM_ECODE_OK; error: if (map_array != NULL) SCFree(map_array); if (mt_det_ctxs_hash != NULL) HashTableFree(mt_det_ctxs_hash); SCMutexUnlock(&master->lock); return TM_ECODE_FAILED; } /** \internal * \brief Helper for DetectThread setup functions */ static TmEcode ThreadCtxDoInit (DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx) { PatternMatchThreadPrepare(&det_ctx->mtc, de_ctx->mpm_matcher); PmqSetup(&det_ctx->pmq); det_ctx->spm_thread_ctx = SpmMakeThreadCtx(de_ctx->spm_global_thread_ctx); if (det_ctx->spm_thread_ctx == NULL) { return TM_ECODE_FAILED; } /* DeState */ if (de_ctx->sig_array_len > 0) { det_ctx->match_array_len = de_ctx->sig_array_len; det_ctx->match_array = SCCalloc(det_ctx->match_array_len, sizeof(Signature *)); if (det_ctx->match_array == NULL) { return TM_ECODE_FAILED; } RuleMatchCandidateTxArrayInit(det_ctx, de_ctx->sig_array_len); } /* Alert processing queue */ AlertQueueInit(det_ctx); /* byte_extract storage */ det_ctx->byte_values = SCMalloc(sizeof(*det_ctx->byte_values) * (de_ctx->byte_extract_max_local_id + 1)); if (det_ctx->byte_values == NULL) { return TM_ECODE_FAILED; } /* Allocate space for base64 decoded data. */ if (de_ctx->base64_decode_max_len) { det_ctx->base64_decoded = SCMalloc(de_ctx->base64_decode_max_len); if (det_ctx->base64_decoded == NULL) { return TM_ECODE_FAILED; } det_ctx->base64_decoded_len = 0; } det_ctx->inspect.buffers_size = de_ctx->buffer_type_id; det_ctx->inspect.buffers = SCCalloc(det_ctx->inspect.buffers_size, sizeof(InspectionBuffer)); if (det_ctx->inspect.buffers == NULL) { return TM_ECODE_FAILED; } det_ctx->inspect.to_clear_queue = SCCalloc(det_ctx->inspect.buffers_size, sizeof(uint32_t)); if (det_ctx->inspect.to_clear_queue == NULL) { return TM_ECODE_FAILED; } det_ctx->inspect.to_clear_idx = 0; det_ctx->multi_inspect.buffers_size = de_ctx->buffer_type_id; det_ctx->multi_inspect.buffers = SCCalloc(det_ctx->multi_inspect.buffers_size, sizeof(InspectionBufferMultipleForList)); if (det_ctx->multi_inspect.buffers == NULL) { return TM_ECODE_FAILED; } det_ctx->multi_inspect.to_clear_queue = SCCalloc(det_ctx->multi_inspect.buffers_size, sizeof(uint32_t)); if (det_ctx->multi_inspect.to_clear_queue == NULL) { return TM_ECODE_FAILED; } det_ctx->multi_inspect.to_clear_idx = 0; DetectEngineThreadCtxInitKeywords(de_ctx, det_ctx); DetectEngineThreadCtxInitGlobalKeywords(det_ctx); #ifdef PROFILE_RULES SCProfilingRuleThreadSetup(de_ctx->profile_ctx, det_ctx); #endif #ifdef PROFILING SCProfilingKeywordThreadSetup(de_ctx->profile_keyword_ctx, det_ctx); SCProfilingPrefilterThreadSetup(de_ctx->profile_prefilter_ctx, det_ctx); SCProfilingSghThreadSetup(de_ctx->profile_sgh_ctx, det_ctx); #endif SC_ATOMIC_INIT(det_ctx->so_far_used_by_detect); return TM_ECODE_OK; } /** \brief initialize thread specific detection engine context * * \note there is a special case when using delayed detect. In this case the * function is called twice per thread. The first time the rules are not * yet loaded. de_ctx->delayed_detect_initialized will be 0. The 2nd * time they will be loaded. de_ctx->delayed_detect_initialized will be 1. * This is needed to do the per thread counter registration before the * packet runtime starts. In delayed detect mode, the first call will * return a NULL ptr through the data ptr. * * \param tv ThreadVars for this thread * \param initdata pointer to de_ctx * \param data[out] pointer to store our thread detection ctx * * \retval TM_ECODE_OK if all went well * \retval TM_ECODE_FAILED on serious errors */ TmEcode DetectEngineThreadCtxInit(ThreadVars *tv, void *initdata, void **data) { DetectEngineThreadCtx *det_ctx = SCCalloc(1, sizeof(DetectEngineThreadCtx)); if (unlikely(det_ctx == NULL)) return TM_ECODE_FAILED; det_ctx->tv = tv; det_ctx->de_ctx = DetectEngineGetCurrent(); if (det_ctx->de_ctx == NULL) { #ifdef UNITTESTS if (RunmodeIsUnittests()) { det_ctx->de_ctx = (DetectEngineCtx *)initdata; } else { DetectEngineThreadCtxDeinit(tv, det_ctx); return TM_ECODE_FAILED; } #else DetectEngineThreadCtxDeinit(tv, det_ctx); return TM_ECODE_FAILED; #endif } if (det_ctx->de_ctx->type == DETECT_ENGINE_TYPE_NORMAL || det_ctx->de_ctx->type == DETECT_ENGINE_TYPE_TENANT) { if (ThreadCtxDoInit(det_ctx->de_ctx, det_ctx) != TM_ECODE_OK) { DetectEngineThreadCtxDeinit(tv, det_ctx); return TM_ECODE_FAILED; } } /** alert counter setup */ det_ctx->counter_alerts = StatsRegisterCounter("detect.alert", tv); det_ctx->counter_alerts_overflow = StatsRegisterCounter("detect.alert_queue_overflow", tv); det_ctx->counter_alerts_suppressed = StatsRegisterCounter("detect.alerts_suppressed", tv); /* Register counter for Lua rule errors. */ det_ctx->lua_rule_errors = StatsRegisterCounter("detect.lua.errors", tv); /* Register a counter for Lua blocked function attempts. */ det_ctx->lua_blocked_function_errors = StatsRegisterCounter("detect.lua.blocked_function_errors", tv); /* Register a counter for Lua instruction limit errors. */ det_ctx->lua_instruction_limit_errors = StatsRegisterCounter("detect.lua.instruction_limit_errors", tv); /* Register a counter for Lua memory limit errors. */ det_ctx->lua_memory_limit_errors = StatsRegisterCounter("detect.lua.memory_limit_errors", tv); det_ctx->json_content = NULL; det_ctx->json_content_capacity = 0; det_ctx->json_content_len = 0; #ifdef PROFILING det_ctx->counter_mpm_list = StatsRegisterAvgCounter("detect.mpm_list", tv); det_ctx->counter_nonmpm_list = StatsRegisterAvgCounter("detect.nonmpm_list", tv); det_ctx->counter_fnonmpm_list = StatsRegisterAvgCounter("detect.fnonmpm_list", tv); det_ctx->counter_match_list = StatsRegisterAvgCounter("detect.match_list", tv); #endif if (DetectEngineMultiTenantEnabled()) { if (DetectEngineThreadCtxInitForMT(tv, det_ctx) != TM_ECODE_OK) { DetectEngineThreadCtxDeinit(tv, det_ctx); return TM_ECODE_FAILED; } } /* pass thread data back to caller */ *data = (void *)det_ctx; return TM_ECODE_OK; } /** * \internal * \brief initialize a det_ctx for reload cases * \param new_de_ctx the new detection engine * \param mt flag to indicate if MT should be set up for this det_ctx * this should only be done for the 'root' det_ctx * * \retval det_ctx detection engine thread ctx or NULL in case of error */ DetectEngineThreadCtx *DetectEngineThreadCtxInitForReload( ThreadVars *tv, DetectEngineCtx *new_de_ctx, int mt) { DetectEngineThreadCtx *det_ctx = SCCalloc(1, sizeof(DetectEngineThreadCtx)); if (unlikely(det_ctx == NULL)) return NULL; det_ctx->tenant_id = new_de_ctx->tenant_id; det_ctx->tv = tv; det_ctx->de_ctx = DetectEngineReference(new_de_ctx); if (det_ctx->de_ctx == NULL) { SCFree(det_ctx); return NULL; } /* most of the init happens here */ if (det_ctx->de_ctx->type == DETECT_ENGINE_TYPE_NORMAL || det_ctx->de_ctx->type == DETECT_ENGINE_TYPE_TENANT) { if (ThreadCtxDoInit(det_ctx->de_ctx, det_ctx) != TM_ECODE_OK) { DetectEngineDeReference(&det_ctx->de_ctx); SCFree(det_ctx); return NULL; } } /** alert counter setup */ det_ctx->counter_alerts = StatsRegisterCounter("detect.alert", tv); det_ctx->counter_alerts_overflow = StatsRegisterCounter("detect.alert_queue_overflow", tv); det_ctx->counter_alerts_suppressed = StatsRegisterCounter("detect.alerts_suppressed", tv); #ifdef PROFILING uint16_t counter_mpm_list = StatsRegisterAvgCounter("detect.mpm_list", tv); uint16_t counter_nonmpm_list = StatsRegisterAvgCounter("detect.nonmpm_list", tv); uint16_t counter_fnonmpm_list = StatsRegisterAvgCounter("detect.fnonmpm_list", tv); uint16_t counter_match_list = StatsRegisterAvgCounter("detect.match_list", tv); det_ctx->counter_mpm_list = counter_mpm_list; det_ctx->counter_nonmpm_list = counter_nonmpm_list; det_ctx->counter_fnonmpm_list = counter_fnonmpm_list; det_ctx->counter_match_list = counter_match_list; #endif if (mt && DetectEngineMultiTenantEnabled()) { if (DetectEngineThreadCtxInitForMT(tv, det_ctx) != TM_ECODE_OK) { DetectEngineDeReference(&det_ctx->de_ctx); SCFree(det_ctx); return NULL; } } return det_ctx; } static void DetectEngineThreadCtxFree(DetectEngineThreadCtx *det_ctx) { #if DEBUG SCLogDebug("PACKET PKT_STREAM_ADD: %"PRIu64, det_ctx->pkt_stream_add_cnt); SCLogDebug("PAYLOAD MPM %"PRIu64"/%"PRIu64, det_ctx->payload_mpm_cnt, det_ctx->payload_mpm_size); SCLogDebug("STREAM MPM %"PRIu64"/%"PRIu64, det_ctx->stream_mpm_cnt, det_ctx->stream_mpm_size); SCLogDebug("PAYLOAD SIG %"PRIu64"/%"PRIu64, det_ctx->payload_persig_cnt, det_ctx->payload_persig_size); SCLogDebug("STREAM SIG %"PRIu64"/%"PRIu64, det_ctx->stream_persig_cnt, det_ctx->stream_persig_size); #endif if (det_ctx->tenant_array != NULL) { SCFree(det_ctx->tenant_array); det_ctx->tenant_array = NULL; } #ifdef PROFILE_RULES SCProfilingRuleThreadCleanup(det_ctx); #endif #ifdef PROFILING SCProfilingKeywordThreadCleanup(det_ctx); SCProfilingPrefilterThreadCleanup(det_ctx); SCProfilingSghThreadCleanup(det_ctx); #endif /** \todo get rid of this static */ if (det_ctx->de_ctx != NULL) { PatternMatchThreadDestroy(&det_ctx->mtc, det_ctx->de_ctx->mpm_matcher); } PmqFree(&det_ctx->pmq); if (det_ctx->spm_thread_ctx != NULL) { SpmDestroyThreadCtx(det_ctx->spm_thread_ctx); } if (det_ctx->match_array != NULL) SCFree(det_ctx->match_array); RuleMatchCandidateTxArrayFree(det_ctx); AlertQueueFree(det_ctx); if (det_ctx->post_rule_work_queue.q) SCFree(det_ctx->post_rule_work_queue.q); if (det_ctx->byte_values != NULL) SCFree(det_ctx->byte_values); /* Decoded base64 data. */ if (det_ctx->base64_decoded != NULL) { SCFree(det_ctx->base64_decoded); } if (det_ctx->inspect.buffers) { for (uint32_t i = 0; i < det_ctx->inspect.buffers_size; i++) { InspectionBufferFree(&det_ctx->inspect.buffers[i]); } SCFree(det_ctx->inspect.buffers); } if (det_ctx->inspect.to_clear_queue) { SCFree(det_ctx->inspect.to_clear_queue); } if (det_ctx->multi_inspect.buffers) { for (uint32_t i = 0; i < det_ctx->multi_inspect.buffers_size; i++) { InspectionBufferMultipleForList *fb = &det_ctx->multi_inspect.buffers[i]; for (uint32_t x = 0; x < fb->size; x++) { InspectionBufferFree(&fb->inspection_buffers[x]); } SCFree(fb->inspection_buffers); } SCFree(det_ctx->multi_inspect.buffers); } if (det_ctx->multi_inspect.to_clear_queue) { SCFree(det_ctx->multi_inspect.to_clear_queue); } DetectEngineThreadCtxDeinitGlobalKeywords(det_ctx); if (det_ctx->de_ctx != NULL) { DetectEngineThreadCtxDeinitKeywords(det_ctx->de_ctx, det_ctx); #ifdef UNITTESTS if (!RunmodeIsUnittests() || det_ctx->de_ctx->ref_cnt > 0) DetectEngineDeReference(&det_ctx->de_ctx); #else DetectEngineDeReference(&det_ctx->de_ctx); #endif } if (det_ctx->json_content) { SCFree(det_ctx->json_content); det_ctx->json_content = NULL; det_ctx->json_content_capacity = 0; } AppLayerDecoderEventsFreeEvents(&det_ctx->decoder_events); PrefilterPktNonPFStatsDump(); SCFree(det_ctx); ThresholdCacheThreadFree(); } TmEcode DetectEngineThreadCtxDeinit(ThreadVars *tv, void *data) { DetectEngineThreadCtx *det_ctx = (DetectEngineThreadCtx *)data; if (det_ctx == NULL) { SCLogWarning("argument \"data\" NULL"); return TM_ECODE_OK; } if (det_ctx->mt_det_ctxs_hash != NULL) { HashTableFree(det_ctx->mt_det_ctxs_hash); det_ctx->mt_det_ctxs_hash = NULL; } DetectEngineThreadCtxFree(det_ctx); return TM_ECODE_OK; } static uint32_t DetectKeywordCtxHashFunc(HashListTable *ht, void *data, uint16_t datalen) { DetectEngineThreadKeywordCtxItem *ctx = data; const char *name = ctx->name; uint64_t hash = StringHashDjb2((const uint8_t *)name, (uint32_t)strlen(name)) + (ptrdiff_t)ctx->data; hash %= ht->array_size; return (uint32_t)hash; } static char DetectKeywordCtxCompareFunc(void *data1, uint16_t len1, void *data2, uint16_t len2) { DetectEngineThreadKeywordCtxItem *ctx1 = data1; DetectEngineThreadKeywordCtxItem *ctx2 = data2; const char *name1 = ctx1->name; const char *name2 = ctx2->name; return (strcmp(name1, name2) == 0 && ctx1->data == ctx2->data); } static void DetectKeywordCtxFreeFunc(void *ptr) { SCFree(ptr); } /** \brief Register Thread keyword context Funcs * * \param de_ctx detection engine to register in * \param name keyword name for error printing * \param InitFunc function ptr * \param data keyword init data to pass to Func. Can be NULL. * \param FreeFunc function ptr * \param mode 0 normal (ctx per keyword instance) 1 shared (one ctx per det_ct) * * \retval id for retrieval of ctx at runtime * \retval -1 on error * * \note make sure "data" remains valid and it free'd elsewhere. It's * recommended to store it in the keywords global ctx so that * it's freed when the de_ctx is freed. */ int DetectRegisterThreadCtxFuncs(DetectEngineCtx *de_ctx, const char *name, void *(*InitFunc)(void *), void *data, void (*FreeFunc)(void *), int mode) { BUG_ON(de_ctx == NULL || InitFunc == NULL || FreeFunc == NULL); if (de_ctx->keyword_hash == NULL) { de_ctx->keyword_hash = HashListTableInit(4096, // TODO DetectKeywordCtxHashFunc, DetectKeywordCtxCompareFunc, DetectKeywordCtxFreeFunc); BUG_ON(de_ctx->keyword_hash == NULL); } if (mode) { DetectEngineThreadKeywordCtxItem search = { .data = data, .name = name }; DetectEngineThreadKeywordCtxItem *item = HashListTableLookup(de_ctx->keyword_hash, (void *)&search, 0); if (item) return item->id; /* fall through */ } DetectEngineThreadKeywordCtxItem *item = SCCalloc(1, sizeof(DetectEngineThreadKeywordCtxItem)); if (unlikely(item == NULL)) return -1; item->InitFunc = InitFunc; item->FreeFunc = FreeFunc; item->data = data; item->name = name; item->id = de_ctx->keyword_id++; if (HashListTableAdd(de_ctx->keyword_hash, (void *)item, 0) < 0) { SCFree(item); return -1; } return item->id; } /** \brief Remove Thread keyword context registration * * \param de_ctx detection engine to deregister from * \param det_ctx detection engine thread context to deregister from * \param data keyword init data to pass to Func. Can be NULL. * \param name keyword name for error printing * * \retval 1 Item unregistered * \retval 0 otherwise * * \note make sure "data" remains valid and it free'd elsewhere. It's * recommended to store it in the keywords global ctx so that * it's freed when the de_ctx is freed. */ int DetectUnregisterThreadCtxFuncs(DetectEngineCtx *de_ctx, void *data, const char *name) { /* might happen if we call this before a call to *Register* */ if (de_ctx->keyword_hash == NULL) return 1; DetectEngineThreadKeywordCtxItem remove = { .data = data, .name = name }; if (HashListTableRemove(de_ctx->keyword_hash, (void *)&remove, 0) == 0) return 1; return 0; } /** \brief Retrieve thread local keyword ctx by id * * \param det_ctx detection engine thread ctx to retrieve the ctx from * \param id id of the ctx returned by DetectRegisterThreadCtxInitFunc at * keyword init. * * \retval ctx or NULL on error */ void *DetectThreadCtxGetKeywordThreadCtx(DetectEngineThreadCtx *det_ctx, int id) { if (id < 0 || id > det_ctx->keyword_ctxs_size || det_ctx->keyword_ctxs_array == NULL) return NULL; return det_ctx->keyword_ctxs_array[id]; } /** \brief Register Thread keyword context Funcs (Global) * * IDs stay static over reloads and between tenants * * \param name keyword name for error printing * \param InitFunc function ptr * \param FreeFunc function ptr * * \retval id for retrieval of ctx at runtime * \retval -1 on error */ int DetectRegisterThreadCtxGlobalFuncs(const char *name, void *(*InitFunc)(void *), void *data, void (*FreeFunc)(void *)) { int id; BUG_ON(InitFunc == NULL || FreeFunc == NULL); DetectEngineMasterCtx *master = &g_master_de_ctx; /* if already registered, return existing id */ DetectEngineThreadKeywordCtxItem *item = master->keyword_list; while (item != NULL) { if (strcmp(name, item->name) == 0) { id = item->id; return id; } item = item->next; } item = SCCalloc(1, sizeof(*item)); if (unlikely(item == NULL)) { return -1; } item->InitFunc = InitFunc; item->FreeFunc = FreeFunc; item->name = name; item->data = data; item->next = master->keyword_list; master->keyword_list = item; item->id = master->keyword_id++; id = item->id; return id; } /** \brief Retrieve thread local keyword ctx by id * * \param det_ctx detection engine thread ctx to retrieve the ctx from * \param id id of the ctx returned by DetectRegisterThreadCtxInitFunc at * keyword init. * * \retval ctx or NULL on error */ void *DetectThreadCtxGetGlobalKeywordThreadCtx(DetectEngineThreadCtx *det_ctx, int id) { if (id < 0 || id > det_ctx->global_keyword_ctxs_size || det_ctx->global_keyword_ctxs_array == NULL) { return NULL; } return det_ctx->global_keyword_ctxs_array[id]; } /** \brief Check if detection is enabled * \retval bool true or false */ int DetectEngineEnabled(void) { DetectEngineMasterCtx *master = &g_master_de_ctx; SCMutexLock(&master->lock); if (master->list == NULL) { SCMutexUnlock(&master->lock); return 0; } SCMutexUnlock(&master->lock); return 1; } uint32_t DetectEngineGetVersion(void) { uint32_t version; DetectEngineMasterCtx *master = &g_master_de_ctx; SCMutexLock(&master->lock); version = master->version; SCMutexUnlock(&master->lock); return version; } void DetectEngineBumpVersion(void) { DetectEngineMasterCtx *master = &g_master_de_ctx; SCMutexLock(&master->lock); master->version++; SCLogDebug("master version now %u", master->version); SCMutexUnlock(&master->lock); } DetectEngineCtx *DetectEngineGetCurrent(void) { DetectEngineMasterCtx *master = &g_master_de_ctx; SCMutexLock(&master->lock); DetectEngineCtx *de_ctx = master->list; while (de_ctx) { if (de_ctx->type == DETECT_ENGINE_TYPE_NORMAL || de_ctx->type == DETECT_ENGINE_TYPE_DD_STUB || de_ctx->type == DETECT_ENGINE_TYPE_MT_STUB) { de_ctx->ref_cnt++; SCLogDebug("de_ctx %p ref_cnt %u", de_ctx, de_ctx->ref_cnt); SCMutexUnlock(&master->lock); return de_ctx; } de_ctx = de_ctx->next; } SCMutexUnlock(&master->lock); return NULL; } DetectEngineCtx *DetectEngineReference(DetectEngineCtx *de_ctx) { if (de_ctx == NULL) return NULL; de_ctx->ref_cnt++; return de_ctx; } bool DetectEngineMultiTenantEnabled(void) { DetectEngineMasterCtx *master = &g_master_de_ctx; SCMutexLock(&master->lock); bool enabled = master->multi_tenant_enabled; SCMutexUnlock(&master->lock); return enabled; } /** \internal * \brief load a tenant from a yaml file * * \param tenant_id the tenant id by which the config is known * \param filename full path of a yaml file * \param loader_id id of loader thread or -1 * * \retval 0 ok * \retval -1 failed */ static int DetectEngineMultiTenantLoadTenant(uint32_t tenant_id, const char *filename, int loader_id) { DetectEngineCtx *de_ctx = NULL; char prefix[64]; snprintf(prefix, sizeof(prefix), "multi-detect.%u", tenant_id); SCStat st; if (SCStatFn(filename, &st) != 0) { SCLogError("failed to stat file %s", filename); goto error; } de_ctx = DetectEngineGetByTenantId(tenant_id); if (de_ctx != NULL) { SCLogError("tenant %u already registered", tenant_id); DetectEngineDeReference(&de_ctx); goto error; } SCConfNode *node = SCConfGetNode(prefix); if (node == NULL) { SCLogError("failed to properly setup yaml %s", filename); goto error; } de_ctx = DetectEngineCtxInitWithPrefix(prefix, tenant_id); if (de_ctx == NULL) { SCLogError("initializing detection engine " "context failed."); goto error; } SCLogDebug("de_ctx %p with prefix %s", de_ctx, de_ctx->config_prefix); de_ctx->type = DETECT_ENGINE_TYPE_TENANT; de_ctx->tenant_id = tenant_id; de_ctx->loader_id = loader_id; de_ctx->tenant_path = SCStrdup(filename); if (de_ctx->tenant_path == NULL) { SCLogError("Failed to duplicate path"); goto error; } if (SigLoadSignatures(de_ctx, NULL, false) < 0) { SCLogError("Loading signatures failed."); goto error; } DetectEngineAddToMaster(de_ctx); return 0; error: if (de_ctx != NULL) { DetectEngineCtxFree(de_ctx); } return -1; } static int DetectEngineMultiTenantReloadTenant(uint32_t tenant_id, const char *filename, int reload_cnt) { DetectEngineCtx *old_de_ctx = DetectEngineGetByTenantId(tenant_id); if (old_de_ctx == NULL) { SCLogError("tenant detect engine not found"); return -1; } if (filename == NULL) filename = old_de_ctx->tenant_path; char prefix[64]; snprintf(prefix, sizeof(prefix), "multi-detect.%u.reload.%d", tenant_id, reload_cnt); reload_cnt++; SCLogDebug("prefix %s", prefix); if (SCConfYamlLoadFileWithPrefix(filename, prefix) != 0) { SCLogError("failed to load yaml"); goto error; } SCConfNode *node = SCConfGetNode(prefix); if (node == NULL) { SCLogError("failed to properly setup yaml %s", filename); goto error; } DetectEngineCtx *new_de_ctx = DetectEngineCtxInitWithPrefix(prefix, tenant_id); if (new_de_ctx == NULL) { SCLogError("initializing detection engine " "context failed."); goto error; } SCLogDebug("de_ctx %p with prefix %s", new_de_ctx, new_de_ctx->config_prefix); new_de_ctx->type = DETECT_ENGINE_TYPE_TENANT; new_de_ctx->tenant_id = tenant_id; new_de_ctx->loader_id = old_de_ctx->loader_id; new_de_ctx->tenant_path = SCStrdup(filename); if (new_de_ctx->tenant_path == NULL) { SCLogError("Failed to duplicate path"); goto new_de_ctx_error; } if (SigLoadSignatures(new_de_ctx, NULL, false) < 0) { SCLogError("Loading signatures failed."); goto new_de_ctx_error; } DetectEngineAddToMaster(new_de_ctx); /* move to free list */ DetectEngineMoveToFreeList(old_de_ctx); DetectEngineDeReference(&old_de_ctx); return 0; new_de_ctx_error: DetectEngineCtxFree(new_de_ctx); error: DetectEngineDeReference(&old_de_ctx); return -1; } typedef struct TenantLoaderCtx_ { uint32_t tenant_id; int reload_cnt; /**< used by reload */ char *yaml; /**< heap alloc'd copy of file path for the yaml */ } TenantLoaderCtx; static void DetectLoaderFreeTenant(void *ctx) { TenantLoaderCtx *t = (TenantLoaderCtx *)ctx; if (t->yaml != NULL) { SCFree(t->yaml); } SCFree(t); } static int DetectLoaderFuncLoadTenant(void *vctx, int loader_id) { TenantLoaderCtx *ctx = (TenantLoaderCtx *)vctx; SCLogDebug("loader %d", loader_id); if (DetectEngineMultiTenantLoadTenant(ctx->tenant_id, ctx->yaml, loader_id) != 0) { return -1; } return 0; } static int DetectLoaderSetupLoadTenant(uint32_t tenant_id, const char *yaml) { TenantLoaderCtx *t = SCCalloc(1, sizeof(*t)); if (t == NULL) return -ENOMEM; t->tenant_id = tenant_id; t->yaml = SCStrdup(yaml); if (t->yaml == NULL) { SCFree(t); return -ENOMEM; } return DetectLoaderQueueTask(-1, DetectLoaderFuncLoadTenant, t, DetectLoaderFreeTenant); } static int DetectLoaderFuncReloadTenant(void *vctx, int loader_id) { TenantLoaderCtx *ctx = (TenantLoaderCtx *)vctx; SCLogDebug("loader_id %d", loader_id); if (DetectEngineMultiTenantReloadTenant(ctx->tenant_id, ctx->yaml, ctx->reload_cnt) != 0) { return -1; } return 0; } static int DetectLoaderSetupReloadTenants(const int reload_cnt) { int ret = 0; DetectEngineMasterCtx *master = &g_master_de_ctx; SCMutexLock(&master->lock); DetectEngineCtx *de_ctx = master->list; while (de_ctx) { if (de_ctx->type == DETECT_ENGINE_TYPE_TENANT) { TenantLoaderCtx *t = SCCalloc(1, sizeof(*t)); if (t == NULL) { ret = -1; goto error; } t->tenant_id = de_ctx->tenant_id; t->reload_cnt = reload_cnt; int loader_id = de_ctx->loader_id; int r = DetectLoaderQueueTask( loader_id, DetectLoaderFuncReloadTenant, t, DetectLoaderFreeTenant); if (r < 0) { ret = -2; goto error; } } de_ctx = de_ctx->next; } error: SCMutexUnlock(&master->lock); return ret; } static int DetectLoaderSetupReloadTenant(uint32_t tenant_id, const char *yaml, int reload_cnt) { DetectEngineCtx *old_de_ctx = DetectEngineGetByTenantId(tenant_id); if (old_de_ctx == NULL) return -ENOENT; int loader_id = old_de_ctx->loader_id; DetectEngineDeReference(&old_de_ctx); TenantLoaderCtx *t = SCCalloc(1, sizeof(*t)); if (t == NULL) return -ENOMEM; t->tenant_id = tenant_id; if (yaml != NULL) { t->yaml = SCStrdup(yaml); if (t->yaml == NULL) { SCFree(t); return -ENOMEM; } } t->reload_cnt = reload_cnt; SCLogDebug("loader_id %d", loader_id); return DetectLoaderQueueTask( loader_id, DetectLoaderFuncReloadTenant, t, DetectLoaderFreeTenant); } /** \brief Load a tenant and wait for loading to complete */ int DetectEngineLoadTenantBlocking(uint32_t tenant_id, const char *yaml) { int r = DetectLoaderSetupLoadTenant(tenant_id, yaml); if (r < 0) return r; if (DetectLoadersSync() != 0) return -1; return 0; } /** \brief Reload a tenant and wait for loading to complete */ int DetectEngineReloadTenantBlocking(uint32_t tenant_id, const char *yaml, int reload_cnt) { int r = DetectLoaderSetupReloadTenant(tenant_id, yaml, reload_cnt); if (r < 0) return r; if (DetectLoadersSync() != 0) return -1; return 0; } /** \brief Reload all tenants and wait for loading to complete */ int DetectEngineReloadTenantsBlocking(const int reload_cnt) { int r = DetectLoaderSetupReloadTenants(reload_cnt); if (r < 0) return r; if (DetectLoadersSync() != 0) return -1; return 0; } static int DetectEngineMultiTenantSetupLoadLivedevMappings( const SCConfNode *mappings_root_node, bool failure_fatal) { SCConfNode *mapping_node = NULL; int mapping_cnt = 0; if (mappings_root_node != NULL) { TAILQ_FOREACH(mapping_node, &mappings_root_node->head, next) { SCConfNode *tenant_id_node = SCConfNodeLookupChild(mapping_node, "tenant-id"); if (tenant_id_node == NULL) goto bad_mapping; SCConfNode *device_node = SCConfNodeLookupChild(mapping_node, "device"); if (device_node == NULL) goto bad_mapping; uint32_t tenant_id = 0; if (StringParseUint32(&tenant_id, 10, (uint16_t)strlen(tenant_id_node->val), tenant_id_node->val) < 0) { SCLogError("tenant-id " "of %s is invalid", tenant_id_node->val); goto bad_mapping; } const char *dev = device_node->val; LiveDevice *ld = LiveGetDevice(dev); if (ld == NULL) { SCLogWarning("device %s not found", dev); goto bad_mapping; } if (ld->tenant_id_set) { SCLogWarning("device %s already mapped to tenant-id %u", dev, ld->tenant_id); goto bad_mapping; } ld->tenant_id = tenant_id; ld->tenant_id_set = true; if (DetectEngineTenantRegisterLivedev(tenant_id, ld->id) != 0) { goto error; } SCLogConfig("device %s connected to tenant-id %u", dev, tenant_id); mapping_cnt++; continue; bad_mapping: if (failure_fatal) goto error; } } SCLogConfig("%d device - tenant-id mappings defined", mapping_cnt); return mapping_cnt; error: return 0; } static int DetectEngineMultiTenantSetupLoadVlanMappings( const SCConfNode *mappings_root_node, bool failure_fatal) { SCConfNode *mapping_node = NULL; int mapping_cnt = 0; if (mappings_root_node != NULL) { TAILQ_FOREACH(mapping_node, &mappings_root_node->head, next) { SCConfNode *tenant_id_node = SCConfNodeLookupChild(mapping_node, "tenant-id"); if (tenant_id_node == NULL) goto bad_mapping; SCConfNode *vlan_id_node = SCConfNodeLookupChild(mapping_node, "vlan-id"); if (vlan_id_node == NULL) goto bad_mapping; uint32_t tenant_id = 0; if (StringParseUint32(&tenant_id, 10, (uint16_t)strlen(tenant_id_node->val), tenant_id_node->val) < 0) { SCLogError("tenant-id " "of %s is invalid", tenant_id_node->val); goto bad_mapping; } uint16_t vlan_id = 0; if (StringParseUint16( &vlan_id, 10, (uint16_t)strlen(vlan_id_node->val), vlan_id_node->val) < 0) { SCLogError("vlan-id " "of %s is invalid", vlan_id_node->val); goto bad_mapping; } if (vlan_id == 0 || vlan_id >= 4095) { SCLogError("vlan-id " "of %s is invalid. Valid range 1-4094.", vlan_id_node->val); goto bad_mapping; } if (DetectEngineTenantRegisterVlanId(tenant_id, vlan_id) != 0) { goto error; } SCLogConfig("vlan %u connected to tenant-id %u", vlan_id, tenant_id); mapping_cnt++; continue; bad_mapping: if (failure_fatal) goto error; } } return mapping_cnt; error: return 0; } /** * \brief setup multi-detect / multi-tenancy * * See if MT is enabled. If so, setup the selector, tenants and mappings. * Tenants and mappings are optional, and can also dynamically be added * and removed from the unix socket. */ int DetectEngineMultiTenantSetup(const bool unix_socket) { enum DetectEngineTenantSelectors tenant_selector = TENANT_SELECTOR_UNKNOWN; DetectEngineMasterCtx *master = &g_master_de_ctx; int failure_fatal = 0; (void)SCConfGetBool("engine.init-failure-fatal", &failure_fatal); int enabled = 0; (void)SCConfGetBool("multi-detect.enabled", &enabled); if (enabled == 1) { DetectLoadersInit(); TmModuleDetectLoaderRegister(); DetectLoaderThreadSpawn(); TmThreadContinueDetectLoaderThreads(); SCMutexLock(&master->lock); master->multi_tenant_enabled = 1; const char *handler = NULL; if (SCConfGet("multi-detect.selector", &handler) == 1) { SCLogConfig("multi-tenant selector type %s", handler); if (strcmp(handler, "vlan") == 0) { tenant_selector = master->tenant_selector = TENANT_SELECTOR_VLAN; int vlanbool = 0; if ((SCConfGetBool("vlan.use-for-tracking", &vlanbool)) == 1 && vlanbool == 0) { SCLogError("vlan tracking is disabled, " "can't use multi-detect selector 'vlan'"); SCMutexUnlock(&master->lock); goto error; } } else if (strcmp(handler, "direct") == 0) { tenant_selector = master->tenant_selector = TENANT_SELECTOR_DIRECT; } else if (strcmp(handler, "device") == 0) { tenant_selector = master->tenant_selector = TENANT_SELECTOR_LIVEDEV; if (EngineModeIsIPS()) { SCLogWarning("multi-tenant 'device' mode not supported for IPS"); SCMutexUnlock(&master->lock); goto error; } } else { SCLogError("unknown value %s " "multi-detect.selector", handler); SCMutexUnlock(&master->lock); goto error; } } SCMutexUnlock(&master->lock); SCLogConfig("multi-detect is enabled (multi tenancy). Selector: %s", handler); /* traffic -- tenant mappings */ SCConfNode *mappings_root_node = SCConfGetNode("multi-detect.mappings"); if (tenant_selector == TENANT_SELECTOR_VLAN) { int mapping_cnt = DetectEngineMultiTenantSetupLoadVlanMappings(mappings_root_node, failure_fatal); if (mapping_cnt == 0) { /* no mappings are valid when we're in unix socket mode, * they can be added on the fly. Otherwise warn/error * depending on failure_fatal */ if (unix_socket) { SCLogNotice("no tenant traffic mappings defined, " "tenants won't be used until mappings are added"); } else { if (failure_fatal) { SCLogError("no multi-detect mappings defined"); goto error; } else { SCLogWarning("no multi-detect mappings defined"); } } } } else if (tenant_selector == TENANT_SELECTOR_LIVEDEV) { int mapping_cnt = DetectEngineMultiTenantSetupLoadLivedevMappings(mappings_root_node, failure_fatal); if (mapping_cnt == 0) { if (failure_fatal) { SCLogError("no multi-detect mappings defined"); goto error; } else { SCLogWarning("no multi-detect mappings defined"); } } } /* tenants */ SCConfNode *tenants_root_node = SCConfGetNode("multi-detect.tenants"); SCConfNode *tenant_node = NULL; if (tenants_root_node != NULL) { const char *path = NULL; SCConfNode *path_node = SCConfGetNode("multi-detect.config-path"); if (path_node) { path = path_node->val; SCLogConfig("tenants config path: %s", path); } TAILQ_FOREACH(tenant_node, &tenants_root_node->head, next) { SCConfNode *id_node = SCConfNodeLookupChild(tenant_node, "id"); if (id_node == NULL) { goto bad_tenant; } SCConfNode *yaml_node = SCConfNodeLookupChild(tenant_node, "yaml"); if (yaml_node == NULL) { goto bad_tenant; } uint32_t tenant_id = 0; if (StringParseUint32( &tenant_id, 10, (uint16_t)strlen(id_node->val), id_node->val) < 0) { SCLogError("tenant_id " "of %s is invalid", id_node->val); goto bad_tenant; } SCLogDebug("tenant id: %u, %s", tenant_id, yaml_node->val); char yaml_path[PATH_MAX] = ""; if (path) { PathMerge(yaml_path, PATH_MAX, path, yaml_node->val); } else { strlcpy(yaml_path, yaml_node->val, sizeof(yaml_path)); } SCLogDebug("tenant path: %s", yaml_path); /* setup the yaml in this loop so that it's not done by the loader * threads. SCConfYamlLoadFileWithPrefix is not thread safe. */ char prefix[64]; snprintf(prefix, sizeof(prefix), "multi-detect.%u", tenant_id); if (SCConfYamlLoadFileWithPrefix(yaml_path, prefix) != 0) { SCLogError("failed to load yaml %s", yaml_path); goto bad_tenant; } int r = DetectLoaderSetupLoadTenant(tenant_id, yaml_path); if (r < 0) { /* error logged already */ goto bad_tenant; } continue; bad_tenant: if (failure_fatal) goto error; } } /* wait for our loaders to complete their tasks */ if (DetectLoadersSync() != 0) { goto error; } VarNameStoreActivate(); } else { SCLogDebug("multi-detect not enabled (multi tenancy)"); } return 0; error: return -1; } static uint32_t DetectEngineTenantGetIdFromVlanId(const void *ctx, const Packet *p) { const DetectEngineThreadCtx *det_ctx = ctx; uint32_t x = 0; uint32_t vlan_id = 0; if (p->vlan_idx == 0) return 0; vlan_id = p->vlan_id[0]; if (det_ctx == NULL || det_ctx->tenant_array == NULL || det_ctx->tenant_array_size == 0) return 0; /* not very efficient, but for now we're targeting only limited amounts. * Can use hash/tree approach later. */ for (x = 0; x < det_ctx->tenant_array_size; x++) { if (det_ctx->tenant_array[x].traffic_id == vlan_id) return det_ctx->tenant_array[x].tenant_id; } return 0; } static uint32_t DetectEngineTenantGetIdFromLivedev(const void *ctx, const Packet *p) { const DetectEngineThreadCtx *det_ctx = ctx; const LiveDevice *ld = p->livedev; if (ld == NULL || det_ctx == NULL) return 0; SCLogDebug("using tenant-id %u for packet on device %s", ld->tenant_id, ld->dev); return ld->tenant_id; } static int DetectEngineTenantRegisterSelector( enum DetectEngineTenantSelectors selector, uint32_t tenant_id, uint32_t traffic_id) { DetectEngineMasterCtx *master = &g_master_de_ctx; SCMutexLock(&master->lock); if (!(master->tenant_selector == TENANT_SELECTOR_UNKNOWN || master->tenant_selector == selector)) { SCLogInfo("conflicting selector already set"); SCMutexUnlock(&master->lock); return -1; } DetectEngineTenantMapping *m = master->tenant_mapping_list; while (m) { if (m->traffic_id == traffic_id) { SCLogInfo("traffic id already registered"); SCMutexUnlock(&master->lock); return -1; } m = m->next; } DetectEngineTenantMapping *map = SCCalloc(1, sizeof(*map)); if (map == NULL) { SCLogInfo("memory fail"); SCMutexUnlock(&master->lock); return -1; } map->traffic_id = traffic_id; map->tenant_id = tenant_id; map->next = master->tenant_mapping_list; master->tenant_mapping_list = map; master->tenant_selector = selector; SCLogDebug("tenant handler %u %u %u registered", selector, tenant_id, traffic_id); SCMutexUnlock(&master->lock); return 0; } static int DetectEngineTenantUnregisterSelector( enum DetectEngineTenantSelectors selector, uint32_t tenant_id, uint32_t traffic_id) { DetectEngineMasterCtx *master = &g_master_de_ctx; SCMutexLock(&master->lock); if (master->tenant_mapping_list == NULL) { SCMutexUnlock(&master->lock); return -1; } DetectEngineTenantMapping *prev = NULL; DetectEngineTenantMapping *map = master->tenant_mapping_list; while (map) { if (map->traffic_id == traffic_id && map->tenant_id == tenant_id) { if (prev != NULL) prev->next = map->next; else master->tenant_mapping_list = map->next; map->next = NULL; SCFree(map); SCLogInfo("tenant handler %u %u %u unregistered", selector, tenant_id, traffic_id); SCMutexUnlock(&master->lock); return 0; } prev = map; map = map->next; } SCMutexUnlock(&master->lock); return -1; } int DetectEngineTenantRegisterLivedev(uint32_t tenant_id, int device_id) { return DetectEngineTenantRegisterSelector( TENANT_SELECTOR_LIVEDEV, tenant_id, (uint32_t)device_id); } int DetectEngineTenantRegisterVlanId(uint32_t tenant_id, uint16_t vlan_id) { return DetectEngineTenantRegisterSelector(TENANT_SELECTOR_VLAN, tenant_id, (uint32_t)vlan_id); } int DetectEngineTenantUnregisterVlanId(uint32_t tenant_id, uint16_t vlan_id) { return DetectEngineTenantUnregisterSelector(TENANT_SELECTOR_VLAN, tenant_id, (uint32_t)vlan_id); } int DetectEngineTenantRegisterPcapFile(uint32_t tenant_id) { SCLogInfo("registering %u %d 0", TENANT_SELECTOR_DIRECT, tenant_id); return DetectEngineTenantRegisterSelector(TENANT_SELECTOR_DIRECT, tenant_id, 0); } int DetectEngineTenantUnregisterPcapFile(uint32_t tenant_id) { SCLogInfo("unregistering %u %d 0", TENANT_SELECTOR_DIRECT, tenant_id); return DetectEngineTenantUnregisterSelector(TENANT_SELECTOR_DIRECT, tenant_id, 0); } static uint32_t DetectEngineTenantGetIdFromPcap(const void *ctx, const Packet *p) { return p->pcap_v.tenant_id; } DetectEngineCtx *DetectEngineGetByTenantId(uint32_t tenant_id) { DetectEngineMasterCtx *master = &g_master_de_ctx; SCMutexLock(&master->lock); if (master->list == NULL) { SCMutexUnlock(&master->lock); return NULL; } DetectEngineCtx *de_ctx = master->list; while (de_ctx) { if (de_ctx->type == DETECT_ENGINE_TYPE_TENANT && de_ctx->tenant_id == tenant_id) { de_ctx->ref_cnt++; break; } de_ctx = de_ctx->next; } SCMutexUnlock(&master->lock); return de_ctx; } void DetectEngineDeReference(DetectEngineCtx **de_ctx) { BUG_ON((*de_ctx)->ref_cnt == 0); (*de_ctx)->ref_cnt--; *de_ctx = NULL; } static int DetectEngineAddToList(DetectEngineCtx *instance) { DetectEngineMasterCtx *master = &g_master_de_ctx; if (instance == NULL) return -1; if (master->list == NULL) { master->list = instance; } else { instance->next = master->list; master->list = instance; } return 0; } int DetectEngineAddToMaster(DetectEngineCtx *de_ctx) { int r; if (de_ctx == NULL) return -1; SCLogDebug("adding de_ctx %p to master", de_ctx); DetectEngineMasterCtx *master = &g_master_de_ctx; SCMutexLock(&master->lock); r = DetectEngineAddToList(de_ctx); SCMutexUnlock(&master->lock); return r; } static int DetectEngineMoveToFreeListNoLock(DetectEngineMasterCtx *master, DetectEngineCtx *de_ctx) { DetectEngineCtx *instance = master->list; if (instance == NULL) { return -1; } /* remove from active list */ if (instance == de_ctx) { master->list = instance->next; } else { DetectEngineCtx *prev = instance; instance = instance->next; /* already checked first element */ while (instance) { DetectEngineCtx *next = instance->next; if (instance == de_ctx) { prev->next = instance->next; break; } prev = instance; instance = next; } if (instance == NULL) { return -1; } } /* instance is now detached from list */ instance->next = NULL; /* add to free list */ if (master->free_list == NULL) { master->free_list = instance; } else { instance->next = master->free_list; master->free_list = instance; } SCLogDebug("detect engine %p moved to free list (%u refs)", de_ctx, de_ctx->ref_cnt); return 0; } int DetectEngineMoveToFreeList(DetectEngineCtx *de_ctx) { int ret = 0; DetectEngineMasterCtx *master = &g_master_de_ctx; SCMutexLock(&master->lock); ret = DetectEngineMoveToFreeListNoLock(master, de_ctx); SCMutexUnlock(&master->lock); return ret; } void DetectEnginePruneFreeList(void) { DetectEngineMasterCtx *master = &g_master_de_ctx; SCMutexLock(&master->lock); DetectEngineCtx *prev = NULL; DetectEngineCtx *instance = master->free_list; while (instance) { DetectEngineCtx *next = instance->next; SCLogDebug("detect engine %p has %u ref(s)", instance, instance->ref_cnt); if (instance->ref_cnt == 0) { if (prev == NULL) { master->free_list = next; } else { prev->next = next; } SCLogDebug("freeing detect engine %p", instance); DetectEngineCtxFree(instance); instance = NULL; } prev = instance; instance = next; } SCMutexUnlock(&master->lock); } void DetectEngineClearMaster(void) { DetectEngineMasterCtx *master = &g_master_de_ctx; SCMutexLock(&master->lock); DetectEngineCtx *instance = master->list; while (instance) { DetectEngineCtx *next = instance->next; DEBUG_VALIDATE_BUG_ON(instance->ref_cnt); SCLogDebug("detect engine %p has %u ref(s)", instance, instance->ref_cnt); instance->ref_cnt = 0; DetectEngineMoveToFreeListNoLock(master, instance); instance = next; } SCMutexUnlock(&master->lock); DetectEnginePruneFreeList(); } static int reloads = 0; /** \brief Reload the detection engine * * \param filename YAML file to load for the detect config * * \retval -1 error * \retval 0 ok */ int DetectEngineReload(const SCInstance *suri) { DetectEngineCtx *new_de_ctx = NULL; DetectEngineCtx *old_de_ctx = NULL; char prefix[128]; memset(prefix, 0, sizeof(prefix)); SCLogNotice("rule reload starting"); if (suri->conf_filename != NULL) { snprintf(prefix, sizeof(prefix), "detect-engine-reloads.%d", reloads++); SCLogConfig("Reloading %s", suri->conf_filename); if (SCConfYamlLoadFileWithPrefix(suri->conf_filename, prefix) != 0) { SCLogError("failed to load yaml %s", suri->conf_filename); return -1; } SCConfNode *node = SCConfGetNode(prefix); if (node == NULL) { SCLogError("failed to properly setup yaml %s", suri->conf_filename); return -1; } if (suri->additional_configs) { for (int i = 0; suri->additional_configs[i] != NULL; i++) { SCLogConfig("Reloading %s", suri->additional_configs[i]); SCConfYamlHandleInclude(node, suri->additional_configs[i]); } } #if 0 SCConfDump(); #endif } /* get a reference to the current de_ctx */ old_de_ctx = DetectEngineGetCurrent(); if (old_de_ctx == NULL) return -1; SCLogDebug("get ref to old_de_ctx %p", old_de_ctx); DatasetReload(); /* only reload a regular 'normal' and 'delayed detect stub' detect engines */ if (!(old_de_ctx->type == DETECT_ENGINE_TYPE_NORMAL || old_de_ctx->type == DETECT_ENGINE_TYPE_DD_STUB)) { DetectEngineDeReference(&old_de_ctx); SCLogNotice("rule reload complete"); return -1; } /* get new detection engine */ new_de_ctx = DetectEngineCtxInitWithPrefix(prefix, old_de_ctx->tenant_id); if (new_de_ctx == NULL) { SCLogError("initializing detection engine " "context failed."); DetectEngineDeReference(&old_de_ctx); return -1; } if (SigLoadSignatures(new_de_ctx, suri->sig_file, suri->sig_file_exclusive) != 0) { DetectEngineCtxFree(new_de_ctx); DetectEngineDeReference(&old_de_ctx); return -1; } SCLogDebug("set up new_de_ctx %p", new_de_ctx); /* Copy over callbacks. */ new_de_ctx->RateFilterCallback = old_de_ctx->RateFilterCallback; new_de_ctx->rate_filter_callback_arg = old_de_ctx->rate_filter_callback_arg; /* add to master */ DetectEngineAddToMaster(new_de_ctx); /* move to old free list */ DetectEngineMoveToFreeList(old_de_ctx); DetectEngineDeReference(&old_de_ctx); SCLogDebug("going to reload the threads to use new_de_ctx %p", new_de_ctx); /* update the threads */ DetectEngineReloadThreads(new_de_ctx); SCLogDebug("threads now run new_de_ctx %p", new_de_ctx); /* walk free list, freeing the old_de_ctx */ DetectEnginePruneFreeList(); DatasetPostReloadCleanup(); DetectEngineBumpVersion(); SCLogDebug("old_de_ctx should have been freed"); SCLogNotice("rule reload complete"); #ifdef HAVE_MALLOC_TRIM /* The reload process potentially frees up large amounts of memory. * Encourage the memory management system to reclaim as much as it * can. */ malloc_trim(0); #endif return 0; } static uint32_t TenantIdHash(HashTable *h, void *data, uint16_t data_len) { DetectEngineThreadCtx *det_ctx = (DetectEngineThreadCtx *)data; return det_ctx->tenant_id % h->array_size; } static char TenantIdCompare(void *d1, uint16_t d1_len, void *d2, uint16_t d2_len) { DetectEngineThreadCtx *det1 = (DetectEngineThreadCtx *)d1; DetectEngineThreadCtx *det2 = (DetectEngineThreadCtx *)d2; return (det1->tenant_id == det2->tenant_id); } static void TenantIdFree(void *d) { DetectEngineThreadCtxFree(d); } int DetectEngineMTApply(void) { DetectEngineMasterCtx *master = &g_master_de_ctx; SCMutexLock(&master->lock); if (master->tenant_selector == TENANT_SELECTOR_UNKNOWN) { SCLogInfo("error, no tenant selector"); SCMutexUnlock(&master->lock); return -1; } DetectEngineCtx *stub_de_ctx = NULL; DetectEngineCtx *list = master->list; for ( ; list != NULL; list = list->next) { SCLogDebug("list %p tenant %u", list, list->tenant_id); if (list->type == DETECT_ENGINE_TYPE_NORMAL || list->type == DETECT_ENGINE_TYPE_MT_STUB || list->type == DETECT_ENGINE_TYPE_DD_STUB) { stub_de_ctx = list; break; } } if (stub_de_ctx == NULL) { stub_de_ctx = DetectEngineCtxInitStubForMT(); if (stub_de_ctx == NULL) { SCMutexUnlock(&master->lock); return -1; } if (master->list == NULL) { master->list = stub_de_ctx; } else { stub_de_ctx->next = master->list; master->list = stub_de_ctx; } } /* update the threads */ SCLogDebug("MT reload starting"); DetectEngineReloadThreads(stub_de_ctx); SCLogDebug("MT reload done"); SCMutexUnlock(&master->lock); /* walk free list, freeing the old_de_ctx */ DetectEnginePruneFreeList(); // needed for VarNameStoreFree DetectEngineBumpVersion(); SCLogDebug("old_de_ctx should have been freed"); return 0; } static int g_parse_metadata = 0; void DetectEngineSetParseMetadata(void) { g_parse_metadata = 1; } void DetectEngineUnsetParseMetadata(void) { g_parse_metadata = 0; } int DetectEngineMustParseMetadata(void) { return g_parse_metadata; } const char *DetectSigmatchListEnumToString(enum DetectSigmatchListEnum type) { switch (type) { case DETECT_SM_LIST_MATCH: return "packet"; case DETECT_SM_LIST_PMATCH: return "packet/stream payload"; case DETECT_SM_LIST_TMATCH: return "tag"; case DETECT_SM_LIST_BASE64_DATA: return "base64_data"; case DETECT_SM_LIST_POSTMATCH: return "post-match"; case DETECT_SM_LIST_SUPPRESS: return "suppress"; case DETECT_SM_LIST_THRESHOLD: return "threshold"; case DETECT_SM_LIST_MAX: return "max (internal)"; } return "error"; } /* events api */ void DetectEngineSetEvent(DetectEngineThreadCtx *det_ctx, uint8_t e) { AppLayerDecoderEventsSetEventRaw(&det_ctx->decoder_events, e); det_ctx->events++; } bool DetectMd5ValidateCallback( const Signature *s, const char **sigerror, const DetectBufferType *map) { for (uint32_t x = 0; x < s->init_data->buffer_index; x++) { if (s->init_data->buffers[x].id != (uint32_t)map->id) continue; const SigMatch *sm = s->init_data->buffers[x].head; for (; sm != NULL; sm = sm->next) { if (sm->type != DETECT_CONTENT) continue; const DetectContentData *cd = (DetectContentData *)sm->ctx; if (cd->flags & DETECT_CONTENT_NOCASE) { *sigerror = "md5-like keyword should not be used together with " "nocase, since the rule is automatically " "lowercased anyway which makes nocase redundant."; SCLogWarning("rule %u: buffer %s: %s", s->id, map->name, *sigerror); } if (cd->content_len != SC_MD5_HEX_LEN) { *sigerror = "Invalid length for md5-like keyword (should " "be 32 characters long). This rule will therefore " "never match."; SCLogError("rule %u: buffer %s: %s", s->id, map->name, *sigerror); return false; } for (size_t i = 0; i < cd->content_len; ++i) { if (!isxdigit(cd->content[i])) { *sigerror = "Invalid md5-like string (should be string of hexadecimal characters)." "This rule will therefore never match."; SCLogWarning("rule %u: buffer %s: %s", s->id, map->name, *sigerror); return false; } } } } return true; } void SCDetectEngineRegisterRateFilterCallback(SCDetectRateFilterFunc fn, void *arg) { DetectEngineCtx *de_ctx = DetectEngineGetCurrent(); de_ctx->RateFilterCallback = fn; de_ctx->rate_filter_callback_arg = arg; DetectEngineDeReference(&de_ctx); } int DetectEngineThreadCtxGetJsonContext(DetectEngineThreadCtx *det_ctx) { if (det_ctx->json_content_len > SIG_JSON_CONTENT_ARRAY_LEN - 1) { SCLogDebug("json content length %u exceeds maximum %u", det_ctx->json_content_len, SIG_JSON_CONTENT_ARRAY_LEN); return -1; } if (det_ctx->json_content_len >= det_ctx->json_content_capacity) { if (det_ctx->json_content_capacity == 0) { det_ctx->json_content_capacity = 1; } else { det_ctx->json_content_capacity *= 2; } void *tmp = SCRealloc( det_ctx->json_content, det_ctx->json_content_capacity * sizeof(SigJsonContent)); if (unlikely(tmp == NULL)) { return -1; } SCLogDebug("reallocated json content array to %u items", det_ctx->json_content_capacity); det_ctx->json_content = tmp; } return 0; } /*************************************Unittest*********************************/ #ifdef UNITTESTS static int DetectEngineInitYamlConf(const char *conf) { SCConfCreateContextBackup(); SCConfInit(); return SCConfYamlLoadString(conf, strlen(conf)); } static void DetectEngineDeInitYamlConf(void) { SCConfDeInit(); SCConfRestoreContextBackup(); } static int DetectEngineTest01(void) { const char *conf = "%YAML 1.1\n" "---\n" "detect-engine:\n" " - profile: medium\n" " - custom-values:\n" " toclient_src_groups: 2\n" " toclient_dst_groups: 2\n" " toclient_sp_groups: 2\n" " toclient_dp_groups: 3\n" " toserver_src_groups: 2\n" " toserver_dst_groups: 4\n" " toserver_sp_groups: 2\n" " toserver_dp_groups: 25\n" " - inspection-recursion-limit: 0\n"; FAIL_IF(DetectEngineInitYamlConf(conf) == -1); DetectEngineCtx *de_ctx = DetectEngineCtxInit(); FAIL_IF_NULL(de_ctx); FAIL_IF_NOT(de_ctx->inspection_recursion_limit == -1); DetectEngineCtxFree(de_ctx); DetectEngineDeInitYamlConf(); PASS; } static int DetectEngineTest02(void) { const char *conf = "%YAML 1.1\n" "---\n" "detect-engine:\n" " - profile: medium\n" " - custom-values:\n" " toclient_src_groups: 2\n" " toclient_dst_groups: 2\n" " toclient_sp_groups: 2\n" " toclient_dp_groups: 3\n" " toserver_src_groups: 2\n" " toserver_dst_groups: 4\n" " toserver_sp_groups: 2\n" " toserver_dp_groups: 25\n" " - inspection-recursion-limit:\n"; FAIL_IF(DetectEngineInitYamlConf(conf) == -1); DetectEngineCtx *de_ctx = DetectEngineCtxInit(); FAIL_IF_NULL(de_ctx); FAIL_IF_NOT( de_ctx->inspection_recursion_limit == DETECT_ENGINE_DEFAULT_INSPECTION_RECURSION_LIMIT); DetectEngineCtxFree(de_ctx); DetectEngineDeInitYamlConf(); PASS; } static int DetectEngineTest03(void) { const char *conf = "%YAML 1.1\n" "---\n" "detect-engine:\n" " - profile: medium\n" " - custom-values:\n" " toclient_src_groups: 2\n" " toclient_dst_groups: 2\n" " toclient_sp_groups: 2\n" " toclient_dp_groups: 3\n" " toserver_src_groups: 2\n" " toserver_dst_groups: 4\n" " toserver_sp_groups: 2\n" " toserver_dp_groups: 25\n"; FAIL_IF(DetectEngineInitYamlConf(conf) == -1); DetectEngineCtx *de_ctx = DetectEngineCtxInit(); FAIL_IF_NULL(de_ctx); FAIL_IF_NOT( de_ctx->inspection_recursion_limit == DETECT_ENGINE_DEFAULT_INSPECTION_RECURSION_LIMIT); DetectEngineCtxFree(de_ctx); DetectEngineDeInitYamlConf(); PASS; } static int DetectEngineTest04(void) { const char *conf = "%YAML 1.1\n" "---\n" "detect-engine:\n" " - profile: medium\n" " - custom-values:\n" " toclient_src_groups: 2\n" " toclient_dst_groups: 2\n" " toclient_sp_groups: 2\n" " toclient_dp_groups: 3\n" " toserver_src_groups: 2\n" " toserver_dst_groups: 4\n" " toserver_sp_groups: 2\n" " toserver_dp_groups: 25\n" " - inspection-recursion-limit: 10\n"; FAIL_IF(DetectEngineInitYamlConf(conf) == -1); DetectEngineCtx *de_ctx = DetectEngineCtxInit(); FAIL_IF_NULL(de_ctx); FAIL_IF_NOT(de_ctx->inspection_recursion_limit == 10); DetectEngineCtxFree(de_ctx); DetectEngineDeInitYamlConf(); PASS; } static int DetectEngineTest08(void) { const char *conf = "%YAML 1.1\n" "---\n" "detect-engine:\n" " - profile: custom\n" " - custom-values:\n" " toclient-groups: 23\n" " toserver-groups: 27\n"; FAIL_IF(DetectEngineInitYamlConf(conf) == -1); DetectEngineCtx *de_ctx = DetectEngineCtxInit(); FAIL_IF_NULL(de_ctx); FAIL_IF_NOT(de_ctx->max_uniq_toclient_groups == 23); FAIL_IF_NOT(de_ctx->max_uniq_toserver_groups == 27); DetectEngineCtxFree(de_ctx); DetectEngineDeInitYamlConf(); PASS; } /** \test bug 892 bad values */ static int DetectEngineTest09(void) { const char *conf = "%YAML 1.1\n" "---\n" "detect-engine:\n" " - profile: custom\n" " - custom-values:\n" " toclient-groups: BA\n" " toserver-groups: BA\n" " - inspection-recursion-limit: 10\n"; FAIL_IF(DetectEngineInitYamlConf(conf) == -1); DetectEngineCtx *de_ctx = DetectEngineCtxInit(); FAIL_IF_NULL(de_ctx); FAIL_IF_NOT(de_ctx->max_uniq_toclient_groups == 20); FAIL_IF_NOT(de_ctx->max_uniq_toserver_groups == 40); DetectEngineCtxFree(de_ctx); DetectEngineDeInitYamlConf(); PASS; } #endif void DetectEngineRegisterTests(void) { #ifdef UNITTESTS UtRegisterTest("DetectEngineTest01", DetectEngineTest01); UtRegisterTest("DetectEngineTest02", DetectEngineTest02); UtRegisterTest("DetectEngineTest03", DetectEngineTest03); UtRegisterTest("DetectEngineTest04", DetectEngineTest04); UtRegisterTest("DetectEngineTest08", DetectEngineTest08); UtRegisterTest("DetectEngineTest09", DetectEngineTest09); #endif }