Coverage Report

Created: 2026-09-28 07:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata8/src/detect-filename.c
Line
Count
Source
1
/* Copyright (C) 2007-2022 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
 * \author Pablo Rincon <pablo.rincon.crespo@gmail.com>
23
 *
24
 */
25
26
#include "suricata-common.h"
27
#include "threads.h"
28
#include "decode.h"
29
30
#include "detect.h"
31
32
#include "detect-engine.h"
33
#include "detect-engine-buffer.h"
34
#include "detect-engine-mpm.h"
35
#include "detect-engine-state.h"
36
#include "detect-engine-file.h"
37
#include "detect-engine-prefilter.h"
38
#include "detect-engine-content-inspection.h"
39
40
#include "detect-parse.h"
41
#include "detect-content.h"
42
#include "detect-file-data.h"
43
44
#include "flow.h"
45
#include "flow-var.h"
46
#include "flow-util.h"
47
48
#include "util-debug.h"
49
#include "util-spm-bm.h"
50
#include "util-unittest.h"
51
#include "util-unittest-helper.h"
52
#include "util-profiling.h"
53
54
#include "app-layer.h"
55
#include "app-layer-htp.h"
56
57
#include "stream-tcp.h"
58
59
#include "detect-filename.h"
60
#include "app-layer-parser.h"
61
62
static int DetectFileextSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str);
63
static int DetectFilenameSetup (DetectEngineCtx *, Signature *, const char *);
64
static int DetectFilenameSetupSticky(DetectEngineCtx *de_ctx, Signature *s, const char *str);
65
#ifdef UNITTESTS
66
static void DetectFilenameRegisterTests(void);
67
#endif
68
static int g_file_match_list_id = 0;
69
static int g_file_name_buffer_id = 0;
70
71
static int PrefilterMpmFilenameRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, MpmCtx *mpm_ctx,
72
        const DetectBufferMpmRegistry *mpm_reg, int list_id);
73
static uint8_t DetectEngineInspectFilename(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
74
        const DetectEngineAppInspectionEngine *engine, const Signature *s, Flow *f, uint8_t flags,
75
        void *alstate, void *txv, uint64_t tx_id);
76
77
/**
78
 * \brief Registration function for keyword: filename
79
 */
