Coverage Report

Created: 2026-02-14 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/src/detect-filestore.c
Line
Count
Source
1
/* Copyright (C) 2007-2021 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 filestore keyword
24
 */
25
26
#include "suricata-common.h"
27
#include "threads.h"
28
#include "decode.h"
29
30
#include "detect.h"
31
#include "detect-parse.h"
32
33
#include "detect-engine.h"
34
#include "detect-engine-mpm.h"
35
#include "detect-engine-state.h"
36
37
#include "feature.h"
38
39
#include "flow.h"
40
#include "flow-var.h"
41
#include "flow-util.h"
42
43
#include "util-debug.h"
44
#include "util-spm-bm.h"
45
#include "util-unittest.h"
46
#include "util-unittest-helper.h"
47
48
#include "app-layer.h"
49
#include "app-layer-parser.h"
50
#include "app-layer-htp.h"
51
52
#include "stream-tcp.h"
53
54
#include "detect-filestore.h"
55
56
#include "util-validate.h"
57
58
/**
59
 * \brief Regex for parsing our flow options
60
 */
61
73
#define PARSE_REGEX  "^\\s*([A-z_]+)\\s*(?:,\\s*([A-z_]+))?\\s*(?:,\\s*([A-z_]+))?\\s*$"
62
63
static DetectParseRegex parse_regex;
64
65
static int DetectFilestoreMatch (DetectEngineThreadCtx *,
66
        Flow *, uint8_t, File *, const Signature *, const SigMatchCtx *);
67
static int DetectFilestorePostMatch(DetectEngineThreadCtx *det_ctx,
68
        Packet *p, const Signature *s, const SigMatchCtx *ctx);
69
static int DetectFilestoreSetup (DetectEngineCtx *, Signature *, const char *);
70
static void DetectFilestoreFree(DetectEngineCtx *, void *);
71
#ifdef UNITTESTS
72
static void DetectFilestoreRegisterTests(void);
73
#endif
74
static int g_file_match_list_id = 0;
75
76
/**
77
 * \brief Registration function for keyword: filestore
78
 */
79
void DetectFilestoreRegister(void)
80
73
{
81
73
    sigmatch_table[DETECT_FILESTORE].name = "filestore";
82
73
    sigmatch_table[DETECT_FILESTORE].desc = "stores files to disk if the rule matched";
83
73
    sigmatch_table[DETECT_FILESTORE].url = "/rules/file-keywords.html#filestore";
84
73
    sigmatch_table[DETECT_FILESTORE].FileMatch = DetectFilestoreMatch;
85
73
    sigmatch_table[DETECT_FILESTORE].Setup = DetectFilestoreSetup;
86
73
    sigmatch_table[DETECT_FILESTORE].Free  = DetectFilestoreFree;
87
#ifdef UNITTESTS
88
    sigmatch_table[DETECT_FILESTORE].RegisterTests = DetectFilestoreRegisterTests;
89
#endif
90
73
    sigmatch_table[DETECT_FILESTORE].flags = SIGMATCH_OPTIONAL_OPT;
91
92
73
    sigmatch_table[DETECT_FILESTORE_POSTMATCH].name = "__filestore__postmatch__";
93
73
    sigmatch_table[DETECT_FILESTORE_POSTMATCH].Match = DetectFilestorePostMatch;
94
73
    sigmatch_table[DETECT_FILESTORE_POSTMATCH].Free  = DetectFilestoreFree;
95
96
73
    DetectSetupParseRegexes(PARSE_REGEX, &parse_regex);
97
98
73
    g_file_match_list_id = DetectBufferTypeRegister("files");
99
73
}
100
101
/**
102
 *  \brief apply the post match filestore with options
103
 */
104
static int FilestorePostMatchWithOptions(Packet *p, Flow *f, const DetectFilestoreData *filestore,
105
        FileContainer *fc, uint32_t file_id, uint64_t tx_id)
