Coverage Report

Created: 2025-07-23 07:29

/src/suricata7/src/detect-metadata.c
Line
Count
Source (jump to first uncovered line)
1
/* Copyright (C) 2007-2017 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 metadata keyword support
24
 *
25
 * \todo Do we need to do anything more this is used in snort host attribute table
26
 *       It is also used for rule management.
27
 */
28
29
#include "suricata-common.h"
30
#include "detect.h"
31
#include "detect-parse.h"
32
#include "detect-engine.h"
33
#include "detect-metadata.h"
34
#include "util-hash-string.h"
35
#include "util-unittest.h"
36
#include "rust.h"
37
#include "util-validate.h"
38
39
static int DetectMetadataSetup (DetectEngineCtx *, Signature *, const char *);
40
#ifdef UNITTESTS
41
static void DetectMetadataRegisterTests(void);
42
#endif
43
44
void DetectMetadataRegister (void)
45
73
{
46
73
    sigmatch_table[DETECT_METADATA].name = "metadata";
47
73
    sigmatch_table[DETECT_METADATA].desc = "used for logging";
48
73
    sigmatch_table[DETECT_METADATA].url = "/rules/meta.html#metadata";
49
73
    sigmatch_table[DETECT_METADATA].Match = NULL;
50
73
    sigmatch_table[DETECT_METADATA].Setup = DetectMetadataSetup;
51
73
    sigmatch_table[DETECT_METADATA].Free  = NULL;
52
#ifdef UNITTESTS
53
    sigmatch_table[DETECT_METADATA].RegisterTests = DetectMetadataRegisterTests;
54
#endif
55
73
}
56
57
/**
58
 *  \brief Free a Metadata object
59
 */