80
void DetectFilenameRegister(void)
81
79
{
82
79
    sigmatch_table[DETECT_FILENAME].name = "filename";
83
79
    sigmatch_table[DETECT_FILENAME].desc = "match on the file name";
84
79
    sigmatch_table[DETECT_FILENAME].url = "/rules/file-keywords.html#filename";
85
79
    sigmatch_table[DETECT_FILENAME].Setup = DetectFilenameSetup;
86
#ifdef UNITTESTS
87
    sigmatch_table[DETECT_FILENAME].RegisterTests = DetectFilenameRegisterTests;
88
#endif
89
79
    sigmatch_table[DETECT_FILENAME].flags = SIGMATCH_QUOTES_OPTIONAL|SIGMATCH_HANDLE_NEGATION;
90
79
    sigmatch_table[DETECT_FILENAME].alternative = DETECT_FILE_NAME;
91
92
79
    sigmatch_table[DETECT_FILEEXT].name = "fileext";
93
79
    sigmatch_table[DETECT_FILEEXT].desc = "match on the extension of a file name";
94
79
    sigmatch_table[DETECT_FILEEXT].url = "/rules/file-keywords.html#fileext";
95
79
    sigmatch_table[DETECT_FILEEXT].Setup = DetectFileextSetup;
96
79
    sigmatch_table[DETECT_FILEEXT].flags = SIGMATCH_QUOTES_OPTIONAL | SIGMATCH_HANDLE_NEGATION;
97
79
    sigmatch_table[DETECT_FILEEXT].alternative = DETECT_FILE_NAME;
98
99
79
    sigmatch_table[DETECT_FILE_NAME].name = "file.name";
100
79
    sigmatch_table[DETECT_FILE_NAME].desc = "sticky buffer to match on the file name";
101
79
    sigmatch_table[DETECT_FILE_NAME].url = "/rules/file-keywords.html#filename";
102
79
    sigmatch_table[DETECT_FILE_NAME].Setup = DetectFilenameSetupSticky;
103
79
    sigmatch_table[DETECT_FILE_NAME].flags =
104
79
            SIGMATCH_OPTIONAL_OPT | SIGMATCH_INFO_STICKY_BUFFER | SIGMATCH_SUPPORT_DIR;
105
106
79
    DetectBufferTypeSetDescriptionByName("file.name", "file name");
107
108
79
    g_file_match_list_id = DetectBufferTypeRegister("files");
109
79
    g_file_name_buffer_id = DetectBufferTypeRegister("file.name");
110
111
79
    SCLogDebug("registering filename rule option");
112
79
    filehandler_table[DETECT_FILENAME].name = "files";
113
79
    filehandler_table[DETECT_FILENAME].priority = 0;
114
79
    filehandler_table[DETECT_FILENAME].PrefilterFn = NULL;
115
79
    filehandler_table[DETECT_FILENAME].Callback = DetectFileInspectGeneric;
116
117
79
    filehandler_table[DETECT_FILE_NAME].name = "file.name";
118
79
    filehandler_table[DETECT_FILE_NAME].priority = 2;
119
79
    filehandler_table[DETECT_FILE_NAME].PrefilterFn = PrefilterMpmFilenameRegister;
120
79
    filehandler_table[DETECT_FILE_NAME].Callback = DetectEngineInspectFilename;
121
122
79
    DetectBufferTypeSupportsMultiInstance("file.name");
123
79
}
124
125
static int DetectFileextSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str)
126
31.8k
{
127
31.8k
    if (s->init_data->transforms.cnt) {
128
14
        SCLogError("previous transforms not consumed before 'fileext'");
129
14
        SCReturnInt(-1);
130
14
    }
131
31.8k
    s->init_data->list = DETECT_SM_LIST_NOTSET;
132
31.8k
    s->file_flags |= (FILE_SIG_NEED_FILE | FILE_SIG_NEED_FILENAME);
133
134
31.8k
    size_t dotstr_len = strlen(str) + 2;
135
31.8k
    char *dotstr = SCCalloc(1, dotstr_len);
136
31.8k
    if (dotstr == NULL)
137
0
        return -1;
138
31.8k
    dotstr[0] = '.';
139
31.8k
    strlcat(dotstr, str, dotstr_len);
140
141
31.8k
    if (DetectContentSetup(de_ctx, s, dotstr) < 0) {
142
357
        SCFree(dotstr);
143
357
        return -1;
144
357
    }
145
31.4k
    SCFree(dotstr);
146
147
31.4k
    SigMatch *sm = DetectGetLastSMFromLists(s, DETECT_CONTENT, -1);
148
31.4k
    if (sm == NULL)
149
0
        return -1;
150
151
31.4k
    DetectContentData *cd = (DetectContentData *)sm->ctx;
152
31.4k
    cd->flags |= DETECT_CONTENT_ENDS_WITH;
153
31.4k
    if (DetectContentConvertToNocase(de_ctx, cd) != 0)
154
0
        return -1;
155
31.4k
    if (DetectEngineContentModifierBufferSetup(
156
31.4k
                de_ctx, s, NULL, DETECT_FILE_NAME, g_file_name_buffer_id, s->alproto) < 0)
157
176
        return -1;
158
159
31.2k
    return 0;
160
31.4k
}
161
/**
162
 * \brief this function is used to parse filename options
163
 * \brief into the current signature
164
 *
165
 * \param de_ctx pointer to the Detection Engine Context
166
 * \param s pointer to the Current Signature
167
 * \param str pointer to the user provided "filename" option
168
 *
169
 * \retval 0 on Success
170
 * \retval -1 on Failure
171
 */
172
static int DetectFilenameSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str)
173
12.2k
{
174
12.2k
    if (s->init_data->transforms.cnt) {
175
2
        SCLogError("previous transforms not consumed before 'filename'");
176
2
        SCReturnInt(-1);
177
2
    }
178
12.2k
    s->init_data->list = DETECT_SM_LIST_NOTSET;
179
12.2k
    s->file_flags |= (FILE_SIG_NEED_FILE | FILE_SIG_NEED_FILENAME);
180
181
12.2k
    if (DetectContentSetup(de_ctx, s, str) < 0) {
182
1.10k
        return -1;
183
1.10k
    }
184
185
11.1k
    SigMatch *sm = DetectGetLastSMFromLists(s, DETECT_CONTENT, -1);
186
11.1k
    if (sm == NULL)
187
0
        return -1;
188
189
11.1k
    DetectContentData *cd = (DetectContentData *)sm->ctx;
190
11.1k
    if (DetectContentConvertToNocase(de_ctx, cd) != 0)
191
0
        return -1;
192
11.1k
    if (DetectEngineContentModifierBufferSetup(
193
11.1k
                de_ctx, s, NULL, DETECT_FILE_NAME, g_file_name_buffer_id, s->alproto) < 0)
194
2
        return -1;
195
196
11.1k
    return 0;
197
11.1k
}
198
199
/* file.name implementation */
200
201
/**
202
 * \brief this function setup the file.data keyword used in the rule
203
 *
204
 * \param de_ctx   Pointer to the Detection Engine Context
205
 * \param s        Pointer to the Signature to which the current keyword belongs
206
 * \param str      Should hold an empty string always
207
 *
208
 * \retval 0       On success
209
 */