106
71
{
107
71
    if (filestore == NULL) {
108
0
        SCReturnInt(0);
109
0
    }
110
111
71
    int this_file = 0;
112
71
    int this_tx = 0;
113
71
    int this_flow = 0;
114
71
    int rule_dir = 0;
115
71
    int toserver_dir = 0;
116
71
    int toclient_dir = 0;
117
118
71
    switch (filestore->direction) {
119
71
        case FILESTORE_DIR_DEFAULT:
120
71
            rule_dir = 1;
121
            // will use both sides if scope is not default
122
            // fallthrough
123
71
        case FILESTORE_DIR_BOTH:
124
71
            toserver_dir = 1;
125
71
            toclient_dir = 1;
126
71
            break;
127
0
        case FILESTORE_DIR_TOSERVER:
128
0
            toserver_dir = 1;
129
0
            break;
130
0
        case FILESTORE_DIR_TOCLIENT:
131
0
            toclient_dir = 1;
132
0
            break;
133
71
    }
134
135
71
    switch (filestore->scope) {
136
71
        case FILESTORE_SCOPE_DEFAULT:
137
71
            if (rule_dir) {
138
71
                this_file = 1;
139
71
            } else if ((p->flowflags & FLOW_PKT_TOCLIENT) && toclient_dir) {
140
0
                this_file = 1;
141
0
            } else if ((p->flowflags & FLOW_PKT_TOSERVER) && toserver_dir) {
142
0
                this_file = 1;
143
0
            }
144
71
            break;
145
0
        case FILESTORE_SCOPE_TX:
146
0
            this_tx = 1;
147
0
            break;
148
0
        case FILESTORE_SCOPE_SSN:
149
0
            this_flow = 1;
150
0
            break;
151
71
    }
152
153
71
    if (this_file)  {
154
71
        FileStoreFileById(fc, file_id);
155
71
    } else if (this_tx) {
156
        /* set in AppLayerTxData. Parsers and logger will propagate it to the
157
         * individual files, both new and current. */
158
0
        void *txv = AppLayerParserGetTx(f->proto, f->alproto, f->alstate, tx_id);
159
0
        DEBUG_VALIDATE_BUG_ON(txv == NULL);
160
0
        if (txv != NULL) {
161
0
            AppLayerTxData *txd = AppLayerParserGetTxData(f->proto, f->alproto, txv);
162
0
            if (txd != NULL) {
163
0
                if (toclient_dir) {
164
0
                    txd->file_flags |= FLOWFILE_STORE_TC;
165
0
                }
166
0
                if (toserver_dir) {
167
0
                    txd->file_flags |= FLOWFILE_STORE_TS;
168
0
                }
169
0
            }
170
0
        }
171
0
    } else if (this_flow) {
172
        /* set in flow and AppLayerStateData */
173
0
        AppLayerStateData *sd = AppLayerParserGetStateData(f->proto, f->alproto, f->alstate);
174
0
        if (toclient_dir) {
175
0
            f->file_flags |= FLOWFILE_STORE_TC;
176
0
            if (sd != NULL) {
177
0
                sd->file_flags |= FLOWFILE_STORE_TC;
178
0
            }
179
0
        }
180
0
        if (toserver_dir) {
181
0
            f->file_flags |= FLOWFILE_STORE_TS;
182
0
            if (sd != NULL) {
183
0
                sd->file_flags |= FLOWFILE_STORE_TS;
184
0
            }
185
0
        }
186
0
    } else {
187
0
        FileStoreFileById(fc, file_id);
188
0
    }
189
190
71
    SCReturnInt(0);
191
71
}
192
193
/**
194
 *  \brief post-match function for filestore
195
 *
196
 *  \param t thread local vars
197
 *  \param det_ctx pattern matcher thread local data
198
 *  \param p packet
199
 *
200
 *  The match function for filestore records store candidates in the det_ctx.
201
 *  When we are sure all parts of the signature matched, we run this function
202
 *  to finalize the filestore.
203
 */
