Coverage Report

Created: 2026-09-28 07:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}