/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 */ |