210
static int DetectFilenameSetupSticky(DetectEngineCtx *de_ctx, Signature *s, const char *str)
211
16.0k
{
212
16.0k
    if (SCDetectBufferSetActiveList(de_ctx, s, g_file_name_buffer_id) < 0)
213
353
        return -1;
214
15.7k
    s->file_flags |= (FILE_SIG_NEED_FILE | FILE_SIG_NEED_FILENAME);
215
15.7k
    return 0;
216
16.0k
}
217
218
static InspectionBuffer *FilenameGetDataCallback(DetectEngineThreadCtx *det_ctx,
219
        const DetectEngineTransforms *transforms, Flow *f, uint8_t flow_flags, File *cur_file,
220
        int list_id, int local_file_id)
221
43.1k
{
222
43.1k
    SCEnter();
223
224
43.1k
    InspectionBuffer *buffer = InspectionBufferMultipleForListGet(det_ctx, list_id, local_file_id);
225
43.1k
    if (buffer == NULL)
226
0
        return NULL;
227
43.1k
    if (buffer->initialized)
228
21.2k
        return buffer;
229
230
21.9k
    const uint8_t *data = cur_file->name;
231
21.9k
    uint32_t data_len = cur_file->name_len;
232
233
21.9k
    InspectionBufferSetupMulti(det_ctx, buffer, transforms, data, data_len);
234
235
21.9k
    SCReturnPtr(buffer, "InspectionBuffer");
236
43.1k
}
237
238
static uint8_t DetectEngineInspectFilename(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
239
        const DetectEngineAppInspectionEngine *engine, const Signature *s, Flow *f, uint8_t flags,
240
        void *alstate, void *txv, uint64_t tx_id)
241
57.7k
{
242
57.7k
    const DetectEngineTransforms *transforms = NULL;
243
57.7k
    if (!engine->mpm) {
244
369
        transforms = engine->v2.transforms;
245
369
    }
246
247
57.7k
    AppLayerGetFileState files = AppLayerParserGetTxFiles(f, txv, flags);
248
57.7k
    FileContainer *ffc = files.fc;
249
57.7k
    if (ffc == NULL || ffc->head == NULL) {
250
36.1k
        const bool eof = (AppLayerParserGetStateProgress(f->proto, f->alproto, txv, flags) >
251
36.1k
                          engine->progress);
252
36.1k
        if (eof && engine->match_on_null) {
253
0
            return DETECT_ENGINE_INSPECT_SIG_MATCH;
254
0
        }
255
36.1k
        if (ffc != NULL) {
256
33.9k
            return DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
257
33.9k
        }
258
2.24k
        return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH_FILES;
259
36.1k
    }
260
261
21.5k
    uint8_t r = DETECT_ENGINE_INSPECT_SIG_NO_MATCH;
262
21.5k
    int local_file_id = 0;
263
21.6k
    for (File *file = ffc->head; file != NULL; file = file->next) {
264
21.5k
        InspectionBuffer *buffer = FilenameGetDataCallback(
265
21.5k
                det_ctx, transforms, f, flags, file, engine->sm_list, local_file_id);
266
21.5k
        if (buffer == NULL) {
267
0
            local_file_id++;
268
0
            continue;
269
0
        }
270
271
21.5k
        const bool match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd, NULL, f,
272
21.5k
                buffer->inspect, buffer->inspect_len, buffer->inspect_offset,
273
21.5k
                DETECT_CI_FLAGS_SINGLE, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE);
274
21.5k
        if (match) {
275
21.4k
            return DETECT_ENGINE_INSPECT_SIG_MATCH;
276
21.4k
        } else {
277
116
            r = DETECT_ENGINE_INSPECT_SIG_CANT_MATCH_FILES;
278
116
        }
279
116
        local_file_id++;
280
116
    }
281
108
    return r;
282
21.5k
}
283
284
typedef struct PrefilterMpmFilename {
285
    int list_id;
286
    const MpmCtx *mpm_ctx;
287
    const DetectEngineTransforms *transforms;
288
} PrefilterMpmFilename;
289
290
/** \brief Filedata Filedata Mpm prefilter callback
291
 *
292
 *  \param det_ctx detection engine thread ctx
293
 *  \param p packet to inspect
294
 *  \param f flow to inspect
295
 *  \param txv tx to inspect
296
 *  \param pectx inspection context
297
 */
298
static void PrefilterTxFilename(DetectEngineThreadCtx *det_ctx, const void *pectx, Packet *p,
299
        Flow *f, void *txv, const uint64_t idx, const AppLayerTxData *txd, const uint8_t flags)