204
static int DetectFilestorePostMatch(DetectEngineThreadCtx *det_ctx,
205
        Packet *p, const Signature *s, const SigMatchCtx *ctx)
206
60.4k
{
207
60.4k
    SCEnter();
208
209
60.4k
    if (det_ctx->filestore_cnt == 0) {
210
38.4k
        SCReturnInt(0);
211
38.4k
    }
212
213
22.0k
    if ((s->filestore_ctx == NULL && !(s->flags & SIG_FLAG_FILESTORE)) || p->flow == NULL) {
214
0
#ifndef DEBUG
215
0
        SCReturnInt(0);
216
#else
217
        BUG_ON(1);
218
#endif
219
0
    }
220
221
22.0k
    if (p->flow->proto == IPPROTO_TCP && p->flow->protoctx != NULL) {
222
        /* set filestore depth for stream reassembling */
223
21.9k
        TcpSession *ssn = (TcpSession *)p->flow->protoctx;
224
21.9k
        TcpSessionSetReassemblyDepth(ssn, FileReassemblyDepth());
225
21.9k
    }
226
227
22.0k
    SCLogDebug("s->filestore_ctx %p", s->filestore_ctx);
228
229
22.0k
    const uint8_t flags = STREAM_FLAGS_FOR_PACKET(p);
230
57.9k
    for (uint16_t u = 0; u < det_ctx->filestore_cnt; u++) {
231
35.8k
        void *alstate = FlowGetAppState(p->flow);
232
35.8k
        AppLayerParserSetStreamDepthFlag(
233
35.8k
                p->flow->proto, p->flow->alproto, alstate, det_ctx->filestore[u].tx_id, flags);
234
235
35.8k
        void *txv = AppLayerParserGetTx(
236
35.8k
                p->flow->proto, p->flow->alproto, alstate, det_ctx->filestore[u].tx_id);
237
35.8k
        DEBUG_VALIDATE_BUG_ON(txv == NULL);
238
35.8k
        if (txv) {
239
35.8k
            AppLayerGetFileState files = AppLayerParserGetTxFiles(p->flow, alstate, txv, flags);
240
35.8k
            FileContainer *ffc_tx = files.fc;
241
35.8k
            DEBUG_VALIDATE_BUG_ON(ffc_tx == NULL);
242
35.8k
            if (ffc_tx) {
243
35.8k
                SCLogDebug("u %u txv %p ffc_tx %p file_id %u", u, txv, ffc_tx,
244
35.8k
                        det_ctx->filestore[u].file_id);
245
246
                /* filestore for single files only */
247
35.8k
                if (s->filestore_ctx == NULL) {
248
35.7k
                    FileStoreFileById(ffc_tx, det_ctx->filestore[u].file_id);
249
35.7k
                } else {
250
91
                    FilestorePostMatchWithOptions(p, p->flow, s->filestore_ctx, ffc_tx,
251
91
                            det_ctx->filestore[u].file_id, det_ctx->filestore[u].tx_id);
252
91
                }
253
35.8k
            }
254
35.8k
        }
255
35.8k
    }
256
22.0k
    SCReturnInt(0);
257
22.0k
}
258
259
/**
260
 * \brief match the specified filestore
261
 *
262
 * \param t thread local vars
263
 * \param det_ctx pattern matcher thread local data
264
 * \param f *LOCKED* flow
265
 * \param flags direction flags
266
 * \param file file being inspected
267
 * \param s signature being inspected
268
 * \param m sigmatch that we will cast into DetectFilestoreData
269
 *
270
 * \retval 0 no match
271
 * \retval 1 match
272
 *
273
 * \todo when we start supporting more protocols, the logic in this function
274
 *       needs to be put behind a api.
275
 */
276
static int DetectFilestoreMatch (DetectEngineThreadCtx *det_ctx, Flow *f,
277
        uint8_t flags, File *file, const Signature *s, const SigMatchCtx *m)