60
void DetectMetadataFree(DetectMetadata *mdata)
61
239k
{
62
239k
    SCEnter();
63
64
239k
    SCFree(mdata);
65
66
239k
    SCReturn;
67
239k
}
68
69
int DetectMetadataHashInit(DetectEngineCtx *de_ctx)
70
135k
{
71
135k
    if (! DetectEngineMustParseMetadata())
72
61
        return 0;
73
74
135k
    de_ctx->metadata_table = HashTableInit(4096, StringHashFunc, StringHashCompareFunc, StringHashFreeFunc);
75
135k
    if (de_ctx->metadata_table == NULL)
76
0
        return -1;
77
135k
    return 0;
78
135k
}
79
80
void DetectMetadataHashFree(DetectEngineCtx *de_ctx)
81
135k
{
82
135k
    if (de_ctx->metadata_table)
83
135k
        HashTableFree(de_ctx->metadata_table);
84
135k
}
85
86
static const char *DetectMetadataHashAdd(DetectEngineCtx *de_ctx, const char *string)
87
479k
{
88
479k
    const char *hstring = (char *)HashTableLookup(
89
479k
            de_ctx->metadata_table, (void *)string, (uint16_t)strlen(string));
90
479k
    if (hstring) {
91
425k
        return hstring;
92
425k
    }
93
94
53.5k
    const char *astring = SCStrdup(string);
95
53.5k
    if (astring == NULL) {
96
0
        return NULL;
97
0
    }
98
99
53.5k
    if (HashTableAdd(de_ctx->metadata_table, (void *)astring, (uint16_t)strlen(astring)) == 0) {
100
53.5k
        return (char *)HashTableLookup(
101
53.5k
                de_ctx->metadata_table, (void *)astring, (uint16_t)strlen(astring));
102
53.5k
    } else {
103
0
        SCFree((void *)astring);
104
0
    }
105
0
    return NULL;
106
53.5k
}
107
108
static int SortHelper(const void *a, const void *b)
109
850k
{
110
850k
    const DetectMetadata *ma = *(const DetectMetadata **)a;
111
850k
    const DetectMetadata *mb = *(const DetectMetadata **)b;
112
850k
    return strcasecmp(ma->key, mb->key);
113
850k
}
114
115
static char *CraftPreformattedJSON(const DetectMetadata *head)
116
136k
{
117
136k
    int cnt = 0;
118
573k
    for (const DetectMetadata *m = head; m != NULL; m = m->next) {
119
436k
        cnt++;
120
436k
    }
121
136k
    if (cnt == 0)
122
0
        return NULL;
123
124
136k
    const DetectMetadata *array[cnt];
125
136k
    int i = 0;
126
573k
    for (const DetectMetadata *m = head; m != NULL; m = m->next) {
127
436k
        array[i++] = m;
128
436k
    }
129
136k
    BUG_ON(i != cnt);
130
136k
    qsort(array, cnt, sizeof(DetectMetadata *), SortHelper);
131
132
136k
    JsonBuilder *js = jb_new_object();
133
136k
    if (js == NULL)
134
0
        return NULL;
135
136
    /* array is sorted by key, so we can create a jsonbuilder object
137
     * with each key appearing just once with one or more values */
138
136k
    bool array_open = false;
139
573k
    for (int j = 0; j < cnt; j++) {
140
436k
        const DetectMetadata *m = array[j];
141
436k
        const DetectMetadata *nm = j + 1 < cnt ? array[j + 1] : NULL;
142
436k
        DEBUG_VALIDATE_BUG_ON(m == NULL); // for scan-build
143
144
436k
        if (nm && strcasecmp(m->key, nm->key) == 0) {
145
114k
            if (!array_open) {
146
37.4k
                jb_open_array(js, m->key);
147
37.4k
                array_open = true;
148
37.4k
            }
149
114k
            jb_append_string(js, m->value);
150
322k
        } else {
151
322k
            if (!array_open) {
152
285k
                jb_open_array(js, m->key);
153
285k
            }
154
322k
            jb_append_string(js, m->value);
155
322k
            jb_close(js);
156
322k
            array_open = false;
157
322k
        }
158
436k
    }
159
136k
    jb_close(js);
160
    /* we have a complete json builder. Now store it as a C string */
161
136k
    const size_t len = jb_len(js);
162
410k
#define MD_STR "\"metadata\":"
163
273k
#define MD_STR_LEN (sizeof(MD_STR) - 1)
164
136k
    char *str = SCMalloc(len + MD_STR_LEN + 1);
165
136k
    if (str == NULL) {
166
0
        jb_free(js);
167
0
        return NULL;
168
0
    }
169
136k
    char *ptr = str;
170
136k
    memcpy(ptr, MD_STR, MD_STR_LEN);
171
136k
    ptr += MD_STR_LEN;
172
136k
    memcpy(ptr, jb_ptr(js), len);
173
136k
    ptr += len;
174
136k
    *ptr = '\0';
175
136k
#undef MD_STR
176
136k
#undef MD_STR_LEN
177
136k
    jb_free(js);
178
136k
    return str;
179
136k
}
180
181
static int DetectMetadataParse(DetectEngineCtx *de_ctx, Signature *s, const char *metadatastr)
182
143k
{
183
143k
    DetectMetadata *head = s->metadata ? s->metadata->list : NULL;
184
143k
    char copy[strlen(metadatastr)+1];
185
143k
    strlcpy(copy, metadatastr, sizeof(copy));
186
143k
    char *xsaveptr = NULL;
187
143k
    char *key = strtok_r(copy, ",", &xsaveptr);
188
452k
    while (key != NULL) {
189
430k
        while (*key != '\0' && isblank(*key)) {
190
122k
            key++;
191
122k
        }
192
308k
        char *val = strchr(key, ' ');
193
308k
        if (val != NULL) {
194
242k
            *val++ = '\0';
195
379k
            while (*val != '\0' && isblank(*val)) {
196
137k
                val++;
197
137k
            }
198
242k
        } else {
199
            /* Skip metadata without a value. */
200
66.0k
            goto next;
201
66.0k
        }
202
203
        /* Also skip metadata if the key or value is empty. */
204
242k
        if (strlen(key) == 0 || strlen(val) == 0) {
205
2.53k
            goto next;
206
2.53k
        }
207
208
239k
        const char *hkey = DetectMetadataHashAdd(de_ctx, key);
209
239k
        if (hkey == NULL) {
210
0
            SCLogError("can't create metadata key");
211
0
            continue;
212
0
        }
213
214
239k
        const char *hval = DetectMetadataHashAdd(de_ctx, val);
215
239k
        if (hval == NULL) {
216
0
            SCLogError("can't create metadata value");
217
0
            goto next;
218
0
        }
219
220
239k
        SCLogDebug("key: %s, value: %s", hkey, hval);
221
222
239k
        DetectMetadata *dkv = SCMalloc(sizeof(DetectMetadata));
223
239k
        if (dkv == NULL) {
224
0
            goto next;
225
0
        }
226
239k
        dkv->key = hkey;
227
239k
        dkv->value = hval;
228
239k
        dkv->next = head;
229
239k
        head = dkv;
230
231
308k
    next:
232
308k
        key = strtok_r(NULL, ",", &xsaveptr);
233
308k
    }
234
143k
    if (head != NULL) {
235
136k
        if (s->metadata == NULL) {
236
100k
            s->metadata = SCCalloc(1, sizeof(*s->metadata));
237
100k
            if (s->metadata == NULL) {
238
0
                for (DetectMetadata *m = head; m != NULL; ) {
239
0
                    DetectMetadata *next_m = m->next;
240
0
                    DetectMetadataFree(m);
241
0
                    m = next_m;
242
0
                }
243
0
                return -1;
244
0
            }
245
100k
        }
246
136k
        s->metadata->list = head;
247
136k
        SCFree(s->metadata->json_str);
248
136k
        s->metadata->json_str = CraftPreformattedJSON(head);
249
136k
    }
250
143k
    return 0;
251
143k
}
252
253
static int DetectMetadataSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr)
254
164k
{
255
164k
    if (DetectEngineMustParseMetadata()) {
256
143k
        DetectMetadataParse(de_ctx, s, rawstr);
257
143k
    }
258
259
164k
    return 0;
260
164k
}
261
262
#ifdef UNITTESTS
263
264
static int DetectMetadataParseTest01(void)
265
{
266
    DetectEngineUnsetParseMetadata();
267
    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
268
    FAIL_IF_NULL(de_ctx);
269
270
    Signature *sig = DetectEngineAppendSig(de_ctx,
271
                                           "alert tcp any any -> any any "
272
                                           "(metadata: toto 1; sid:1; rev:1;)");
273
    FAIL_IF_NULL(sig);
274
    FAIL_IF(sig->metadata); 
275
276
    DetectEngineCtxFree(de_ctx);
277
    PASS;
278
}
279
280
static int DetectMetadataParseTest02(void)
281
{
282
    DetectEngineSetParseMetadata();
283
    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
284
    FAIL_IF_NULL(de_ctx);
285
    Signature *sig = DetectEngineAppendSig(de_ctx,
286
                                           "alert tcp any any -> any any "
287
                                           "(metadata: toto 1; "
288
                                           "metadata: titi 2, jaivu gros_minet;"
289
                                           "sid:1; rev:1;)");
290
    FAIL_IF_NULL(sig);
291
    FAIL_IF_NULL(sig->metadata); 
292
    FAIL_IF_NULL(sig->metadata->list); 
293
    FAIL_IF_NULL(sig->metadata->list->key); 
294
    FAIL_IF(strcmp("jaivu", sig->metadata->list->key));
295
    FAIL_IF(strcmp("gros_minet", sig->metadata->list->value));
296
    FAIL_IF_NULL(sig->metadata->list->next); 
297
    DetectMetadata *dm = sig->metadata->list->next;
298
    FAIL_IF(strcmp("titi", dm->key));
299
    dm = dm->next;
300
    FAIL_IF_NULL(dm);
301
    FAIL_IF(strcmp("toto", dm->key));
302
    FAIL_IF_NOT(strcmp(sig->metadata->json_str,
303
        "\"metadata\":{\"jaivu\":[\"gros_minet\"],\"titi\":[\"2\"],\"toto\":[\"1\"]}") == 0);
304
    DetectEngineCtxFree(de_ctx);
305
    PASS;
306
}
307
308
/**
309
 * \brief this function registers unit tests for DetectCipService
310
 */
311
static void DetectMetadataRegisterTests(void)
312
{
313
    UtRegisterTest("DetectMetadataParseTest01", DetectMetadataParseTest01);
314
    UtRegisterTest("DetectMetadataParseTest02", DetectMetadataParseTest02);
315
}
316
#endif /* UNITTESTS */