/* 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 * * Implements the datarep keyword */ #include "suricata-common.h" #include "decode.h" #include "detect.h" #include "threads.h" #include "datasets.h" #include "detect-datarep.h" #include "detect-parse.h" #include "detect-engine.h" #include "detect-engine-mpm.h" #include "detect-engine-state.h" #include "util-byte.h" #include "util-debug.h" #include "util-print.h" #include "util-misc.h" #include "util-path.h" #define PARSE_REGEX "([a-z]+)(?:,\\s*([\\-_A-z0-9\\s\\.]+)){1,4}" static DetectParseRegex parse_regex; int DetectDatarepMatch (ThreadVars *, DetectEngineThreadCtx *, Packet *, const Signature *, const SigMatchCtx *); static int DetectDatarepSetup (DetectEngineCtx *, Signature *, const char *); void DetectDatarepFree (DetectEngineCtx *, void *); void DetectDatarepRegister (void) { sigmatch_table[DETECT_DATAREP].name = "datarep"; sigmatch_table[DETECT_DATAREP].desc = "operate on datasets (experimental)"; sigmatch_table[DETECT_DATAREP].url = "/rules/dataset-keywords.html#datarep"; sigmatch_table[DETECT_DATAREP].Setup = DetectDatarepSetup; sigmatch_table[DETECT_DATAREP].Free = DetectDatarepFree; DetectSetupParseRegexes(PARSE_REGEX, &parse_regex); } /* 1 match 0 no match -1 can't match */ int DetectDatarepBufferMatch(DetectEngineThreadCtx *det_ctx, const DetectDatarepData *sd, const uint8_t *data, const uint32_t data_len) { if (data == NULL || data_len == 0) return 0; DataRepResultType r = DatasetLookupwRep(sd->set, data, data_len, &sd->rep); if (!r.found) return 0; switch (sd->op) { case DATAREP_OP_GT: if (r.rep.value > sd->rep.value) return 1; break; case DATAREP_OP_LT: if (r.rep.value < sd->rep.value) return 1; break; case DATAREP_OP_EQ: if (r.rep.value == sd->rep.value) return 1; break; } return 0; } static int DetectDatarepParse(const char *str, char *cmd, int cmd_len, char *name, int name_len, enum DatasetTypes *type, char *load, size_t load_size, uint16_t *rep_value, uint64_t *memcap, uint32_t *hashsize) { bool cmd_set = false; bool name_set = false; bool value_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 (!name_set) { if (val) { return -1; } strlcpy(name, key, name_len); name_set = true; } else if (!cmd_set) { if (val) { return -1; } strlcpy(cmd, key, cmd_len); cmd_set = true; } else if (!value_set) { if (val) { return -1; } if (StringParseUint16(rep_value, 10, 0, key) < 0) return -1; value_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, "ip") == 0) { *type = DATASET_TYPE_IPV6; } else if (strcmp(val, "ipv6") == 0) { *type = DATASET_TYPE_IPV6; } else { SCLogDebug("bad type %s", val); return -1; } } else if (strcmp(key, "load") == 0) { SCLogDebug("load %s", val); strlcpy(load, val, load_size); } 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 (strlen(load) > 0 && *type == DATASET_TYPE_NOTSET) { SCLogError("if load is used type must be set as well"); return 0; } if (!name_set || !cmd_set || !value_set) { SCLogError("missing values"); return 0; } /* 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); } 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); } else { SCLogDebug("path '%s' does not exist (HAVE_LIBGEN_H)", path); } #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); } else { SCLogDebug("path '%s' does not exist (non-HAVE_LIBGEN_H)", loadp); } SCFree(loadp); // TODO try data-dir as well? } return 0; } static int DetectDatarepSetup (DetectEngineCtx *de_ctx, Signature *s, const char *rawstr) { char cmd_str[16] = "", name[64] = ""; enum DatasetTypes type = DATASET_TYPE_NOTSET; char load[PATH_MAX] = ""; uint16_t value = 0; uint64_t memcap = 0; uint32_t hashsize = 0; if (DetectBufferGetActiveList(de_ctx, s) == -1) { SCLogError("datarep is only supported for sticky buffers"); SCReturnInt(-1); } int list = s->init_data->list; if (list == DETECT_SM_LIST_NOTSET) { SCLogError("datarep is only supported for sticky buffers"); SCReturnInt(-1); } if (!DetectDatarepParse(rawstr, cmd_str, sizeof(cmd_str), name, sizeof(name), &type, load, sizeof(load), &value, &memcap, &hashsize)) { return -1; } if (strlen(load) != 0) { if (SetupLoadPath(de_ctx, load, sizeof(load)) != 0) return -1; } enum DetectDatarepOp op; if (strcmp(cmd_str,">") == 0) { op = DATAREP_OP_GT; } else if (strcmp(cmd_str,"<") == 0) { op = DATAREP_OP_LT; } else if (strcmp(cmd_str,"==") == 0) { op = DATAREP_OP_EQ; } else { SCLogError("datarep operation \"%s\" is not supported.", cmd_str); return -1; } Dataset *set = DatasetGet(name, type, /* no save */ NULL, load, memcap, hashsize); if (set == NULL) { SCLogError("failed to set up datarep set '%s'.", name); return -1; } DetectDatarepData *cd = SCCalloc(1, sizeof(DetectDatarepData)); if (unlikely(cd == NULL)) goto error; cd->set = set; cd->op = op; cd->rep.value = value; 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. */ if (SigMatchAppendSMToList(de_ctx, s, DETECT_DATAREP, (SigMatchCtx *)cd, list) == NULL) { goto error; } return 0; error: if (cd != NULL) SCFree(cd); return -1; } void DetectDatarepFree (DetectEngineCtx *de_ctx, void *ptr) { DetectDatarepData *fd = (DetectDatarepData *)ptr; if (fd == NULL) return; SCFree(fd); }