278
22.1k
{
279
22.1k
    uint32_t file_id = 0;
280
281
22.1k
    SCEnter();
282
283
22.1k
    if (!RunmodeIsUnittests()) {
284
22.1k
        extern bool g_filedata_logger_enabled;
285
22.1k
        if (!g_filedata_logger_enabled) {
286
0
            SCLogDebug("not storing file match: no filedata logger enabled");
287
0
            SCReturnInt(1);
288
0
        }
289
22.1k
    }
290
22.1k
    if (det_ctx->filestore_cnt >= DETECT_FILESTORE_MAX) {
291
169
        SCReturnInt(1);
292
169
    }
293
294
    /* file can be NULL when a rule with filestore scope > file
295
     * matches. */
296
22.0k
    if (file != NULL) {
297
22.0k
        file_id = file->file_track_id;
298
22.0k
        if (file->sid != NULL && s->id > 0) {
299
22.0k
            if (file->sid_cnt >= file->sid_max) {
300
1
                void *p = SCRealloc(file->sid, sizeof(uint32_t) * (file->sid_max + 8));
301
1
                if (p == NULL) {
302
0
                    SCFree(file->sid);
303
0
                    file->sid = NULL;
304
0
                    file->sid_cnt = 0;
305
0
                    file->sid_max = 0;
306
0
                    goto continue_after_realloc_fail;
307
1
                } else {
308
1
                    file->sid = p;
309
1
                    file->sid_max += 8;
310
1
                }
311
1
            }
312
22.0k
            file->sid[file->sid_cnt] = s->id;
313
22.0k
            file->sid_cnt++;
314
22.0k
        }
315
22.0k
    }
316
317
22.0k
continue_after_realloc_fail:
318
319
22.0k
    det_ctx->filestore[det_ctx->filestore_cnt].file_id = file_id;
320
22.0k
    det_ctx->filestore[det_ctx->filestore_cnt].tx_id = det_ctx->tx_id;
321
322
22.0k
    SCLogDebug("%u, file %u, tx %"PRIu64, det_ctx->filestore_cnt,
323
22.0k
        det_ctx->filestore[det_ctx->filestore_cnt].file_id,
324
22.0k
        det_ctx->filestore[det_ctx->filestore_cnt].tx_id);
325
326
22.0k
    det_ctx->filestore_cnt++;
327
22.0k
    SCReturnInt(1);
328
22.0k
}
329
330
/**
331
 * \brief this function is used to parse filestore options
332
 * \brief into the current signature
333
 *
334
 * \param de_ctx pointer to the Detection Engine Context
335
 * \param s pointer to the Current Signature
336
 * \param str pointer to the user provided "filestore" option
337
 *
338
 * \retval 0 on Success
339
 * \retval -1 on Failure
340
 */
