summaryrefslogtreecommitdiffstats
path: root/src/detect-engine.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:39:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:39:49 +0000
commita0aa2307322cd47bbf416810ac0292925e03be87 (patch)
tree37076262a026c4b48c8a0e84f44ff9187556ca35 /src/detect-engine.c
parentInitial commit. (diff)
downloadsuricata-a0aa2307322cd47bbf416810ac0292925e03be87.tar.xz
suricata-a0aa2307322cd47bbf416810ac0292925e03be87.zip
Adding upstream version 1:7.0.3.upstream/1%7.0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/detect-engine.c')
-rw-r--r--src/detect-engine.c5151
1 files changed, 5151 insertions, 0 deletions
diff --git a/src/detect-engine.c b/src/detect-engine.c
new file mode 100644
index 0000000..c863663
--- /dev/null
+++ b/src/detect-engine.c
@@ -0,0 +1,5151 @@
+/* Copyright (C) 2007-2022 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Victor Julien <victor@inliniac.net>
+ */
+
+#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-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 "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.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
+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, },
+};
+// clang-format on
+
+/** \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 */
+void DetectFrameInspectEngineRegister(const char *name, int dir,
+ InspectionBufferFrameInspectFunc Callback, AppProto alproto, uint8_t type)
+{
+ 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);
+ }
+
+ 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 (g_frame_inspect_engines == NULL) {
+ g_frame_inspect_engines = new_engine;
+ } else {
+ DetectEngineFrameInspectionEngine *t = g_frame_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 */
+void DetectAppLayerInspectEngineRegister2(const char *name,
+ AppProto alproto, uint32_t dir, int progress,
+ InspectEngineFuncPtr2 Callback2,
+ InspectionBufferGetDataPtr GetData)
+{
+ 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) ||
+ (Callback2 == NULL))
+ {
+ SCLogError("Invalid arguments");
+ BUG_ON(1);
+ } else if (Callback2 == DetectEngineInspectBufferGeneric && GetData == NULL) {
+ SCLogError("Invalid arguments: must register "
+ "GetData with DetectEngineInspectBufferGeneric");
+ BUG_ON(1);
+ }
+
+ uint8_t direction;
+ if (dir == SIG_FLAG_TOSERVER) {
+ direction = 0;
+ } else {
+ direction = 1;
+ }
+
+ DetectEngineAppInspectionEngine *new_engine = SCMalloc(sizeof(DetectEngineAppInspectionEngine));
+ if (unlikely(new_engine == NULL)) {
+ exit(EXIT_FAILURE);
+ }
+ memset(new_engine, 0, sizeof(*new_engine));
+ 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 = Callback2;
+ new_engine->v2.GetData = GetData;
+
+ 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;
+ }
+}
+
+/* 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->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;
+
+ 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) {
+ AppendAppInspectEngine(
+ de_ctx, t, s, smd, 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 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);
+ hash += hashlittle_safe((uint8_t *)&map->transforms, sizeof(map->transforms), 0);
+ hash %= ht->array_size;
+ 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);
+ r &= (memcmp((uint8_t *)&map1->transforms, (uint8_t *)&map2->transforms, sizeof(map2->transforms)) == 0);
+ 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;
+ return;
+}
+#endif
+static int DetectBufferTypeAdd(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 = 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))
+{
+ 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);
+ if (map && map->ValidateCallback) {
+ return map->ValidateCallback(s, sigerror);
+ }
+ return true;
+}
+
+SigMatch *DetectBufferGetFirstSigMatch(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 s->init_data->buffers[i].head;
+ }
+ }
+ return NULL;
+}
+
+SigMatch *DetectBufferGetLastSigMatch(const Signature *s, const uint32_t buf_id)
+{
+ SigMatch *last = NULL;
+ for (uint32_t i = 0; i < s->init_data->buffer_index; i++) {
+ if (buf_id == s->init_data->buffers[i].id) {
+ last = s->init_data->buffers[i].tail;
+ }
+ }
+ return last;
+}
+
+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;
+}
+
+int DetectBufferSetActiveList(DetectEngineCtx *de_ctx, Signature *s, const int list)
+{
+ BUG_ON(s->init_data == NULL);
+
+ if (s->init_data->list == DETECT_SM_LIST_BASE64_DATA) {
+ SCLogError("Rule buffer cannot be reset after base64_data.");
+ return -1;
+ }
+
+ if (s->init_data->list && s->init_data->transforms.cnt) {
+ SCLogError("no matches following transform(s)");
+ return -1;
+ }
+ s->init_data->list = list;
+ s->init_data->list_set = true;
+
+ // check if last has matches -> if no, error
+ if (s->init_data->curbuf && s->init_data->curbuf->head == NULL) {
+ SCLogError("previous sticky buffer has no matches");
+ return -1;
+ }
+
+ for (uint32_t x = 0; x < s->init_data->buffers_size; x++) {
+ SignatureInitDataBuffer *b = &s->init_data->buffers[x];
+ for (SigMatch *sm = b->head; sm != NULL; sm = sm->next) {
+ SCLogDebug(
+ "buf:%p: id:%u: '%s' pos %u", b, b->id, sigmatch_table[sm->type].name, sm->idx);
+ }
+ if ((uint32_t)list == b->id) {
+ SCLogDebug("found buffer %p for list %d", b, list);
+ if (s->init_data->buffers[x].sm_init) {
+ s->init_data->buffers[x].sm_init = false;
+ SCLogDebug("sm_init was true for %p list %d", b, list);
+ s->init_data->curbuf = b;
+ return 0;
+
+ } else if (DetectEngineBufferTypeSupportsMultiInstanceGetById(de_ctx, list)) {
+ // fall through
+ } else {
+ SCLogWarning("duplicate instance for %s in '%s'",
+ DetectEngineBufferTypeGetNameById(de_ctx, list), s->sig_str);
+ s->init_data->curbuf = b;
+ return 0;
+ }
+ }
+ }
+
+ if (list < DETECT_SM_LIST_MAX)
+ return 0;
+
+ if (SignatureInitDataBufferCheckExpand(s) < 0) {
+ SCLogError("failed to expand rule buffer array");
+ return -1;
+ }
+
+ /* initialize new buffer */
+ s->init_data->curbuf = &s->init_data->buffers[s->init_data->buffer_index++];
+ s->init_data->curbuf->id = list;
+ s->init_data->curbuf->head = NULL;
+ s->init_data->curbuf->tail = NULL;
+ s->init_data->curbuf->multi_capable =
+ DetectEngineBufferTypeSupportsMultiInstanceGetById(de_ctx, list);
+ SCLogDebug("new: idx %u list %d set up curbuf %p", s->init_data->buffer_index - 1, list,
+ s->init_data->curbuf);
+
+ return 0;
+}
+
+int DetectBufferGetActiveList(DetectEngineCtx *de_ctx, Signature *s)
+{
+ BUG_ON(s->init_data == NULL);
+
+ if (s->init_data->list && s->init_data->transforms.cnt) {
+ if (s->init_data->list == DETECT_SM_LIST_NOTSET ||
+ s->init_data->list < DETECT_SM_LIST_DYNAMIC_START) {
+ SCLogError("previous transforms not consumed "
+ "(list: %u, transform_cnt %u)",
+ s->init_data->list, s->init_data->transforms.cnt);
+ SCReturnInt(-1);
+ }
+
+ SCLogDebug("buffer %d has transform(s) registered: %d",
+ s->init_data->list, s->init_data->transforms.cnt);
+ int new_list = DetectEngineBufferTypeGetByIdTransforms(de_ctx, s->init_data->list,
+ s->init_data->transforms.transforms, s->init_data->transforms.cnt);
+ if (new_list == -1) {
+ SCReturnInt(-1);
+ }
+ int base_list = s->init_data->list;
+ SCLogDebug("new_list %d", new_list);
+ s->init_data->list = new_list;
+ s->init_data->list_set = false;
+ // reset transforms now that we've set up the list
+ s->init_data->transforms.cnt = 0;
+
+ if (s->init_data->curbuf && s->init_data->curbuf->head != NULL) {
+ if (SignatureInitDataBufferCheckExpand(s) < 0) {
+ SCLogError("failed to expand rule buffer array");
+ return -1;
+ }
+ s->init_data->curbuf = &s->init_data->buffers[s->init_data->buffer_index++];
+ s->init_data->curbuf->multi_capable =
+ DetectEngineBufferTypeSupportsMultiInstanceGetById(de_ctx, base_list);
+ }
+ if (s->init_data->curbuf == NULL) {
+ SCLogError("failed to setup buffer");
+ DEBUG_VALIDATE_BUG_ON(1);
+ SCReturnInt(-1);
+ }
+ s->init_data->curbuf->id = new_list;
+ SCLogDebug("new list after applying transforms: %u", new_list);
+ }
+
+ SCReturnInt(0);
+}
+
+void InspectionBufferClean(DetectEngineThreadCtx *det_ctx)
+{
+ /* single buffers */
+ for (uint32_t i = 0; i < det_ctx->inspect.to_clear_idx; i++)
+ {
+ const uint32_t idx = det_ctx->inspect.to_clear_queue[i];
+ InspectionBuffer *buffer = &det_ctx->inspect.buffers[idx];
+ buffer->inspect = NULL;
+ buffer->initialized = false;
+ }
+ det_ctx->inspect.to_clear_idx = 0;
+
+ /* multi buffers */
+ for (uint32_t i = 0; i < det_ctx->multi_inspect.to_clear_idx; i++)
+ {
+ const uint32_t idx = det_ctx->multi_inspect.to_clear_queue[i];
+ InspectionBufferMultipleForList *mbuffer = &det_ctx->multi_inspect.buffers[idx];
+ for (uint32_t x = 0; x <= mbuffer->max; x++) {
+ InspectionBuffer *buffer = &mbuffer->inspection_buffers[x];
+ buffer->inspect = NULL;
+ buffer->initialized = false;
+ }
+ mbuffer->init = 0;
+ mbuffer->max = 0;
+ }
+ det_ctx->multi_inspect.to_clear_idx = 0;
+}
+
+InspectionBuffer *InspectionBufferGet(DetectEngineThreadCtx *det_ctx, const int list_id)
+{
+ return &det_ctx->inspect.buffers[list_id];
+}
+
+static InspectionBufferMultipleForList *InspectionBufferGetMulti(
+ DetectEngineThreadCtx *det_ctx, const int list_id)
+{
+ InspectionBufferMultipleForList *buffer = &det_ctx->multi_inspect.buffers[list_id];
+ if (!buffer->init) {
+ det_ctx->multi_inspect.to_clear_queue[det_ctx->multi_inspect.to_clear_idx++] = list_id;
+ buffer->init = 1;
+ }
+ return buffer;
+}
+
+/** \brief for a InspectionBufferMultipleForList get a InspectionBuffer
+ * \param fb the multiple buffer array
+ * \param local_id the index to get a buffer
+ * \param buffer the inspect buffer or NULL in case of error */
+InspectionBuffer *InspectionBufferMultipleForListGet(
+ DetectEngineThreadCtx *det_ctx, const int list_id, const uint32_t local_id)
+{
+ if (unlikely(local_id >= 1024)) {
+ DetectEngineSetEvent(det_ctx, DETECT_EVENT_TOO_MANY_BUFFERS);
+ return NULL;
+ }
+
+ InspectionBufferMultipleForList *fb = InspectionBufferGetMulti(det_ctx, list_id);
+
+ if (local_id >= fb->size) {
+ uint32_t old_size = fb->size;
+ uint32_t new_size = local_id + 1;
+ uint32_t grow_by = new_size - old_size;
+ SCLogDebug("size is %u, need %u, so growing by %u", old_size, new_size, grow_by);
+
+ SCLogDebug("fb->inspection_buffers %p", fb->inspection_buffers);
+ void *ptr = SCRealloc(fb->inspection_buffers, (local_id + 1) * sizeof(InspectionBuffer));
+ if (ptr == NULL)
+ return NULL;
+
+ InspectionBuffer *to_zero = (InspectionBuffer *)ptr + old_size;
+ SCLogDebug("ptr %p to_zero %p", ptr, to_zero);
+ memset((uint8_t *)to_zero, 0, (grow_by * sizeof(InspectionBuffer)));
+ fb->inspection_buffers = ptr;
+ fb->size = new_size;
+ }
+
+ fb->max = MAX(fb->max, local_id);
+ InspectionBuffer *buffer = &fb->inspection_buffers[local_id];
+ SCLogDebug("using buffer %p", buffer);
+#ifdef DEBUG_VALIDATION
+ buffer->multi = true;
+#endif
+ return buffer;
+}
+
+void InspectionBufferInit(InspectionBuffer *buffer, uint32_t initial_size)
+{
+ memset(buffer, 0, sizeof(*buffer));
+ buffer->buf = SCCalloc(initial_size, sizeof(uint8_t));
+ if (buffer->buf != NULL) {
+ buffer->size = initial_size;
+ }
+}
+
+/** \brief setup the buffer empty */
+void InspectionBufferSetupMultiEmpty(InspectionBuffer *buffer)
+{
+#ifdef DEBUG_VALIDATION
+ DEBUG_VALIDATE_BUG_ON(buffer->initialized);
+ DEBUG_VALIDATE_BUG_ON(!buffer->multi);
+#endif
+ buffer->inspect = NULL;
+ buffer->inspect_len = 0;
+ buffer->len = 0;
+ buffer->initialized = true;
+}
+
+/** \brief setup the buffer with our initial data */
+void InspectionBufferSetupMulti(InspectionBuffer *buffer, const DetectEngineTransforms *transforms,
+ const uint8_t *data, const uint32_t data_len)
+{
+#ifdef DEBUG_VALIDATION
+ DEBUG_VALIDATE_BUG_ON(!buffer->multi);
+#endif
+ buffer->inspect = buffer->orig = data;
+ buffer->inspect_len = buffer->orig_len = data_len;
+ buffer->len = 0;
+ buffer->initialized = true;
+
+ InspectionBufferApplyTransforms(buffer, transforms);
+}
+
+/** \brief setup the buffer with our initial data */
+void InspectionBufferSetup(DetectEngineThreadCtx *det_ctx, const int list_id,
+ InspectionBuffer *buffer, const uint8_t *data, const uint32_t data_len)
+{
+#ifdef DEBUG_VALIDATION
+ DEBUG_VALIDATE_BUG_ON(buffer->multi);
+ DEBUG_VALIDATE_BUG_ON(buffer != InspectionBufferGet(det_ctx, list_id));
+#endif
+ if (buffer->inspect == NULL) {
+#ifdef UNITTESTS
+ if (det_ctx && list_id != -1)
+#endif
+ det_ctx->inspect.to_clear_queue[det_ctx->inspect.to_clear_idx++] = list_id;
+ }
+ buffer->inspect = buffer->orig = data;
+ buffer->inspect_len = buffer->orig_len = data_len;
+ buffer->len = 0;
+ buffer->initialized = true;
+}
+
+void InspectionBufferFree(InspectionBuffer *buffer)
+{
+ if (buffer->buf != NULL) {
+ SCFree(buffer->buf);
+ }
+ memset(buffer, 0, sizeof(*buffer));
+}
+
+/**
+ * \brief make sure that the buffer has at least 'min_size' bytes
+ * Expand the buffer if necessary
+ */
+void InspectionBufferCheckAndExpand(InspectionBuffer *buffer, uint32_t min_size)
+{
+ if (likely(buffer->size >= min_size))
+ return;
+
+ uint32_t new_size = (buffer->size == 0) ? 4096 : buffer->size;
+ while (new_size < min_size) {
+ new_size *= 2;
+ }
+
+ void *ptr = SCRealloc(buffer->buf, new_size);
+ if (ptr != NULL) {
+ buffer->buf = ptr;
+ buffer->size = new_size;
+ }
+}
+
+void InspectionBufferCopy(InspectionBuffer *buffer, uint8_t *buf, uint32_t buf_len)
+{
+ InspectionBufferCheckAndExpand(buffer, buf_len);
+
+ if (buffer->size) {
+ uint32_t copy_size = MIN(buf_len, buffer->size);
+ memcpy(buffer->buf, buf, copy_size);
+ buffer->inspect = buffer->buf;
+ buffer->inspect_len = copy_size;
+ buffer->initialized = true;
+ }
+}
+
+/** \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;
+}
+
+void InspectionBufferApplyTransforms(InspectionBuffer *buffer,
+ const DetectEngineTransforms *transforms)
+{
+ if (transforms) {
+ for (int i = 0; i < DETECT_TRANSFORMS_MAX; i++) {
+ const int id = transforms->transforms[i].transform;
+ if (id == 0)
+ break;
+ BUG_ON(sigmatch_table[id].Transform == NULL);
+ sigmatch_table[id].Transform(buffer, transforms->transforms[i].options);
+ SCLogDebug("applied transform %s", sigmatch_table[id].name);
+ }
+ }
+}
+
+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;
+ 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 false;
+ }
+ KEYWORD_PROFILING_END(det_ctx, smd->type, 1);
+ if (smd->is_last) {
+ SCLogDebug("match and is_last");
+ break;
+ }
+ smd++;
+ }
+ return true;
+}
+
+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) {
+ det_ctx->flags |= DETECT_ENGINE_THREAD_CTX_STREAM_CONTENT_MATCH;
+ *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 false;
+ }
+ if (DetectEngineInspectPacketPayload(de_ctx, det_ctx, s, p->flow, p) != 1) {
+ return false;
+ }
+ }
+ } else {
+ if (DetectEngineInspectPacketPayload(de_ctx, det_ctx, s, p->flow, p) != 1) {
+ return false;
+ }
+ }
+ return true;
+}
+
+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) == false) {
+ SCLogDebug("sid %u: e %p Callback returned false", 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 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)) {
+ 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;
+
+ det_ctx->discontinue_matching = 0;
+ det_ctx->buffer_offset = 0;
+ det_ctx->inspection_recursion_counter = 0;
+
+ /* Inspect all the uricontents fetched on each
+ * transaction at the app layer */
+ int r = DetectEngineContentInspection(de_ctx, det_ctx,
+ s, engine->smd,
+ NULL, f,
+ (uint8_t *)data, data_len, offset, ci_flags,
+ DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE);
+ if (r == 1) {
+ 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 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;
+ }
+
+ const uint32_t data_len = buffer->inspect_len;
+ const uint8_t *data = buffer->inspect;
+ const uint64_t offset = 0;
+
+ uint8_t ci_flags = DETECT_CI_FLAGS_START|DETECT_CI_FLAGS_END;
+ ci_flags |= buffer->flags;
+
+ det_ctx->discontinue_matching = 0;
+ det_ctx->buffer_offset = 0;
+ det_ctx->inspection_recursion_counter = 0;
+
+ /* Inspect all the uricontents fetched on each
+ * transaction at the app layer */
+ int r = DetectEngineContentInspection(det_ctx->de_ctx, det_ctx,
+ s, engine->smd,
+ p, p->flow,
+ (uint8_t *)data, data_len, offset, ci_flags,
+ DETECT_ENGINE_CONTENT_INSPECTION_MODE_HEADER);
+ if (r == 1) {
+ return DETECT_ENGINE_INSPECT_SIG_MATCH;
+ } else {
+ return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
+ }
+}
+
+/** \internal
+ * \brief inject a pseudo packet into each detect thread that doesn't use the
+ * new det_ctx yet
+ */
+static void InjectPackets(ThreadVars **detect_tvs,
+ DetectEngineThreadCtx **new_det_ctx,
+ int no_of_detect_tvs)
+{
+ /* inject a fake packet if the detect thread isn't using the new ctx yet,
+ * this speeds up 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_DETECT_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_DETECT_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_DETECT_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_DETECT_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_DETECT_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;
+ }
+ usleep(1000);
+ 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_DETECT_TM) == 0) {
+ continue;
+ }
+
+ while (!TmThreadsCheckFlag(tv, THV_RUNNING_DONE)) {
+ usleep(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;
+}
+
+static DetectEngineCtx *DetectEngineCtxInitReal(enum DetectEngineType type, const char *prefix)
+{
+ DetectEngineCtx *de_ctx = SCMalloc(sizeof(DetectEngineCtx));
+ if (unlikely(de_ctx == NULL))
+ goto error;
+
+ memset(de_ctx,0,sizeof(DetectEngineCtx));
+ 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;
+
+ 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 (ConfGetBool("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);
+
+ 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;
+ }
+
+ if (DetectEngineCtxLoadConf(de_ctx) == -1) {
+ goto error;
+ }
+
+ SigGroupHeadHashInit(de_ctx);
+ MpmStoreInit(de_ctx);
+ ThresholdHashInit(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);
+
+ SCClassConfInit(de_ctx);
+ if (!SCClassConfLoadClassificationConfigFile(de_ctx, NULL)) {
+ if (RunmodeGetCurrent() == RUNMODE_CONF_TEST)
+ goto error;
+ }
+
+ if (ActionInitConfig() < 0) {
+ goto error;
+ }
+ SCReferenceConfInit(de_ctx);
+ if (SCRConfLoadReferenceConfigFile(de_ctx, NULL) < 0) {
+ if (RunmodeGetCurrent() == 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);
+}
+
+DetectEngineCtx *DetectEngineCtxInitStubForDD(void)
+{
+ return DetectEngineCtxInitReal(DETECT_ENGINE_TYPE_DD_STUB, NULL);
+}
+
+DetectEngineCtx *DetectEngineCtxInit(void)
+{
+ return DetectEngineCtxInitReal(DETECT_ENGINE_TYPE_NORMAL, NULL);
+}
+
+DetectEngineCtx *DetectEngineCtxInitWithPrefix(const char *prefix)
+{
+ if (prefix == NULL || strlen(prefix) == 0)
+ return DetectEngineCtxInit();
+ else
+ return DetectEngineCtxInitReal(DETECT_ENGINE_TYPE_NORMAL, prefix);
+}
+
+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
+
+ /* 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);
+ ThresholdContextDestroy(de_ctx);
+ SigCleanSignatures(de_ctx);
+ if (de_ctx->sig_array)
+ SCFree(de_ctx->sig_array);
+
+ DetectEngineFreeFastPatternList(de_ctx);
+ SCClassConfDeInitContext(de_ctx);
+ SCRConfDeInitContext(de_ctx);
+
+ SigGroupCleanup(de_ctx);
+
+ SpmDestroyGlobalThreadCtx(de_ctx->spm_global_thread_ctx);
+
+ 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 */
+ ConfNode *node = ConfGetNode(de_ctx->config_prefix);
+ if (node != NULL) {
+ ConfNodeRemove(node); /* frees node */
+ }
+#if 0
+ ConfDump();
+#endif
+ }
+
+ DetectPortCleanupList(de_ctx, de_ctx->tcp_whitelist);
+ DetectPortCleanupList(de_ctx, de_ctx->udp_whitelist);
+
+ 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);
+ }
+
+ 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)ConfGet("detect.profile", &de_ctx_profile);
+ (void)ConfGet("detect.sgh-mpm-context", &sgh_mpm_context);
+
+ ConfNode *de_ctx_custom = ConfGetNode("detect-engine");
+ ConfNode *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 ||
+#ifdef BUILD_HYPERSCAN
+ de_ctx->mpm_matcher == MPM_HS ||
+#endif
+ de_ctx->mpm_matcher == MPM_AC_BS) {
+ 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 (run_mode == RUNMODE_UNITTEST) {
+ 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)ConfGet("detect.custom-values.toclient-groups",
+ &max_uniq_toclient_groups_str);
+ (void)ConfGet("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 *)ConfNodeLookupChildValue
+ (opt->head.tqh_first, "toclient-sp-groups");
+ }
+ if (max_uniq_toclient_groups_str == NULL) {
+ max_uniq_toclient_groups_str = (char *)ConfNodeLookupChildValue
+ (opt->head.tqh_first, "toclient-groups");
+ }
+ if (max_uniq_toserver_groups_str == NULL) {
+ max_uniq_toserver_groups_str = (char *)ConfNodeLookupChildValue
+ (opt->head.tqh_first, "toserver-dp-groups");
+ }
+ if (max_uniq_toserver_groups_str == NULL) {
+ max_uniq_toserver_groups_str = (char *)ConfNodeLookupChildValue
+ (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;
+ if (ConfGetInt("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 {
+ ConfNode *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 = ConfNodeLookupChild(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;
+ }
+ } else {
+ 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);
+
+ /* parse port grouping whitelisting settings */
+
+ const char *ports = NULL;
+ (void)ConfGet("detect.grouping.tcp-whitelist", &ports);
+ if (ports) {
+ SCLogConfig("grouping: tcp-whitelist %s", ports);
+ } else {
+ ports = "53, 80, 139, 443, 445, 1433, 3306, 3389, 6666, 6667, 8080";
+ SCLogConfig("grouping: tcp-whitelist (default) %s", ports);
+
+ }
+ if (DetectPortParse(de_ctx, &de_ctx->tcp_whitelist, ports) != 0) {
+ SCLogWarning("'%s' is not a valid value "
+ "for detect.grouping.tcp-whitelist",
+ ports);
+ }
+ DetectPort *x = de_ctx->tcp_whitelist;
+ for ( ; x != NULL; x = x->next) {
+ if (x->port != x->port2) {
+ SCLogWarning("'%s' is not a valid value "
+ "for detect.grouping.tcp-whitelist: only single ports allowed",
+ ports);
+ DetectPortCleanupList(de_ctx, de_ctx->tcp_whitelist);
+ de_ctx->tcp_whitelist = NULL;
+ break;
+ }
+ }
+
+ ports = NULL;
+ (void)ConfGet("detect.grouping.udp-whitelist", &ports);
+ if (ports) {
+ SCLogConfig("grouping: udp-whitelist %s", ports);
+ } else {
+ ports = "53, 135, 5060";
+ SCLogConfig("grouping: udp-whitelist (default) %s", ports);
+
+ }
+ if (DetectPortParse(de_ctx, &de_ctx->udp_whitelist, ports) != 0) {
+ SCLogWarning("'%s' is not a valid value "
+ "for detect.grouping.udp-whitelist",
+ ports);
+ }
+ for (x = de_ctx->udp_whitelist; x != NULL; x = x->next) {
+ if (x->port != x->port2) {
+ SCLogWarning("'%s' is not a valid value "
+ "for detect.grouping.udp-whitelist: only single ports allowed",
+ ports);
+ DetectPortCleanupList(de_ctx, de_ctx->udp_whitelist);
+ de_ctx->udp_whitelist = NULL;
+ break;
+ }
+ }
+
+ de_ctx->prefilter_setting = DETECT_PREFILTER_MPM;
+ const char *pf_setting = NULL;
+ if (ConfGet("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;
+}
+
+/*
+ * getting & (re)setting the internal sig i
+ */
+
+//inline uint32_t DetectEngineGetMaxSigId(DetectEngineCtx *de_ctx)
+//{
+// return de_ctx->signum;
+//}
+
+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 = SCMalloc(de_ctx->keyword_id * sizeof(void *));
+ if (det_ctx->keyword_ctxs_array == NULL) {
+ SCLogError("setting up thread local detect ctx");
+ return TM_ECODE_FAILED;
+ }
+
+ memset(det_ctx->keyword_ctxs_array, 0x00, de_ctx->keyword_id * sizeof(void *));
+
+ 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;
+ 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;
+ HashTable *mt_det_ctxs_hash = NULL;
+
+ if (master->tenant_selector == TENANT_SELECTOR_UNKNOWN) {
+ SCLogError("no tenant selector set: "
+ "set using multi-detect.selector");
+ 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++;
+ }
+
+ 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;
+ }
+
+ return TM_ECODE_OK;
+error:
+ if (map_array != NULL)
+ SCFree(map_array);
+ if (mt_det_ctxs_hash != NULL)
+ HashTableFree(mt_det_ctxs_hash);
+
+ 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);
+ PatternMatchThreadPrepare(&det_ctx->mtcs, de_ctx->mpm_matcher);
+ PatternMatchThreadPrepare(&det_ctx->mtcu, 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;
+ }
+
+ /* sized to the max of our sgh settings. A max setting of 0 implies that all
+ * sgh's have: sgh->non_pf_store_cnt == 0 */
+ if (de_ctx->non_pf_store_cnt_max > 0) {
+ det_ctx->non_pf_id_array = SCCalloc(de_ctx->non_pf_store_cnt_max, sizeof(SigIntId));
+ BUG_ON(det_ctx->non_pf_id_array == NULL);
+ }
+
+ /* DeState */
+ if (de_ctx->sig_array_len > 0) {
+ det_ctx->match_array_len = de_ctx->sig_array_len;
+ det_ctx->match_array = SCMalloc(det_ctx->match_array_len * sizeof(Signature *));
+ if (det_ctx->match_array == NULL) {
+ return TM_ECODE_FAILED;
+ }
+ memset(det_ctx->match_array, 0,
+ det_ctx->match_array_len * sizeof(Signature *));
+
+ 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_max = de_ctx->base64_decode_max_len;
+ 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 = SCMalloc(sizeof(DetectEngineThreadCtx));
+ if (unlikely(det_ctx == NULL))
+ return TM_ECODE_FAILED;
+ memset(det_ctx, 0, sizeof(DetectEngineThreadCtx));
+
+ 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);
+#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 = SCMalloc(sizeof(DetectEngineThreadCtx));
+ if (unlikely(det_ctx == NULL))
+ return NULL;
+ memset(det_ctx, 0, sizeof(DetectEngineThreadCtx));
+
+ 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);
+ PatternMatchThreadDestroy(&det_ctx->mtcs, det_ctx->de_ctx->mpm_matcher);
+ PatternMatchThreadDestroy(&det_ctx->mtcu, 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->non_pf_id_array != NULL)
+ SCFree(det_ctx->non_pf_id_array);
+
+ if (det_ctx->match_array != NULL)
+ SCFree(det_ctx->match_array);
+
+ RuleMatchCandidateTxArrayFree(det_ctx);
+
+ AlertQueueFree(det_ctx);
+
+ 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
+ }
+
+ AppLayerDecoderEventsFreeEvents(&det_ctx->decoder_events);
+
+ SCFree(det_ctx);
+}
+
+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;
+}
+
+void DetectEngineThreadCtxInfo(ThreadVars *t, DetectEngineThreadCtx *det_ctx)
+{
+ /* XXX */
+ PatternMatchThreadPrint(&det_ctx->mtc, det_ctx->de_ctx->mpm_matcher);
+ PatternMatchThreadPrint(&det_ctx->mtcu, det_ctx->de_ctx->mpm_matcher);
+}
+
+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, strlen(name)) + (ptrdiff_t)ctx->data;
+ hash %= ht->array_size;
+ return 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;
+}
+
+/** TODO locking? Not needed if this is a one time setting at startup */
+int DetectEngineMultiTenantEnabled(void)
+{
+ DetectEngineMasterCtx *master = &g_master_de_ctx;
+ return (master->multi_tenant_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;
+ }
+
+ ConfNode *node = ConfGetNode(prefix);
+ if (node == NULL) {
+ SCLogError("failed to properly setup yaml %s", filename);
+ goto error;
+ }
+
+ de_ctx = DetectEngineCtxInitWithPrefix(prefix);
+ 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, 0) < 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 (ConfYamlLoadFileWithPrefix(filename, prefix) != 0) {
+ SCLogError("failed to load yaml");
+ goto error;
+ }
+
+ ConfNode *node = ConfGetNode(prefix);
+ if (node == NULL) {
+ SCLogError("failed to properly setup yaml %s", filename);
+ goto error;
+ }
+
+ DetectEngineCtx *new_de_ctx = DetectEngineCtxInitWithPrefix(prefix);
+ 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 error;
+ }
+
+ if (SigLoadSignatures(new_de_ctx, NULL, 0) < 0) {
+ SCLogError("Loading signatures failed.");
+ goto error;
+ }
+
+ DetectEngineAddToMaster(new_de_ctx);
+
+ /* move to free list */
+ DetectEngineMoveToFreeList(old_de_ctx);
+ DetectEngineDeReference(&old_de_ctx);
+ return 0;
+
+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 ConfNode *mappings_root_node,
+ bool failure_fatal)
+{
+ ConfNode *mapping_node = NULL;
+
+ int mapping_cnt = 0;
+ if (mappings_root_node != NULL) {
+ TAILQ_FOREACH(mapping_node, &mappings_root_node->head, next) {
+ ConfNode *tenant_id_node = ConfNodeLookupChild(mapping_node, "tenant-id");
+ if (tenant_id_node == NULL)
+ goto bad_mapping;
+ ConfNode *device_node = ConfNodeLookupChild(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 ConfNode *mappings_root_node,
+ bool failure_fatal)
+{
+ ConfNode *mapping_node = NULL;
+
+ int mapping_cnt = 0;
+ if (mappings_root_node != NULL) {
+ TAILQ_FOREACH(mapping_node, &mappings_root_node->head, next) {
+ ConfNode *tenant_id_node = ConfNodeLookupChild(mapping_node, "tenant-id");
+ if (tenant_id_node == NULL)
+ goto bad_mapping;
+ ConfNode *vlan_id_node = ConfNodeLookupChild(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)ConfGetBool("engine.init-failure-fatal", &failure_fatal);
+
+ int enabled = 0;
+ (void)ConfGetBool("multi-detect.enabled", &enabled);
+ if (enabled == 1) {
+ DetectLoadersInit();
+ TmModuleDetectLoaderRegister();
+ DetectLoaderThreadSpawn();
+ TmThreadContinueDetectLoaderThreads();
+
+ SCMutexLock(&master->lock);
+ master->multi_tenant_enabled = 1;
+
+ const char *handler = NULL;
+ if (ConfGet("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 ((ConfGetBool("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 */
+ ConfNode *mappings_root_node = ConfGetNode("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 */
+ ConfNode *tenants_root_node = ConfGetNode("multi-detect.tenants");
+ ConfNode *tenant_node = NULL;
+
+ if (tenants_root_node != NULL) {
+ const char *path = NULL;
+ ConfNode *path_node = ConfGetNode("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) {
+ ConfNode *id_node = ConfNodeLookupChild(tenant_node, "id");
+ if (id_node == NULL) {
+ goto bad_tenant;
+ }
+ ConfNode *yaml_node = ConfNodeLookupChild(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. ConfYamlLoadFileWithPrefix is not thread safe. */
+ char prefix[64];
+ snprintf(prefix, sizeof(prefix), "multi-detect.%u", tenant_id);
+ if (ConfYamlLoadFileWithPrefix(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 (ConfYamlLoadFileWithPrefix(suri->conf_filename, prefix) != 0) {
+ SCLogError("failed to load yaml %s", suri->conf_filename);
+ return -1;
+ }
+
+ ConfNode *node = ConfGetNode(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]);
+ ConfYamlHandleInclude(node, suri->additional_configs[i]);
+ }
+ }
+
+#if 0
+ ConfDump();
+#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);
+ 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);
+
+ /* 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");
+ 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++;
+}
+
+AppLayerDecoderEvents *DetectEngineGetEvents(DetectEngineThreadCtx *det_ctx)
+{
+ return det_ctx->decoder_events;
+}
+
+/*************************************Unittest*********************************/
+
+#ifdef UNITTESTS
+
+static int DetectEngineInitYamlConf(const char *conf)
+{
+ ConfCreateContextBackup();
+ ConfInit();
+ return ConfYamlLoadString(conf, strlen(conf));
+}
+
+static void DetectEngineDeInitYamlConf(void)
+{
+ ConfDeInit();
+ ConfRestoreContextBackup();
+
+ return;
+}
+
+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
+ return;
+}