summaryrefslogtreecommitdiffstats
path: root/src/detect-dataset.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-dataset.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-dataset.c')
-rw-r--r--src/detect-dataset.c451
1 files changed, 451 insertions, 0 deletions
diff --git a/src/detect-dataset.c b/src/detect-dataset.c
new file mode 100644
index 0000000..3d29646
--- /dev/null
+++ b/src/detect-dataset.c
@@ -0,0 +1,451 @@
+/* Copyright (C) 2018-2020 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>
+ *
+ * Implements the dataset keyword
+ */
+
+#include "suricata-common.h"
+#include "decode.h"
+#include "detect.h"
+#include "threads.h"
+#include "datasets.h"
+#include "detect-dataset.h"
+
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+#include "detect-engine-state.h"
+
+#include "util-debug.h"
+#include "util-print.h"
+#include "util-misc.h"
+#include "util-path.h"
+#include "util-conf.h"
+
+int DetectDatasetMatch (ThreadVars *, DetectEngineThreadCtx *, Packet *,
+ const Signature *, const SigMatchCtx *);
+static int DetectDatasetSetup (DetectEngineCtx *, Signature *, const char *);
+void DetectDatasetFree (DetectEngineCtx *, void *);
+
+void DetectDatasetRegister (void)
+{
+ sigmatch_table[DETECT_DATASET].name = "dataset";
+ sigmatch_table[DETECT_DATASET].desc = "match sticky buffer against datasets (experimental)";
+ sigmatch_table[DETECT_DATASET].url = "/rules/dataset-keywords.html#dataset";
+ sigmatch_table[DETECT_DATASET].Setup = DetectDatasetSetup;
+ sigmatch_table[DETECT_DATASET].Free = DetectDatasetFree;
+}
+
+/*
+ 1 match
+ 0 no match
+ -1 can't match
+ */
+int DetectDatasetBufferMatch(DetectEngineThreadCtx *det_ctx,
+ const DetectDatasetData *sd,
+ const uint8_t *data, const uint32_t data_len)
+{
+ if (data == NULL || data_len == 0)
+ return 0;
+
+ switch (sd->cmd) {
+ case DETECT_DATASET_CMD_ISSET: {
+ //PrintRawDataFp(stdout, data, data_len);
+ int r = DatasetLookup(sd->set, data, data_len);
+ SCLogDebug("r %d", r);
+ if (r == 1)
+ return 1;
+ break;
+ }
+ case DETECT_DATASET_CMD_ISNOTSET: {
+ //PrintRawDataFp(stdout, data, data_len);
+ int r = DatasetLookup(sd->set, data, data_len);
+ SCLogDebug("r %d", r);
+ if (r < 1)
+ return 1;
+ break;
+ }
+ case DETECT_DATASET_CMD_SET: {
+ //PrintRawDataFp(stdout, data, data_len);
+ int r = DatasetAdd(sd->set, data, data_len);
+ if (r == 1)
+ return 1;
+ break;
+ }
+ default:
+ abort();
+ }
+ return 0;
+}
+
+static int DetectDatasetParse(const char *str, char *cmd, int cmd_len, char *name, int name_len,
+ enum DatasetTypes *type, char *load, size_t load_size, char *save, size_t save_size,
+ uint64_t *memcap, uint32_t *hashsize)
+{
+ bool cmd_set = false;
+ bool name_set = false;
+ bool load_set = false;
+ bool save_set = false;
+ bool state_set = false;
+
+ char copy[strlen(str)+1];
+ strlcpy(copy, str, sizeof(copy));
+ char *xsaveptr = NULL;
+ char *key = strtok_r(copy, ",", &xsaveptr);
+ while (key != NULL) {
+ while (*key != '\0' && isblank(*key)) {
+ key++;
+ }
+ char *val = strchr(key, ' ');
+ if (val != NULL) {
+ *val++ = '\0';
+ while (*val != '\0' && isblank(*val)) {
+ val++;
+ SCLogDebug("cmd %s val %s", key, val);
+ }
+ } else {
+ SCLogDebug("cmd %s", key);
+ }
+
+ if (strlen(key) == 0) {
+ goto next;
+ }
+
+ if (!cmd_set) {
+ if (val && strlen(val) != 0) {
+ return -1;
+ }
+ strlcpy(cmd, key, cmd_len);
+ cmd_set = true;
+ } else if (!name_set) {
+ if (val && strlen(val) != 0) {
+ return -1;
+ }
+ strlcpy(name, key, name_len);
+ name_set = true;
+ } else {
+ if (val == NULL) {
+ return -1;
+ }
+
+ if (strcmp(key, "type") == 0) {
+ SCLogDebug("type %s", val);
+
+ if (strcmp(val, "md5") == 0) {
+ *type = DATASET_TYPE_MD5;
+ } else if (strcmp(val, "sha256") == 0) {
+ *type = DATASET_TYPE_SHA256;
+ } else if (strcmp(val, "string") == 0) {
+ *type = DATASET_TYPE_STRING;
+ } else if (strcmp(val, "ipv4") == 0) {
+ *type = DATASET_TYPE_IPV4;
+ } else if (strcmp(val, "ipv6") == 0) {
+ *type = DATASET_TYPE_IPV6;
+ } else if (strcmp(val, "ip") == 0) {
+ *type = DATASET_TYPE_IPV6;
+ } else {
+ SCLogError("bad type %s", val);
+ return -1;
+ }
+
+ } else if (strcmp(key, "save") == 0) {
+ if (save_set) {
+ SCLogWarning("'save' can only appear once");
+ return -1;
+ }
+ SCLogDebug("save %s", val);
+ strlcpy(save, val, save_size);
+ save_set = true;
+ } else if (strcmp(key, "load") == 0) {
+ if (load_set) {
+ SCLogWarning("'load' can only appear once");
+ return -1;
+ }
+ SCLogDebug("load %s", val);
+ strlcpy(load, val, load_size);
+ load_set = true;
+ } else if (strcmp(key, "state") == 0) {
+ if (state_set) {
+ SCLogWarning("'state' can only appear once");
+ return -1;
+ }
+ SCLogDebug("state %s", val);
+ strlcpy(load, val, load_size);
+ strlcpy(save, val, save_size);
+ state_set = true;
+ }
+ if (strcmp(key, "memcap") == 0) {
+ if (ParseSizeStringU64(val, memcap) < 0) {
+ SCLogWarning("invalid value for memcap: %s,"
+ " resetting to default",
+ val);
+ *memcap = 0;
+ }
+ }
+ if (strcmp(key, "hashsize") == 0) {
+ if (ParseSizeStringU32(val, hashsize) < 0) {
+ SCLogWarning("invalid value for hashsize: %s,"
+ " resetting to default",
+ val);
+ *hashsize = 0;
+ }
+ }
+ }
+
+ SCLogDebug("key: %s, value: %s", key, val);
+
+ next:
+ key = strtok_r(NULL, ",", &xsaveptr);
+ }
+
+ if ((load_set || save_set) && state_set) {
+ SCLogWarning("'state' can not be mixed with 'load' and 'save'");
+ return -1;
+ }
+
+ /* Trim trailing whitespace. */
+ while (strlen(name) > 0 && isblank(name[strlen(name) - 1])) {
+ name[strlen(name) - 1] = '\0';
+ }
+
+ /* Validate name, spaces are not allowed. */
+ for (size_t i = 0; i < strlen(name); i++) {
+ if (isblank(name[i])) {
+ SCLogError("spaces not allowed in dataset names");
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/** \brief wrapper around dirname that does leave input untouched */
+static void GetDirName(const char *in, char *out, size_t outs)
+{
+ if (strlen(in) == 0) {
+ return;
+ }
+
+ size_t size = strlen(in) + 1;
+ char tmp[size];
+ strlcpy(tmp, in, size);
+
+ char *dir = dirname(tmp);
+ BUG_ON(dir == NULL);
+ strlcpy(out, dir, outs);
+ return;
+}
+
+static int SetupLoadPath(const DetectEngineCtx *de_ctx,
+ char *load, size_t load_size)
+{
+ SCLogDebug("load %s", load);
+
+ if (PathIsAbsolute(load)) {
+ return 0;
+ }
+
+ bool done = false;
+#ifdef HAVE_LIBGEN_H
+ BUG_ON(de_ctx->rule_file == NULL);
+
+ char dir[PATH_MAX] = "";
+ GetDirName(de_ctx->rule_file, dir, sizeof(dir));
+
+ SCLogDebug("rule_file %s dir %s", de_ctx->rule_file, dir);
+ char path[PATH_MAX];
+ if (snprintf(path, sizeof(path), "%s/%s", dir, load) >= (int)sizeof(path)) // TODO windows path
+ return -1;
+
+ if (SCPathExists(path)) {
+ done = true;
+ strlcpy(load, path, load_size);
+ SCLogDebug("using path '%s' (HAVE_LIBGEN_H)", load);
+ }
+#endif
+ if (!done) {
+ char *loadp = DetectLoadCompleteSigPath(de_ctx, load);
+ if (loadp == NULL) {
+ return -1;
+ }
+ SCLogDebug("loadp %s", loadp);
+
+ if (SCPathExists(loadp)) {
+ strlcpy(load, loadp, load_size);
+ SCLogDebug("using path '%s' (non-HAVE_LIBGEN_H)", load);
+ }
+ SCFree(loadp);
+ }
+ return 0;
+}
+
+static int SetupSavePath(const DetectEngineCtx *de_ctx,
+ char *save, size_t save_size)
+{
+ SCLogDebug("save %s", save);
+
+ int allow_save = 1;
+ if (ConfGetBool("datasets.rules.allow-write", &allow_save)) {
+ if (!allow_save) {
+ SCLogError("Rules containing save/state datasets have been disabled");
+ return -1;
+ }
+ }
+
+ int allow_absolute = 0;
+ (void)ConfGetBool("datasets.rules.allow-absolute-filenames", &allow_absolute);
+ if (allow_absolute) {
+ SCLogNotice("Allowing absolute filename for dataset rule: %s", save);
+ } else {
+ if (PathIsAbsolute(save)) {
+ SCLogError("Absolute paths not allowed: %s", save);
+ return -1;
+ }
+
+ if (SCPathContainsTraversal(save)) {
+ SCLogError("Directory traversals not allowed: %s", save);
+ return -1;
+ }
+ }
+
+ // data dir
+ const char *dir = ConfigGetDataDirectory();
+ BUG_ON(dir == NULL); // should not be able to fail
+ char path[PATH_MAX];
+ if (snprintf(path, sizeof(path), "%s/%s", dir, save) >= (int)sizeof(path)) // TODO windows path
+ return -1;
+
+ /* TODO check if location exists and is writable */
+
+ strlcpy(save, path, save_size);
+
+ return 0;
+}
+
+int DetectDatasetSetup (DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
+{
+ DetectDatasetData *cd = NULL;
+ SigMatch *sm = NULL;
+ uint8_t cmd = 0;
+ uint64_t memcap = 0;
+ uint32_t hashsize = 0;
+ char cmd_str[16] = "", name[DATASET_NAME_MAX_LEN + 1] = "";
+ enum DatasetTypes type = DATASET_TYPE_NOTSET;
+ char load[PATH_MAX] = "";
+ char save[PATH_MAX] = "";
+
+ if (DetectBufferGetActiveList(de_ctx, s) == -1) {
+ SCLogError("datasets are only supported for sticky buffers");
+ SCReturnInt(-1);
+ }
+
+ int list = s->init_data->list;
+ if (list == DETECT_SM_LIST_NOTSET) {
+ SCLogError("datasets are only supported for sticky buffers");
+ SCReturnInt(-1);
+ }
+
+ if (!DetectDatasetParse(rawstr, cmd_str, sizeof(cmd_str), name, sizeof(name), &type, load,
+ sizeof(load), save, sizeof(save), &memcap, &hashsize)) {
+ return -1;
+ }
+
+ if (strcmp(cmd_str,"isset") == 0) {
+ cmd = DETECT_DATASET_CMD_ISSET;
+ } else if (strcmp(cmd_str,"isnotset") == 0) {
+ cmd = DETECT_DATASET_CMD_ISNOTSET;
+ } else if (strcmp(cmd_str,"set") == 0) {
+ cmd = DETECT_DATASET_CMD_SET;
+ } else if (strcmp(cmd_str,"unset") == 0) {
+ cmd = DETECT_DATASET_CMD_UNSET;
+ } else {
+ SCLogError("dataset action \"%s\" is not supported.", cmd_str);
+ return -1;
+ }
+
+ /* if just 'load' is set, we load data from the same dir as the
+ * rule file. If load+save is used, we use data dir */
+ if (strlen(save) == 0 && strlen(load) != 0) {
+ if (SetupLoadPath(de_ctx, load, sizeof(load)) != 0)
+ return -1;
+ /* if just 'save' is set, we use either full path or the
+ * data-dir */
+ } else if (strlen(save) != 0 && strlen(load) == 0) {
+ if (SetupSavePath(de_ctx, save, sizeof(save)) != 0)
+ return -1;
+ /* use 'save' logic for 'state', but put the resulting
+ * path into 'load' as well. */
+ } else if (strlen(save) != 0 && strlen(load) != 0 &&
+ strcmp(save, load) == 0) {
+ if (SetupSavePath(de_ctx, save, sizeof(save)) != 0)
+ return -1;
+ strlcpy(load, save, sizeof(load));
+ }
+
+ SCLogDebug("name '%s' load '%s' save '%s'", name, load, save);
+ Dataset *set = DatasetGet(name, type, save, load, memcap, hashsize);
+ if (set == NULL) {
+ SCLogError("failed to set up dataset '%s'.", name);
+ return -1;
+ }
+ if (set->hash && SC_ATOMIC_GET(set->hash->memcap_reached)) {
+ SCLogError("dataset too large for set memcap");
+ return -1;
+ }
+
+ cd = SCCalloc(1, sizeof(DetectDatasetData));
+ if (unlikely(cd == NULL))
+ goto error;
+
+ cd->set = set;
+ cd->cmd = cmd;
+
+ SCLogDebug("cmd %s, name %s",
+ cmd_str, strlen(name) ? name : "(none)");
+
+ /* Okay so far so good, lets get this into a SigMatch
+ * and put it in the Signature. */
+ sm = SigMatchAlloc();
+ if (sm == NULL)
+ goto error;
+
+ sm->type = DETECT_DATASET;
+ sm->ctx = (SigMatchCtx *)cd;
+ SigMatchAppendSMToList(s, sm, list);
+ return 0;
+
+error:
+ if (cd != NULL)
+ SCFree(cd);
+ if (sm != NULL)
+ SCFree(sm);
+ return -1;
+}
+
+void DetectDatasetFree (DetectEngineCtx *de_ctx, void *ptr)
+{
+ DetectDatasetData *fd = (DetectDatasetData *)ptr;
+ if (fd == NULL)
+ return;
+
+ SCFree(fd);
+}