341
static int DetectFilestoreSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str)
342
28.7k
{
343
28.7k
    SCEnter();
344
345
28.7k
    static bool warn_not_configured = false;
346
28.7k
    static uint32_t de_version = 0;
347
348
28.7k
    if (de_ctx->filestore_cnt == UINT16_MAX) {
349
0
        SCLogError("Cannot have more than 65535 filestore signatures");
350
0
        return -1;
351
0
    }
352
353
    /* Check on first-time loads (includes following a reload) */
354
28.7k
    if (!warn_not_configured || (de_ctx->version != de_version)) {
355
3.57k
        if (de_version != de_ctx->version) {
356
3.57k
            SCLogDebug("reload-detected; re-checking feature presence; DE version now %"PRIu32,
357
3.57k
                       de_ctx->version);
358
3.57k
        }
359
3.57k
        if (!RequiresFeature(FEATURE_OUTPUT_FILESTORE)) {
360
1
            SCLogWarning("One or more rule(s) depends on the "
361
1
                         "file-store output log which is not enabled. "
362
1
                         "Enable the output \"file-store\".");
363
1
        }
364
3.57k
        warn_not_configured = true;
365
3.57k
        de_version = de_ctx->version;
366
3.57k
    }
367
368
28.7k
    DetectFilestoreData *fd = NULL;
369
28.7k
    SigMatch *sm = NULL;
370
28.7k
    char *args[3] = {NULL,NULL,NULL};
371
28.7k
    int res = 0;
372
28.7k
    size_t pcre2len;
373
28.7k
    pcre2_match_data *match = NULL;
374
375
    /* filestore and bypass keywords can't work together */
376
28.7k
    if (s->flags & SIG_FLAG_BYPASS) {
377
1
        SCLogError("filestore can't work with bypass keyword");
378
1
        return -1;
379
1
    }
380
381
28.7k
    sm = SigMatchAlloc();
382
28.7k
    if (sm == NULL)
383
0
        goto error;
384
385
28.7k
    sm->type = DETECT_FILESTORE;
386
387
28.7k
    if (str != NULL && strlen(str) > 0) {
388
8.41k
        char str_0[32];
389
8.41k
        char str_1[32];
390
8.41k
        char str_2[32];
391
8.41k
        SCLogDebug("str %s", str);
392
393
8.41k
        int ret = DetectParsePcreExec(&parse_regex, &match, str, 0, 0);
394
8.41k
        if (ret < 1 || ret > 4) {
395
286
            SCLogError("parse error, ret %" PRId32 ", string %s", ret, str);
396
286
            goto error;
397
286
        }
398
399
8.13k
        if (ret > 1) {
400
8.13k
            pcre2len = sizeof(str_0);
401
8.13k
            res = pcre2_substring_copy_bynumber(match, 1, (PCRE2_UCHAR8 *)str_0, &pcre2len);
402
8.13k
            if (res < 0) {
403
147
                SCLogError("pcre2_substring_copy_bynumber failed");
404
147
                goto error;
405
147
            }
406
7.98k
            args[0] = (char *)str_0;
407
408
7.98k
            if (ret > 2) {
409
2.02k
                pcre2len = sizeof(str_1);
410
2.02k
                res = pcre2_substring_copy_bynumber(match, 2, (PCRE2_UCHAR8 *)str_1, &pcre2len);
411
2.02k
                if (res < 0) {
412
4
                    SCLogError("pcre2_substring_copy_bynumber failed");
413
4
                    goto error;
414
4
                }
415
2.02k
                args[1] = (char *)str_1;
416
2.02k
            }
417
7.97k
            if (ret > 3) {
418
1.22k
                pcre2len = sizeof(str_2);
419
1.22k
                res = pcre2_substring_copy_bynumber(match, 3, (PCRE2_UCHAR8 *)str_2, &pcre2len);
420
1.22k
                if (res < 0) {
421
10
                    SCLogError("pcre2_substring_copy_bynumber failed");
422
10
                    goto error;
423
10
                }
424
1.21k
                args[2] = (char *)str_2;
425
1.21k
            }
426
7.97k
        }
427
428
7.96k
        fd = SCMalloc(sizeof(DetectFilestoreData));
429
7.96k
        if (unlikely(fd == NULL))
430
0
            goto error;
431
7.96k
        memset(fd, 0x00, sizeof(DetectFilestoreData));
432
433
7.96k
        if (args[0] != NULL) {
434
7.96k
            SCLogDebug("first arg %s", args[0]);
435
436
7.96k
            if (strcasecmp(args[0], "request") == 0 ||
437
7.76k
                    strcasecmp(args[0], "to_server") == 0)
438
350
            {
439
350
                fd->direction = FILESTORE_DIR_TOSERVER;
440
350
                fd->scope = FILESTORE_SCOPE_TX;
441
350
            }
442
7.61k
            else if (strcasecmp(args[0], "response") == 0 ||
443
7.13k
                    strcasecmp(args[0], "to_client") == 0)
444
720
            {
445
720
                fd->direction = FILESTORE_DIR_TOCLIENT;
446
720
                fd->scope = FILESTORE_SCOPE_TX;
447
720
            }
448
6.89k
            else if (strcasecmp(args[0], "both") == 0)
449
266
            {
450
266
                fd->direction = FILESTORE_DIR_BOTH;
451
266
                fd->scope = FILESTORE_SCOPE_TX;
452
266
            }
453
7.96k
        } else {
454
0
            fd->direction = FILESTORE_DIR_DEFAULT;
455
0
        }
456
457
7.96k
        if (args[1] != NULL) {
458
2.01k
            SCLogDebug("second arg %s", args[1]);
459
460
2.01k
            if (strcasecmp(args[1], "file") == 0)
461
13
            {
462
13
                fd->scope = FILESTORE_SCOPE_DEFAULT;
463
1.99k
            } else if (strcasecmp(args[1], "tx") == 0)
464
150
            {
465
150
                fd->scope = FILESTORE_SCOPE_TX;
466
1.84k
            } else if (strcasecmp(args[1], "ssn") == 0 ||
467
1.61k
                       strcasecmp(args[1], "flow") == 0)
468
457
            {
469
457
                fd->scope = FILESTORE_SCOPE_SSN;
470
457
            }
471
5.95k
        } else {
472
5.95k
            if (fd->scope == 0)
473
5.09k
                fd->scope = FILESTORE_SCOPE_DEFAULT;
474
5.95k
        }
475
476
7.96k
        sm->ctx = (SigMatchCtx*)fd;
477
20.3k
    } else {
478
20.3k
        sm->ctx = (SigMatchCtx*)NULL;
479
20.3k
    }
480
481
28.2k
    if (s->alproto == ALPROTO_HTTP1 || s->alproto == ALPROTO_HTTP) {
482
9.21k
        AppLayerHtpNeedFileInspection();
483
9.21k
    }
484
485
28.2k
    SigMatchAppendSMToList(s, sm, g_file_match_list_id);
486
28.2k
    s->filestore_ctx = (const DetectFilestoreData *)sm->ctx;
487
488
28.2k
    sm = SigMatchAlloc();
489
28.2k
    if (unlikely(sm == NULL))
490
0
        goto error;
491
28.2k
    sm->type = DETECT_FILESTORE_POSTMATCH;
492
28.2k
    sm->ctx = NULL;
493
28.2k
    SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_POSTMATCH);
