/src/suricata/src/detect-datarep.c
Line | Count | Source (jump to first uncovered line) |
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-mpm.h" |
36 | | #include "detect-engine-state.h" |
37 | | |
38 | | #include "util-byte.h" |
39 | | #include "util-debug.h" |
40 | | #include "util-print.h" |
41 | | #include "util-misc.h" |
42 | | |
43 | 27 | #define PARSE_REGEX "([a-z]+)(?:,\\s*([\\-_A-z0-9\\s\\.]+)){1,4}" |
44 | | static DetectParseRegex parse_regex; |
45 | | |
46 | | int DetectDatarepMatch (ThreadVars *, DetectEngineThreadCtx *, Packet *, |
47 | | const Signature *, const SigMatchCtx *); |
48 | | static int DetectDatarepSetup (DetectEngineCtx *, Signature *, const char *); |
49 | | void DetectDatarepFree (DetectEngineCtx *, void *); |
50 | | |
51 | | void DetectDatarepRegister (void) |
52 | 27 | { |
53 | 27 | sigmatch_table[DETECT_DATAREP].name = "datarep"; |
54 | 27 | sigmatch_table[DETECT_DATAREP].desc = "operate on datasets (experimental)"; |
55 | 27 | sigmatch_table[DETECT_DATAREP].url = "/rules/dataset-keywords.html#datarep"; |
56 | 27 | sigmatch_table[DETECT_DATAREP].Setup = DetectDatarepSetup; |
57 | 27 | sigmatch_table[DETECT_DATAREP].Free = DetectDatarepFree; |
58 | | |
59 | 27 | DetectSetupParseRegexes(PARSE_REGEX, &parse_regex); |
60 | 27 | } |
61 | | |
62 | | /* |
63 | | 1 match |
64 | | 0 no match |
65 | | -1 can't match |
66 | | */ |
67 | | int DetectDatarepBufferMatch(DetectEngineThreadCtx *det_ctx, |
68 | | const DetectDatarepData *sd, |
69 | | const uint8_t *data, const uint32_t data_len) |
70 | 0 | { |
71 | 0 | if (data == NULL || data_len == 0) |
72 | 0 | return 0; |
73 | | |
74 | 0 | DataRepResultType r = DatasetLookupwRep(sd->set, data, data_len, &sd->rep); |
75 | 0 | if (!r.found) |
76 | 0 | return 0; |
77 | | |
78 | 0 | switch (sd->op) { |
79 | 0 | case DATAREP_OP_GT: |
80 | 0 | if (r.rep.value > sd->rep.value) |
81 | 0 | return 1; |
82 | 0 | break; |
83 | 0 | case DATAREP_OP_LT: |
84 | 0 | if (r.rep.value < sd->rep.value) |
85 | 0 | return 1; |
86 | 0 | break; |
87 | 0 | case DATAREP_OP_EQ: |
88 | 0 | if (r.rep.value == sd->rep.value) |
89 | 0 | return 1; |
90 | 0 | break; |
91 | 0 | } |
92 | 0 | return 0; |
93 | 0 | } |
94 | | |
95 | | static int DetectDatarepParse(const char *str, char *cmd, int cmd_len, char *name, int name_len, |
96 | | enum DatasetTypes *type, char *load, size_t load_size, uint16_t *rep_value, |
97 | | uint64_t *memcap, uint32_t *hashsize) |
98 | 14.4k | { |
99 | 14.4k | bool cmd_set = false; |
100 | 14.4k | bool name_set = false; |
101 | 14.4k | bool value_set = false; |
102 | | |
103 | 14.4k | char copy[strlen(str)+1]; |
104 | 14.4k | strlcpy(copy, str, sizeof(copy)); |
105 | 14.4k | char *xsaveptr = NULL; |
106 | 14.4k | char *key = strtok_r(copy, ",", &xsaveptr); |
107 | 89.1k | while (key != NULL) { |
108 | 138k | while (*key != '\0' && isblank(*key)) { |
109 | 59.7k | key++; |
110 | 59.7k | } |
111 | 79.1k | char *val = strchr(key, ' '); |
112 | 79.1k | if (val != NULL) { |
113 | 38.9k | *val++ = '\0'; |
114 | 42.2k | while (*val != '\0' && isblank(*val)) { |
115 | 3.28k | val++; |
116 | 3.28k | SCLogDebug("cmd %s val %s", key, val); |
117 | 3.28k | } |
118 | 40.1k | } else { |
119 | 40.1k | SCLogDebug("cmd %s", key); |
120 | 40.1k | } |
121 | | |
122 | 79.1k | if (strlen(key) == 0) { |
123 | 275 | goto next; |
124 | 275 | } |
125 | | |
126 | 78.8k | if (!name_set) { |
127 | 14.4k | if (val) { |
128 | 463 | return -1; |
129 | 463 | } |
130 | 14.0k | strlcpy(name, key, name_len); |
131 | 14.0k | name_set = true; |
132 | 64.4k | } else if (!cmd_set) { |
133 | 13.5k | if (val) { |
134 | 526 | return -1; |
135 | 526 | } |
136 | 12.9k | strlcpy(cmd, key, cmd_len); |
137 | 12.9k | cmd_set = true; |
138 | 50.9k | } else if (!value_set) { |
139 | 12.7k | if (val) { |
140 | 743 | return -1; |
141 | 743 | } |
142 | | |
143 | 12.0k | if (StringParseUint16(rep_value, 10, 0, key) < 0) |
144 | 1.22k | return -1; |
145 | | |
146 | 10.8k | value_set = true; |
147 | 38.1k | } else { |
148 | 38.1k | if (val == NULL) { |
149 | 871 | return -1; |
150 | 871 | } |
151 | | |
152 | 37.2k | if (strcmp(key, "type") == 0) { |
153 | 8.50k | SCLogDebug("type %s", val); |
154 | | |
155 | 8.50k | if (strcmp(val, "md5") == 0) { |
156 | 5.78k | *type = DATASET_TYPE_MD5; |
157 | 5.78k | } else if (strcmp(val, "sha256") == 0) { |
158 | 733 | *type = DATASET_TYPE_SHA256; |
159 | 1.98k | } else if (strcmp(val, "string") == 0) { |
160 | 1.32k | *type = DATASET_TYPE_STRING; |
161 | 1.32k | } else { |
162 | 660 | SCLogDebug("bad type %s", val); |
163 | 660 | return -1; |
164 | 660 | } |
165 | | |
166 | 28.7k | } else if (strcmp(key, "load") == 0) { |
167 | 15.3k | SCLogDebug("load %s", val); |
168 | 15.3k | strlcpy(load, val, load_size); |
169 | 15.3k | } |
170 | 36.5k | if (strcmp(key, "memcap") == 0) { |
171 | 675 | if (ParseSizeStringU64(val, memcap) < 0) { |
172 | 434 | SCLogWarning(SC_ERR_INVALID_VALUE, |
173 | 434 | "invalid value for memcap: %s," |
174 | 434 | " resetting to default", |
175 | 434 | val); |
176 | 434 | *memcap = 0; |
177 | 434 | } |
178 | 675 | } |
179 | 36.5k | if (strcmp(key, "hashsize") == 0) { |
180 | 7.02k | if (ParseSizeStringU32(val, hashsize) < 0) { |
181 | 878 | SCLogWarning(SC_ERR_INVALID_VALUE, |
182 | 878 | "invalid value for hashsize: %s," |
183 | 878 | " resetting to default", |
184 | 878 | val); |
185 | 878 | *hashsize = 0; |
186 | 878 | } |
187 | 7.02k | } |
188 | 36.5k | } |
189 | | |
190 | 74.4k | SCLogDebug("key: %s, value: %s", key, val); |
191 | | |
192 | 74.6k | next: |
193 | 74.6k | key = strtok_r(NULL, ",", &xsaveptr); |
194 | 74.6k | } |
195 | | |
196 | 9.97k | if (strlen(load) > 0 && *type == DATASET_TYPE_NOTSET) { |
197 | 1.02k | SCLogError(SC_ERR_INVALID_SIGNATURE, |
198 | 1.02k | "if load is used type must be set as well"); |
199 | 1.02k | return 0; |
200 | 1.02k | } |
201 | | |
202 | 8.95k | if (!name_set || !cmd_set || !value_set) { |
203 | 684 | SCLogError(SC_ERR_INVALID_SIGNATURE, |
204 | 684 | "missing values"); |
205 | 684 | return 0; |
206 | 684 | } |
207 | | |
208 | | /* Trim trailing whitespace. */ |
209 | 8.77k | while (strlen(name) > 0 && isblank(name[strlen(name) - 1])) { |
210 | 507 | name[strlen(name) - 1] = '\0'; |
211 | 507 | } |
212 | | |
213 | | /* Validate name, spaces are not allowed. */ |
214 | 83.7k | for (size_t i = 0; i < strlen(name); i++) { |
215 | 75.5k | if (isblank(name[i])) { |
216 | 51 | SCLogError(SC_ERR_INVALID_SIGNATURE, |
217 | 51 | "spaces not allowed in dataset names"); |
218 | 51 | return 0; |
219 | 51 | } |
220 | 75.5k | } |
221 | | |
222 | 8.21k | return 1; |
223 | 8.27k | } |
224 | | |
225 | | /** \brief wrapper around dirname that does leave input untouched */ |
226 | | static void GetDirName(const char *in, char *out, size_t outs) |
227 | 7.84k | { |
228 | 7.84k | if (strlen(in) == 0) { |
229 | 0 | return; |
230 | 0 | } |
231 | | |
232 | 7.84k | size_t size = strlen(in) + 1; |
233 | 7.84k | char tmp[size]; |
234 | 7.84k | strlcpy(tmp, in, size); |
235 | | |
236 | 7.84k | char *dir = dirname(tmp); |
237 | 7.84k | BUG_ON(dir == NULL); |
238 | 0 | strlcpy(out, dir, outs); |
239 | 7.84k | return; |
240 | 7.84k | } |
241 | | |
242 | | static int SetupLoadPath(const DetectEngineCtx *de_ctx, |
243 | | char *load, size_t load_size) |
244 | 8.53k | { |
245 | 8.53k | SCLogDebug("load %s", load); |
246 | | |
247 | 8.53k | if (PathIsAbsolute(load)) { |
248 | 694 | return 0; |
249 | 694 | } |
250 | | |
251 | 7.84k | bool done = false; |
252 | 7.84k | #ifdef HAVE_LIBGEN_H |
253 | 7.84k | BUG_ON(de_ctx->rule_file == NULL); |
254 | | |
255 | 0 | char dir[PATH_MAX] = ""; |
256 | 7.84k | GetDirName(de_ctx->rule_file, dir, sizeof(dir)); |
257 | | |
258 | 7.84k | SCLogDebug("rule_file %s dir %s", de_ctx->rule_file, dir); |
259 | 7.84k | char path[PATH_MAX]; |
260 | 7.84k | if (snprintf(path, sizeof(path), "%s/%s", dir, load) >= (int)sizeof(path)) // TODO windows path |
261 | 0 | return -1; |
262 | | |
263 | 7.84k | if (SCPathExists(path)) { |
264 | 931 | done = true; |
265 | 931 | strlcpy(load, path, load_size); |
266 | 931 | SCLogDebug("using path '%s' (HAVE_LIBGEN_H)", load); |
267 | 6.91k | } else { |
268 | 6.91k | SCLogDebug("path '%s' does not exist (HAVE_LIBGEN_H)", path); |
269 | 6.91k | } |
270 | 7.84k | #endif |
271 | 7.84k | if (!done) { |
272 | 6.91k | char *loadp = DetectLoadCompleteSigPath(de_ctx, load); |
273 | 6.91k | if (loadp == NULL) { |
274 | 0 | return -1; |
275 | 0 | } |
276 | 6.91k | SCLogDebug("loadp %s", loadp); |
277 | | |
278 | 6.91k | if (SCPathExists(loadp)) { |
279 | 0 | strlcpy(load, loadp, load_size); |
280 | 0 | SCLogDebug("using path '%s' (non-HAVE_LIBGEN_H)", load); |
281 | 6.91k | } else { |
282 | 6.91k | SCLogDebug("path '%s' does not exist (non-HAVE_LIBGEN_H)", loadp); |
283 | 6.91k | } |
284 | 6.91k | SCFree(loadp); |
285 | | |
286 | | // TODO try data-dir as well? |
287 | 6.91k | } |
288 | 7.84k | return 0; |
289 | 7.84k | } |
290 | | |
291 | | static int DetectDatarepSetup (DetectEngineCtx *de_ctx, Signature *s, const char *rawstr) |
292 | 14.5k | { |
293 | 14.5k | SigMatch *sm = NULL; |
294 | 14.5k | char cmd_str[16] = "", name[64] = ""; |
295 | 14.5k | enum DatasetTypes type = DATASET_TYPE_NOTSET; |
296 | 14.5k | char load[PATH_MAX] = ""; |
297 | 14.5k | uint16_t value = 0; |
298 | 14.5k | uint64_t memcap = 0; |
299 | 14.5k | uint32_t hashsize = 0; |
300 | | |
301 | 14.5k | if (DetectBufferGetActiveList(de_ctx, s) == -1) { |
302 | 1 | SCLogError(SC_ERR_INVALID_SIGNATURE, |
303 | 1 | "datarep is only supported for sticky buffers"); |
304 | 1 | SCReturnInt(-1); |
305 | 1 | } |
306 | | |
307 | 14.5k | int list = s->init_data->list; |
308 | 14.5k | if (list == DETECT_SM_LIST_NOTSET) { |
309 | 115 | SCLogError(SC_ERR_INVALID_SIGNATURE, |
310 | 115 | "datarep is only supported for sticky buffers"); |
311 | 115 | SCReturnInt(-1); |
312 | 115 | } |
313 | | |
314 | 14.4k | if (!DetectDatarepParse(rawstr, cmd_str, sizeof(cmd_str), name, sizeof(name), &type, load, |
315 | 14.4k | sizeof(load), &value, &memcap, &hashsize)) { |
316 | 1.75k | return -1; |
317 | 1.75k | } |
318 | | |
319 | 12.7k | if (strlen(load) != 0) { |
320 | 8.53k | if (SetupLoadPath(de_ctx, load, sizeof(load)) != 0) |
321 | 0 | return -1; |
322 | 8.53k | } |
323 | | |
324 | 12.7k | enum DetectDatarepOp op; |
325 | 12.7k | if (strcmp(cmd_str,">") == 0) { |
326 | 10.9k | op = DATAREP_OP_GT; |
327 | 10.9k | } else if (strcmp(cmd_str,"<") == 0) { |
328 | 292 | op = DATAREP_OP_LT; |
329 | 1.48k | } else if (strcmp(cmd_str,"==") == 0) { |
330 | 44 | op = DATAREP_OP_EQ; |
331 | 1.44k | } else { |
332 | 1.44k | SCLogError(SC_ERR_UNKNOWN_VALUE, |
333 | 1.44k | "datarep operation \"%s\" is not supported.", cmd_str); |
334 | 1.44k | return -1; |
335 | 1.44k | } |
336 | | |
337 | 11.2k | Dataset *set = DatasetGet(name, type, /* no save */ NULL, load, memcap, hashsize); |
338 | 11.2k | if (set == NULL) { |
339 | 9.07k | SCLogError(SC_ERR_UNKNOWN_VALUE, |
340 | 9.07k | "failed to set up datarep set '%s'.", name); |
341 | 9.07k | return -1; |
342 | 9.07k | } |
343 | | |
344 | 2.18k | DetectDatarepData *cd = SCCalloc(1, sizeof(DetectDatarepData)); |
345 | 2.18k | if (unlikely(cd == NULL)) |
346 | 0 | goto error; |
347 | | |
348 | 2.18k | cd->set = set; |
349 | 2.18k | cd->op = op; |
350 | 2.18k | cd->rep.value = value; |
351 | | |
352 | 2.18k | SCLogDebug("cmd %s, name %s", |
353 | 2.18k | cmd_str, strlen(name) ? name : "(none)"); |
354 | | |
355 | | /* Okay so far so good, lets get this into a SigMatch |
356 | | * and put it in the Signature. */ |
357 | 2.18k | sm = SigMatchAlloc(); |
358 | 2.18k | if (sm == NULL) |
359 | 0 | goto error; |
360 | | |
361 | 2.18k | sm->type = DETECT_DATAREP; |
362 | 2.18k | sm->ctx = (SigMatchCtx *)cd; |
363 | 2.18k | SigMatchAppendSMToList(s, sm, list); |
364 | 2.18k | return 0; |
365 | | |
366 | 0 | error: |
367 | 0 | if (cd != NULL) |
368 | 0 | SCFree(cd); |
369 | 0 | if (sm != NULL) |
370 | 0 | SCFree(sm); |
371 | 0 | return -1; |
372 | 2.18k | } |
373 | | |
374 | | void DetectDatarepFree (DetectEngineCtx *de_ctx, void *ptr) |
375 | 2.18k | { |
376 | 2.18k | DetectDatarepData *fd = (DetectDatarepData *)ptr; |
377 | | |
378 | 2.18k | if (fd == NULL) |
379 | 0 | return; |
380 | | |
381 | 2.18k | SCFree(fd); |
382 | 2.18k | } |