300
46.8k
{
301
46.8k
    SCEnter();
302
303
46.8k
    if (!AppLayerParserHasFilesInDir(txd, flags))
304
24.5k
        return;
305
306
22.2k
    const PrefilterMpmFilename *ctx = (const PrefilterMpmFilename *)pectx;
307
22.2k
    const MpmCtx *mpm_ctx = ctx->mpm_ctx;
308
22.2k
    const int list_id = ctx->list_id;
309
310
22.2k
    AppLayerGetFileState files = AppLayerParserGetTxFiles(f, txv, flags);
311
22.2k
    FileContainer *ffc = files.fc;
312
22.2k
    if (ffc != NULL) {
313
22.2k
        int local_file_id = 0;
314
43.8k
        for (File *file = ffc->head; file != NULL; file = file->next) {
315
21.5k
            InspectionBuffer *buffer = FilenameGetDataCallback(
316
21.5k
                    det_ctx, ctx->transforms, f, flags, file, list_id, local_file_id);
317
21.5k
            if (buffer == NULL)
318
0
                continue;
319
320
21.5k
            if (buffer->inspect_len >= mpm_ctx->minlen) {
321
17.1k
                (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx, &det_ctx->mtc, &det_ctx->pmq,
322
17.1k
                        buffer->inspect, buffer->inspect_len);
323
17.1k
                PREFILTER_PROFILING_ADD_BYTES(det_ctx, buffer->inspect_len);
324
17.1k
            }
325
21.5k
            local_file_id++;
326
21.5k
        }
327
22.2k
    }
328
22.2k
}
329
330
static void PrefilterMpmFilenameFree(void *ptr)
331
104k
{
332
104k
    SCFree(ptr);
333
104k
}
334
335
static int PrefilterMpmFilenameRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, MpmCtx *mpm_ctx,
336
        const DetectBufferMpmRegistry *mpm_reg, int list_id)
337
104k
{
338
104k
    PrefilterMpmFilename *pectx = SCCalloc(1, sizeof(*pectx));
339
104k
    if (pectx == NULL)
340
0
        return -1;
341
104k
    pectx->list_id = list_id;
342
104k
    pectx->mpm_ctx = mpm_ctx;
343
104k
    pectx->transforms = &mpm_reg->transforms;
344
345
104k
    return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxFilename,
346
104k
            mpm_reg->app_v2.alproto, mpm_reg->app_v2.tx_min_progress,
347
104k
            pectx, PrefilterMpmFilenameFree, mpm_reg->pname);
348
104k
}
349
350
#ifdef UNITTESTS /* UNITTESTS */
351
352
/**
353
 * \test Test parser accepting valid rules and rejecting invalid rules
354
 */
355
static int DetectFilenameSignatureParseTest01(void)
356
{
357
    FAIL_IF_NOT(UTHParseSignature("alert http any any -> any any (flow:to_client; file.name; content:\"abc\"; sid:1;)", true));
358
    FAIL_IF_NOT(UTHParseSignature("alert http any any -> any any (flow:to_client; file.name; content:\"abc\"; nocase; sid:1;)", true));
359
    FAIL_IF_NOT(UTHParseSignature("alert http any any -> any any (flow:to_client; file.name; content:\"abc\"; endswith; sid:1;)", true));
360
    FAIL_IF_NOT(UTHParseSignature("alert http any any -> any any (flow:to_client; file.name; content:\"abc\"; startswith; sid:1;)", true));
361
    FAIL_IF_NOT(UTHParseSignature("alert http any any -> any any (flow:to_client; file.name; content:\"abc\"; startswith; endswith; sid:1;)", true));
362
    FAIL_IF_NOT(UTHParseSignature("alert http any any -> any any (flow:to_client; file.name; bsize:10; sid:1;)", true));
363
364
    FAIL_IF_NOT(UTHParseSignature("alert http any any -> any any (flow:to_client; file.name; content:\"abc\"; rawbytes; sid:1;)", false));
365
    FAIL_IF_NOT(UTHParseSignature("alert tcp any any -> any any (flow:to_client; file.name; sid:1;)", false));
366
    //FAIL_IF_NOT(UTHParseSignature("alert tls any any -> any any (flow:to_client; file.name; content:\"abc\"; sid:1;)", false));
367
    PASS;
368
}
369
/**
370
 * \brief this function registers unit tests for DetectFilename
371
 */
372
void DetectFilenameRegisterTests(void)
373
{
374
    UtRegisterTest("DetectFilenameSignatureParseTest01", DetectFilenameSignatureParseTest01);
375
}
376
#endif /* UNITTESTS */