494
495
28.2k
    s->flags |= SIG_FLAG_FILESTORE;
496
28.2k
    de_ctx->filestore_cnt++;
497
498
28.2k
    if (match)
499
28.2k
        pcre2_match_data_free(match);
500
501
28.2k
    return 0;
502
503
447
error:
504
447
    if (match) {
505
447
        pcre2_match_data_free(match);
506
447
    }
507
447
    if (sm != NULL)
508
447
        SCFree(sm);
509
447
    return -1;
510
28.2k
}
511
512
static void DetectFilestoreFree(DetectEngineCtx *de_ctx, void *ptr)
513
27.7k
{
514
27.7k
    if (ptr != NULL) {
515
12.5k
        SCFree(ptr);
516
12.5k
    }
517
27.7k
}
518
519
#ifdef UNITTESTS
520
/*
521
 * The purpose of this test is to confirm that
522
 * filestore and bypass keywords can't
523
 * can't work together
524
 */
525
static int DetectFilestoreTest01(void)
526
{
527
    DetectEngineCtx *de_ctx = NULL;
528
    int result = 1;
529
530
    de_ctx = DetectEngineCtxInit();
531
    FAIL_IF(de_ctx == NULL);
532
533
    de_ctx->flags |= DE_QUIET;
534
535
    de_ctx->sig_list = SigInit(de_ctx,"alert http any any -> any any "
536
                               "(bypass; filestore; "
537
                               "content:\"message\"; http_host; "
538
                               "sid:1;)");
539
    FAIL_IF_NOT_NULL(de_ctx->sig_list);
540
541
    DetectEngineCtxFree(de_ctx);
542
543
    return result;
544
}
545
546
void DetectFilestoreRegisterTests(void)
547
{
548
    UtRegisterTest("DetectFilestoreTest01", DetectFilestoreTest01);
549
}
550
#endif /* UNITTESTS */