/src/suricata8/src/detect-datarep.c
Line | Count | Source |
1 | | /* Copyright (C) 2018-2020 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | /** |
19 | | * \file |
20 | | * |
21 | | * \author Victor Julien <victor@inliniac.net> |
22 | | * |
23 | | * Implements the datarep keyword |
24 | | */ |
25 | | |
26 | | #include "suricata-common.h" |
27 | | #include "decode.h" |
28 | | #include "detect.h" |
29 | | #include "threads.h" |
30 | | #include "datasets.h" |
31 | | #include "detect-datarep.h" |
32 | | |
33 | | #include "detect-parse.h" |
34 | | #include "detect-engine.h" |
35 | | #include "detect-engine-buffer.h" |
36 | | #include "detect-engine-mpm.h" |
37 | | #include "detect-engine-state.h" |
38 | | |
39 | | #include "util-byte.h" |
40 | | #include "util-debug.h" |
41 | | #include "util-print.h" |
42 | | #include "util-misc.h" |
43 | | #include "util-path.h" |
44 | | |
45 | 79 | #define PARSE_REGEX "([a-z]+)(?:,\\s*([\\-_A-z0-9\\s\\.]+)){1,4}" |
46 | | static DetectParseRegex parse_regex; |
47 | | |
48 | | int DetectDatarepMatch (ThreadVars *, DetectEngineThreadCtx *, Packet *, |
49 | | const Signature *, const SigMatchCtx *); |
50 | | static int DetectDatarepSetup (DetectEngineCtx *, Signature *, const char *); |
51 | | void DetectDatarepFree (DetectEngineCtx *, void *); |
52 | | |
53 | | void DetectDatarepRegister (void) |
54 | 79 | { |
55 | 79 | sigmatch_table[DETECT_DATAREP].name = "datarep"; |
56 | 79 | sigmatch_table[DETECT_DATAREP].desc = "operate on datasets (experimental)"; |
57 | 79 | sigmatch_table[DETECT_DATAREP].url = "/rules/dataset-keywords.html#datarep"; |
58 | 79 | sigmatch_table[DETECT_DATAREP].Setup = DetectDatarepSetup; |
59 | 79 | sigmatch_table[DETECT_DATAREP].Free = DetectDatarepFree; |
60 | | |
61 | 79 | DetectSetupParseRegexes(PARSE_REGEX, &parse_regex); |
62 | 79 | } |
63 | | |
64 | | /* |
65 | | 1 match |
66 | | 0 no match |
67 | | -1 can't match |
68 | | */ |
69 | | int DetectDatarepBufferMatch(DetectEngineThreadCtx *det_ctx, |
70 | | const DetectDatarepData *sd, |
71 | | const uint8_t *data, const uint32_t data_len) |
72 | 277 | { |
73 | 277 | if (data == NULL || data_len == 0) |
74 | 0 | return 0; |
75 | | |
76 | 277 | DataRepResultType r = DatasetLookupwRep(sd->set, data, data_len, &sd->rep); |
77 | 277 | if (!r.found) |
78 | 277 | return 0; |
79 | | |
80 | 0 | switch (sd->op) { |
81 | 0 | case DATAREP_OP_GT: |
82 | 0 | if (r.rep.value > sd->rep.value) |
83 | 0 | return 1; |
84 | 0 | break; |
85 | 0 | case DATAREP_OP_LT: |
86 | 0 | if (r.rep.value < sd->rep.value) |
87 | 0 | return 1; |
88 | 0 | break; |
89 | 0 | case DATAREP_OP_EQ: |
90 | 0 | if (r.rep.value == sd->rep.value) |
91 | 0 | return 1; |
92 | 0 | break; |
93 | 0 | } |
94 | 0 | return 0; |
95 | 0 | } |
96 | | |
97 | | static int DetectDatarepParse(const char *str, char *cmd, int cmd_len, char *name, int name_len, |
98 | | enum DatasetTypes *type, char *load, size_t load_size, uint16_t *rep_value, |
99 | | uint64_t *memcap, uint32_t *hashsize) |
100 | 48.5k | { |
101 | 48.5k | bool cmd_set = false; |
102 | 48.5k | bool name_set = false; |
103 | 48.5k | bool value_set = false; |
104 | | |
105 | 48.5k | char copy[strlen(str)+1]; |
106 | 48.5k | strlcpy(copy, str, sizeof(copy)); |
107 | 48.5k | char *xsaveptr = NULL; |
108 | 48.5k | char *key = strtok_r(copy, ",", &xsaveptr); |
109 | 286k | while (key != NULL) { |
110 | 429k | while (*key != '\0' && isblank(*key)) { |
111 | 173k | key++; |
112 | 173k | } |
113 | 256k | char *val = strchr(key, ' '); |
114 | 256k | if (val != NULL) { |
115 | 110k | *val++ = '\0'; |
116 | 130k | while (*val != '\0' && isblank(*val)) { |
117 | 20.3k | val++; |
118 | 20.3k | SCLogDebug("cmd %s val %s", key, val); |
119 | 20.3k | } |
120 | 145k | } else { |
121 | 145k | SCLogDebug("cmd %s", key); |
122 | 145k | } |
123 | | |
124 | 256k | if (strlen(key) == 0) { |
125 | 1.88k | goto next; |
126 | 1.88k | } |
127 | | |
128 | 254k | if (!name_set) { |
129 | 48.5k | if (val) { |
130 | 1.66k | return -1; |
131 | 1.66k | } |
132 | 46.8k | strlcpy(name, key, name_len); |
133 | 46.8k | name_set = true; |
134 | 205k | } else if (!cmd_set) { |
135 | 46.1k | if (val) { |
136 | 305 | return -1; |
137 | 305 | } |
138 | 45.8k | strlcpy(cmd, key, cmd_len); |
139 | 45.8k | cmd_set = true; |
140 | 159k | } else if (!value_set) { |
141 | 44.0k | if (val) { |
142 | 1.33k | return -1; |
143 | 1.33k | } |
144 | | |
145 | 42.7k | if (StringParseUint16(rep_value, 10, 0, key) < 0) |
146 | 4.21k | return -1; |
147 | | |
148 | 38.5k | value_set = true; |
149 | 115k | } else { |
150 | 115k | if (val == NULL) { |
151 | 8.25k | return -1; |
152 | 8.25k | } |
153 | | |
154 | 107k | if (strcmp(key, "type") == 0) { |
155 | 30.3k | SCLogDebug("type %s", val); |
156 | | |
157 | 30.3k | if (strcmp(val, "md5") == 0) { |
158 | 12.3k | *type = DATASET_TYPE_MD5; |
159 | 18.0k | } else if (strcmp(val, "sha256") == 0) { |
160 | 9.33k | *type = DATASET_TYPE_SHA256; |
161 | 9.33k | } else if (strcmp(val, "string") == 0) { |
162 | 4.57k | *type = DATASET_TYPE_STRING; |
163 | 4.57k | } else if (strcmp(val, "ipv4") == 0) { |
164 | 327 | *type = DATASET_TYPE_IPV4; |
165 | 3.78k | } else if (strcmp(val, "ip") == 0) { |
166 | 976 | *type = DATASET_TYPE_IPV6; |
167 | 2.81k | } else if (strcmp(val, "ipv6") == 0) { |
168 | 209 | *type = DATASET_TYPE_IPV6; |
169 | 2.60k | } else { |
170 | 2.60k | SCLogDebug("bad type %s", val); |
171 | 2.60k | return -1; |
172 | 2.60k | } |
173 | | |
174 | 76.9k | } else if (strcmp(key, "load") == 0) { |
175 | 43.2k | SCLogDebug("load %s", val); |
176 | 43.2k | strlcpy(load, val, load_size); |
177 | 43.2k | } |
178 | 104k | if (strcmp(key, "memcap") == 0) { |
179 | 3.35k | if (ParseSizeStringU64(val, memcap) < 0) { |
180 | 2.39k | SCLogWarning("invalid value for memcap: %s," |
181 | 2.39k | " resetting to default", |
182 | 2.39k | val); |
183 | 2.39k | *memcap = 0; |
184 | 2.39k | } |
185 | 3.35k | } |
186 | 104k | if (strcmp(key, "hashsize") == 0) { |
187 | 11.3k | if (ParseSizeStringU32(val, hashsize) < 0) { |
188 | 1.62k | SCLogWarning("invalid value for hashsize: %s," |
189 | 1.62k | " resetting to default", |
190 | 1.62k | val); |
191 | 1.62k | *hashsize = 0; |
192 | 1.62k | } |
193 | 11.3k | } |
194 | 104k | } |
195 | | |
196 | 235k | SCLogDebug("key: %s, value: %s", key, val); |
197 | | |
198 | 237k | next: |
199 | 237k | key = strtok_r(NULL, ",", &xsaveptr); |
200 | 237k | } |
201 | | |
202 | 30.1k | if (strlen(load) > 0 && *type == DATASET_TYPE_NOTSET) { |
203 | 1.07k | SCLogError("if load is used type must be set as well"); |
204 | 1.07k | return 0; |
205 | 1.07k | } |
206 | | |
207 | 29.1k | if (!name_set || !cmd_set || !value_set) { |
208 | 2.49k | SCLogError("missing values"); |
209 | 2.49k | return 0; |
210 | 2.49k | } |
211 | | |
212 | | /* Trim trailing whitespace. */ |
213 | 27.0k | while (strlen(name) > 0 && isblank(name[strlen(name) - 1])) { |
214 | 418 | name[strlen(name) - 1] = '\0'; |
215 | 418 | } |
216 | | |
217 | | /* Validate name, spaces are not allowed. */ |
218 | 286k | for (size_t i = 0; i < strlen(name); i++) { |
219 | 259k | if (isblank(name[i])) { |
220 | 7 | SCLogError("spaces not allowed in dataset names"); |
221 | 7 | return 0; |
222 | 7 | } |
223 | 259k | } |
224 | | |
225 | 26.6k | return 1; |
226 | 26.6k | } |
227 | | |
228 | | /** \brief wrapper around dirname that does leave input untouched */ |
229 | | static void GetDirName(const char *in, char *out, size_t outs) |
230 | 26.5k | { |
231 | 26.5k | if (strlen(in) == 0) { |
232 | 0 | return; |
233 | 0 | } |
234 | | |
235 | 26.5k | size_t size = strlen(in) + 1; |
236 | 26.5k | char tmp[size]; |
237 | 26.5k | strlcpy(tmp, in, size); |
238 | | |
239 | 26.5k | char *dir = dirname(tmp); |
240 | 26.5k | BUG_ON(dir == NULL); |
241 | 26.5k | strlcpy(out, dir, outs); |
242 | 26.5k | } |
243 | | |
244 | | static int SetupLoadPath(const DetectEngineCtx *de_ctx, |
245 | | char *load, size_t load_size) |
246 | 8.74k | { |
247 | 8.74k | SCLogDebug("load %s", load); |
248 | | |
249 | 8.74k | if (PathIsAbsolute(load)) { |
250 | 702 | return 0; |
251 | 702 | } |
252 | | |
253 | 8.74k | bool done = false; |
254 | 8.04k | #ifdef HAVE_LIBGEN_H |
255 | 8.04k | BUG_ON(de_ctx->rule_file == NULL); |
256 | | |
257 | 8.04k | char dir[PATH_MAX] = ""; |
258 | 8.04k | GetDirName(de_ctx->rule_file, dir, sizeof(dir)); |
259 | | |
260 | 8.04k | SCLogDebug("rule_file %s dir %s", de_ctx->rule_file, dir); |
261 | 8.04k | char path[PATH_MAX]; |
262 | 8.04k | if (snprintf(path, sizeof(path), "%s/%s", dir, load) >= (int)sizeof(path)) // TODO windows path |
263 | 12 | return -1; |
264 | | |
265 | 8.03k | if (SCPathExists(path)) { |
266 | 3 | done = true; |
267 | 3 | strlcpy(load, path, load_size); |
268 | 3 | SCLogDebug("using path '%s' (HAVE_LIBGEN_H)", load); |
269 | 8.03k | } else { |
270 | 8.03k | SCLogDebug("path '%s' does not exist (HAVE_LIBGEN_H)", path); |
271 | 8.03k | } |
272 | 8.03k | #endif |
273 | 8.03k | if (!done) { |
274 | 8.03k | char *loadp = DetectLoadCompleteSigPath(de_ctx, load); |
275 | 8.03k | if (loadp == NULL) { |
276 | 0 | return -1; |
277 | 0 | } |
278 | 8.03k | SCLogDebug("loadp %s", loadp); |
279 | | |
280 | 8.03k | if (SCPathExists(loadp)) { |
281 | 0 | strlcpy(load, loadp, load_size); |
282 | 0 | SCLogDebug("using path '%s' (non-HAVE_LIBGEN_H)", load); |
283 | 8.03k | } else { |
284 | 8.03k | SCLogDebug("path '%s' does not exist (non-HAVE_LIBGEN_H)", loadp); |
285 | 8.03k | } |
286 | 8.03k | SCFree(loadp); |
287 | | |
288 | | // TODO try data-dir as well? |
289 | 8.03k | } |
290 | 8.03k | return 0; |
291 | 8.03k | } |
292 | | |
293 | | static int DetectDatarepSetup (DetectEngineCtx *de_ctx, Signature *s, const char *rawstr) |
294 | 48.7k | { |
295 | 48.7k | char cmd_str[16] = "", name[64] = ""; |
296 | 48.7k | enum DatasetTypes type = DATASET_TYPE_NOTSET; |
297 | 48.7k | char load[PATH_MAX] = ""; |
298 | 48.7k | uint16_t value = 0; |
299 | 48.7k | uint64_t memcap = 0; |
300 | 48.7k | uint32_t hashsize = 0; |
301 | | |
302 | 48.7k | if (DetectBufferGetActiveList(de_ctx, s) == -1) { |
303 | 2 | SCLogError("datarep is only supported for sticky buffers"); |
304 | 2 | SCReturnInt(-1); |
305 | 2 | } |
306 | | |
307 | 48.7k | int list = s->init_data->list; |
308 | 48.7k | if (list == DETECT_SM_LIST_NOTSET) { |
309 | 165 | SCLogError("datarep is only supported for sticky buffers"); |
310 | 165 | SCReturnInt(-1); |
311 | 165 | } |
312 | | |
313 | 48.5k | if (!DetectDatarepParse(rawstr, cmd_str, sizeof(cmd_str), name, sizeof(name), &type, load, |
314 | 48.5k | sizeof(load), &value, &memcap, &hashsize)) { |
315 | 3.57k | return -1; |
316 | 3.57k | } |
317 | | |
318 | 44.9k | if (strlen(load) != 0) { |
319 | 29.5k | if (SetupLoadPath(de_ctx, load, sizeof(load)) != 0) |
320 | 36 | return -1; |
321 | 29.5k | } |
322 | | |
323 | 44.9k | enum DetectDatarepOp op; |
324 | 44.9k | if (strcmp(cmd_str,">") == 0) { |
325 | 35.8k | op = DATAREP_OP_GT; |
326 | 35.8k | } else if (strcmp(cmd_str,"<") == 0) { |
327 | 4.71k | op = DATAREP_OP_LT; |
328 | 4.71k | } else if (strcmp(cmd_str,"==") == 0) { |
329 | 1.69k | op = DATAREP_OP_EQ; |
330 | 2.65k | } else { |
331 | 2.65k | SCLogError("datarep operation \"%s\" is not supported.", cmd_str); |
332 | 2.65k | return -1; |
333 | 2.65k | } |
334 | | |
335 | 42.2k | Dataset *set = DatasetGet(name, type, /* no save */ NULL, load, memcap, hashsize); |
336 | 42.2k | if (set == NULL) { |
337 | 31.0k | SCLogError("failed to set up datarep set '%s'.", name); |
338 | 31.0k | return -1; |
339 | 31.0k | } |
340 | | |
341 | 11.2k | DetectDatarepData *cd = SCCalloc(1, sizeof(DetectDatarepData)); |
342 | 11.2k | if (unlikely(cd == NULL)) |
343 | 0 | goto error; |
344 | | |
345 | 11.2k | cd->set = set; |
346 | 11.2k | cd->op = op; |
347 | 11.2k | cd->rep.value = value; |
348 | | |
349 | 11.2k | SCLogDebug("cmd %s, name %s", |
350 | 11.2k | cmd_str, strlen(name) ? name : "(none)"); |
351 | | |
352 | | /* Okay so far so good, lets get this into a SigMatch |
353 | | * and put it in the Signature. */ |
354 | | |
355 | 11.2k | if (SCSigMatchAppendSMToList(de_ctx, s, DETECT_DATAREP, (SigMatchCtx *)cd, list) == NULL) { |
356 | 0 | goto error; |
357 | 0 | } |
358 | 11.2k | return 0; |
359 | | |
360 | 0 | error: |
361 | 0 | if (cd != NULL) |
362 | 0 | SCFree(cd); |
363 | 0 | return -1; |
364 | 11.2k | } |
365 | | |
366 | | void DetectDatarepFree (DetectEngineCtx *de_ctx, void *ptr) |
367 | 11.2k | { |
368 | 11.2k | DetectDatarepData *fd = (DetectDatarepData *)ptr; |
369 | | |
370 | 11.2k | if (fd == NULL) |
371 | 0 | return; |
372 | | |
373 | 11.2k | SCFree(fd); |
374 | 11.2k | } |