/src/suricata7/src/detect-filemagic.c
Line | Count | Source |
1 | | /* Copyright (C) 2007-2023 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 | | */ |
24 | | |
25 | | #include "suricata-common.h" |
26 | | #include "threads.h" |
27 | | #include "decode.h" |
28 | | |
29 | | #include "detect.h" |
30 | | #include "detect-parse.h" |
31 | | #include "detect-content.h" |
32 | | |
33 | | #include "detect-engine.h" |
34 | | #include "detect-engine-mpm.h" |
35 | | #include "detect-engine-prefilter.h" |
36 | | #include "detect-engine-content-inspection.h" |
37 | | #include "detect-engine-file.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-magic.h" |
46 | | #include "util-print.h" |
47 | | |
48 | | #include "util-unittest.h" |
49 | | #include "util-unittest-helper.h" |
50 | | #include "util-profiling.h" |
51 | | |
52 | | #include "app-layer.h" |
53 | | #include "app-layer-parser.h" |
54 | | |
55 | | #include "stream-tcp.h" |
56 | | |
57 | | #include "detect-file-data.h" |
58 | | #include "detect-filemagic.h" |
59 | | |
60 | | #include "conf.h" |
61 | | |
62 | | #ifndef HAVE_MAGIC |
63 | | |
64 | | static int DetectFilemagicSetupNoSupport (DetectEngineCtx *de_ctx, Signature *s, const char *str) |
65 | 642 | { |
66 | 642 | SCLogError("no libmagic support built in, needed for filemagic keyword"); |
67 | 642 | return -1; |
68 | 642 | } |
69 | | |
70 | | /** |
71 | | * \brief Registration function for keyword: filemagic |
72 | | */ |
73 | | void DetectFilemagicRegister(void) |
74 | 73 | { |
75 | 73 | sigmatch_table[DETECT_FILEMAGIC].name = "filemagic"; |
76 | 73 | sigmatch_table[DETECT_FILEMAGIC].desc = "match on the information libmagic returns about a file"; |
77 | 73 | sigmatch_table[DETECT_FILEMAGIC].url = "/rules/file-keywords.html#filemagic"; |
78 | 73 | sigmatch_table[DETECT_FILEMAGIC].Setup = DetectFilemagicSetupNoSupport; |
79 | 73 | sigmatch_table[DETECT_FILEMAGIC].flags = SIGMATCH_QUOTES_MANDATORY|SIGMATCH_HANDLE_NEGATION; |
80 | 73 | } |
81 | | |
82 | | #else /* HAVE_MAGIC */ |
83 | | |
84 | | typedef struct DetectFilemagicThreadData { |
85 | | magic_t ctx; |
86 | | } DetectFilemagicThreadData; |
87 | | |
88 | | static int DetectFilemagicSetup(DetectEngineCtx *, Signature *, const char *); |
89 | | static int g_file_match_list_id = 0; |
90 | | |
91 | | static int DetectFilemagicSetupSticky(DetectEngineCtx *de_ctx, Signature *s, const char *str); |
92 | | static int g_file_magic_buffer_id = 0; |
93 | | |
94 | | static int PrefilterMpmFilemagicRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, |
95 | | MpmCtx *mpm_ctx, const DetectBufferMpmRegistry *mpm_reg, int list_id); |
96 | | static uint8_t DetectEngineInspectFilemagic(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, |
97 | | const DetectEngineAppInspectionEngine *engine, const Signature *s, Flow *f, uint8_t flags, |
98 | | void *alstate, void *txv, uint64_t tx_id); |
99 | | |
100 | | /** |
101 | | * \brief Registration function for keyword: filemagic |
102 | | */ |
103 | | void DetectFilemagicRegister(void) |
104 | | { |
105 | | sigmatch_table[DETECT_FILEMAGIC].name = "filemagic"; |
106 | | sigmatch_table[DETECT_FILEMAGIC].desc = "match on the information libmagic returns about a file"; |
107 | | sigmatch_table[DETECT_FILEMAGIC].url = "/rules/file-keywords.html#filemagic"; |
108 | | sigmatch_table[DETECT_FILEMAGIC].Setup = DetectFilemagicSetup; |
109 | | sigmatch_table[DETECT_FILEMAGIC].flags = SIGMATCH_QUOTES_MANDATORY|SIGMATCH_HANDLE_NEGATION; |
110 | | sigmatch_table[DETECT_FILEMAGIC].alternative = DETECT_FILE_MAGIC; |
111 | | |
112 | | sigmatch_table[DETECT_FILE_MAGIC].name = "file.magic"; |
113 | | sigmatch_table[DETECT_FILE_MAGIC].desc = "sticky buffer to match on the file magic"; |
114 | | sigmatch_table[DETECT_FILE_MAGIC].url = "/rules/file-keywords.html#filemagic"; |
115 | | sigmatch_table[DETECT_FILE_MAGIC].Setup = DetectFilemagicSetupSticky; |
116 | | sigmatch_table[DETECT_FILE_MAGIC].flags = SIGMATCH_NOOPT|SIGMATCH_INFO_STICKY_BUFFER; |
117 | | |
118 | | filehandler_table[DETECT_FILE_MAGIC].name = "file.magic", |
119 | | filehandler_table[DETECT_FILE_MAGIC].priority = 2; |
120 | | filehandler_table[DETECT_FILE_MAGIC].PrefilterFn = PrefilterMpmFilemagicRegister; |
121 | | filehandler_table[DETECT_FILE_MAGIC].Callback = DetectEngineInspectFilemagic; |
122 | | |
123 | | g_file_match_list_id = DetectBufferTypeRegister("files"); |
124 | | |
125 | | DetectBufferTypeSetDescriptionByName("file.magic", "file magic"); |
126 | | DetectBufferTypeSupportsMultiInstance("file.magic"); |
127 | | |
128 | | g_file_magic_buffer_id = DetectBufferTypeGetByName("file.magic"); |
129 | | SCLogDebug("registering filemagic rule option"); |
130 | | return; |
131 | | } |
132 | | |
133 | | #define FILEMAGIC_MIN_SIZE 512 |
134 | | |
135 | | /** |
136 | | * \brief run the magic check |
137 | | * |
138 | | * \param file the file |
139 | | * |
140 | | * \retval -1 error |
141 | | * \retval 0 ok |
142 | | */ |
143 | | int FilemagicThreadLookup(magic_t *ctx, File *file) |
144 | | { |
145 | | if (ctx == NULL || file == NULL || FileDataSize(file) == 0) { |
146 | | SCReturnInt(-1); |
147 | | } |
148 | | |
149 | | const uint8_t *data = NULL; |
150 | | uint32_t data_len = 0; |
151 | | uint64_t offset = 0; |
152 | | |
153 | | StreamingBufferGetData(file->sb, |
154 | | &data, &data_len, &offset); |
155 | | if (offset == 0) { |
156 | | if (FileDataSize(file) >= FILEMAGIC_MIN_SIZE) { |
157 | | file->magic = MagicThreadLookup(ctx, data, data_len); |
158 | | } else if (file->state >= FILE_STATE_CLOSED) { |
159 | | file->magic = MagicThreadLookup(ctx, data, data_len); |
160 | | } |
161 | | } |
162 | | SCReturnInt(0); |
163 | | } |
164 | | |
165 | | static void *DetectFilemagicThreadInit(void *data /*@unused@*/) |
166 | | { |
167 | | DetectFilemagicThreadData *t = SCCalloc(1, sizeof(DetectFilemagicThreadData)); |
168 | | if (unlikely(t == NULL)) { |
169 | | SCLogError("couldn't alloc ctx memory"); |
170 | | return NULL; |
171 | | } |
172 | | |
173 | | t->ctx = MagicInitContext(); |
174 | | if (t->ctx == NULL) |
175 | | goto error; |
176 | | |
177 | | return (void *)t; |
178 | | |
179 | | error: |
180 | | if (t->ctx) |
181 | | magic_close(t->ctx); |
182 | | SCFree(t); |
183 | | return NULL; |
184 | | } |
185 | | |
186 | | static void DetectFilemagicThreadFree(void *ctx) |
187 | | { |
188 | | if (ctx != NULL) { |
189 | | DetectFilemagicThreadData *t = (DetectFilemagicThreadData *)ctx; |
190 | | if (t->ctx) |
191 | | magic_close(t->ctx); |
192 | | SCFree(t); |
193 | | } |
194 | | } |
195 | | |
196 | | /** |
197 | | * \brief this function is used to parse filemagic options |
198 | | * \brief into the current signature |
199 | | * |
200 | | * \param de_ctx pointer to the Detection Engine Context |
201 | | * \param s pointer to the Current Signature |
202 | | * \param str pointer to the user provided "filemagic" option |
203 | | * |
204 | | * \retval 0 on Success |
205 | | * \retval -1 on Failure |
206 | | */ |
207 | | static int DetectFilemagicSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str) |
208 | | { |
209 | | if (s->init_data->transforms.cnt) { |
210 | | SCLogError("previous transforms not consumed before 'filemagic'"); |
211 | | SCReturnInt(-1); |
212 | | } |
213 | | s->init_data->list = DETECT_SM_LIST_NOTSET; |
214 | | s->file_flags |= (FILE_SIG_NEED_FILE | FILE_SIG_NEED_MAGIC); |
215 | | |
216 | | if (DetectContentSetup(de_ctx, s, str) < 0) { |
217 | | return -1; |
218 | | } |
219 | | |
220 | | SigMatch *sm = DetectGetLastSMFromLists(s, DETECT_CONTENT, -1); |
221 | | if (sm == NULL) |
222 | | return -1; |
223 | | |
224 | | DetectContentData *cd = (DetectContentData *)sm->ctx; |
225 | | if (DetectContentConvertToNocase(de_ctx, cd) != 0) |
226 | | return -1; |
227 | | if (DetectEngineContentModifierBufferSetup( |
228 | | de_ctx, s, NULL, DETECT_FILE_MAGIC, g_file_magic_buffer_id, s->alproto) < 0) |
229 | | return -1; |
230 | | |
231 | | if (de_ctx->filemagic_thread_ctx_id == -1) { |
232 | | de_ctx->filemagic_thread_ctx_id = DetectRegisterThreadCtxFuncs( |
233 | | de_ctx, "filemagic", DetectFilemagicThreadInit, NULL, DetectFilemagicThreadFree, 1); |
234 | | if (de_ctx->filemagic_thread_ctx_id == -1) |
235 | | return -1; |
236 | | } |
237 | | return 0; |
238 | | } |
239 | | |
240 | | /* file.magic implementation */ |
241 | | |
242 | | /** |
243 | | * \brief this function setup the file.magic keyword used in the rule |
244 | | * |
245 | | * \param de_ctx Pointer to the Detection Engine Context |
246 | | * \param s Pointer to the Signature to which the current keyword belongs |
247 | | * \param str Should hold an empty string always |
248 | | * |
249 | | * \retval 0 On success |
250 | | */ |
251 | | static int DetectFilemagicSetupSticky(DetectEngineCtx *de_ctx, Signature *s, const char *str) |
252 | | { |
253 | | if (DetectBufferSetActiveList(de_ctx, s, g_file_magic_buffer_id) < 0) |
254 | | return -1; |
255 | | |
256 | | if (de_ctx->filemagic_thread_ctx_id == -1) { |
257 | | de_ctx->filemagic_thread_ctx_id = DetectRegisterThreadCtxFuncs( |
258 | | de_ctx, "filemagic", DetectFilemagicThreadInit, NULL, DetectFilemagicThreadFree, 1); |
259 | | if (de_ctx->filemagic_thread_ctx_id == -1) |
260 | | return -1; |
261 | | } |
262 | | return 0; |
263 | | } |
264 | | |
265 | | static InspectionBuffer *FilemagicGetDataCallback(DetectEngineThreadCtx *det_ctx, |
266 | | const DetectEngineTransforms *transforms, Flow *f, uint8_t flow_flags, File *cur_file, |
267 | | int list_id, int local_file_id) |
268 | | { |
269 | | SCEnter(); |
270 | | |
271 | | InspectionBuffer *buffer = InspectionBufferMultipleForListGet(det_ctx, list_id, local_file_id); |
272 | | if (buffer == NULL) |
273 | | return NULL; |
274 | | if (buffer->initialized) |
275 | | return buffer; |
276 | | |
277 | | if (cur_file->magic == NULL) { |
278 | | DetectFilemagicThreadData *tfilemagic = |
279 | | (DetectFilemagicThreadData *)DetectThreadCtxGetKeywordThreadCtx( |
280 | | det_ctx, det_ctx->de_ctx->filemagic_thread_ctx_id); |
281 | | if (tfilemagic == NULL) { |
282 | | InspectionBufferSetupMultiEmpty(buffer); |
283 | | return NULL; |
284 | | } |
285 | | |
286 | | FilemagicThreadLookup(&tfilemagic->ctx, cur_file); |
287 | | } |
288 | | if (cur_file->magic == NULL) { |
289 | | return NULL; |
290 | | } |
291 | | |
292 | | const uint8_t *data = (const uint8_t *)cur_file->magic; |
293 | | uint32_t data_len = (uint32_t)strlen(cur_file->magic); |
294 | | |
295 | | InspectionBufferSetupMulti(buffer, transforms, data, data_len); |
296 | | |
297 | | SCReturnPtr(buffer, "InspectionBuffer"); |
298 | | } |
299 | | |
300 | | static uint8_t DetectEngineInspectFilemagic(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, |
301 | | const DetectEngineAppInspectionEngine *engine, const Signature *s, Flow *f, uint8_t flags, |
302 | | void *alstate, void *txv, uint64_t tx_id) |
303 | | { |
304 | | const DetectEngineTransforms *transforms = NULL; |
305 | | if (!engine->mpm) { |
306 | | transforms = engine->v2.transforms; |
307 | | } |
308 | | |
309 | | AppLayerGetFileState files = AppLayerParserGetTxFiles(f, alstate, txv, flags); |
310 | | FileContainer *ffc = files.fc; |
311 | | if (ffc == NULL) { |
312 | | return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH_FILES; |
313 | | } |
314 | | |
315 | | uint8_t r = DETECT_ENGINE_INSPECT_SIG_NO_MATCH; |
316 | | int local_file_id = 0; |
317 | | for (File *file = ffc->head; file != NULL; file = file->next) { |
318 | | InspectionBuffer *buffer = FilemagicGetDataCallback( |
319 | | det_ctx, transforms, f, flags, file, engine->sm_list, local_file_id); |
320 | | if (buffer == NULL) |
321 | | continue; |
322 | | |
323 | | det_ctx->buffer_offset = 0; |
324 | | det_ctx->discontinue_matching = 0; |
325 | | det_ctx->inspection_recursion_counter = 0; |
326 | | int match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd, |
327 | | NULL, f, |
328 | | (uint8_t *)buffer->inspect, |
329 | | buffer->inspect_len, |
330 | | buffer->inspect_offset, DETECT_CI_FLAGS_SINGLE, |
331 | | DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE); |
332 | | if (match == 1) { |
333 | | return DETECT_ENGINE_INSPECT_SIG_MATCH; |
334 | | } else { |
335 | | r = DETECT_ENGINE_INSPECT_SIG_CANT_MATCH_FILES; |
336 | | } |
337 | | local_file_id++; |
338 | | } |
339 | | return r; |
340 | | } |
341 | | |
342 | | typedef struct PrefilterMpmFilemagic { |
343 | | int list_id; |
344 | | const MpmCtx *mpm_ctx; |
345 | | const DetectEngineTransforms *transforms; |
346 | | } PrefilterMpmFilemagic; |
347 | | |
348 | | /** \brief Filedata Filedata Mpm prefilter callback |
349 | | * |
350 | | * \param det_ctx detection engine thread ctx |
351 | | * \param p packet to inspect |
352 | | * \param f flow to inspect |
353 | | * \param txv tx to inspect |
354 | | * \param pectx inspection context |
355 | | */ |
356 | | static void PrefilterTxFilemagic(DetectEngineThreadCtx *det_ctx, const void *pectx, Packet *p, |
357 | | Flow *f, void *txv, const uint64_t idx, const AppLayerTxData *txd, const uint8_t flags) |
358 | | { |
359 | | SCEnter(); |
360 | | |
361 | | if (!AppLayerParserHasFilesInDir(txd, flags)) |
362 | | return; |
363 | | |
364 | | const PrefilterMpmFilemagic *ctx = (const PrefilterMpmFilemagic *)pectx; |
365 | | const MpmCtx *mpm_ctx = ctx->mpm_ctx; |
366 | | const int list_id = ctx->list_id; |
367 | | |
368 | | AppLayerGetFileState files = AppLayerParserGetTxFiles(f, f->alstate, txv, flags); |
369 | | FileContainer *ffc = files.fc; |
370 | | if (ffc != NULL) { |
371 | | int local_file_id = 0; |
372 | | for (File *file = ffc->head; file != NULL; file = file->next) { |
373 | | InspectionBuffer *buffer = FilemagicGetDataCallback( |
374 | | det_ctx, ctx->transforms, f, flags, file, list_id, local_file_id); |
375 | | if (buffer == NULL) |
376 | | continue; |
377 | | |
378 | | if (buffer->inspect_len >= mpm_ctx->minlen) { |
379 | | (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx, |
380 | | &det_ctx->mtcu, &det_ctx->pmq, |
381 | | buffer->inspect, buffer->inspect_len); |
382 | | PREFILTER_PROFILING_ADD_BYTES(det_ctx, buffer->inspect_len); |
383 | | } |
384 | | local_file_id++; |
385 | | } |
386 | | } |
387 | | } |
388 | | |
389 | | static void PrefilterMpmFilemagicFree(void *ptr) |
390 | | { |
391 | | SCFree(ptr); |
392 | | } |
393 | | |
394 | | static int PrefilterMpmFilemagicRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, |
395 | | MpmCtx *mpm_ctx, const DetectBufferMpmRegistry *mpm_reg, int list_id) |
396 | | { |
397 | | PrefilterMpmFilemagic *pectx = SCCalloc(1, sizeof(*pectx)); |
398 | | if (pectx == NULL) |
399 | | return -1; |
400 | | pectx->list_id = list_id; |
401 | | pectx->mpm_ctx = mpm_ctx; |
402 | | pectx->transforms = &mpm_reg->transforms; |
403 | | |
404 | | return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxFilemagic, |
405 | | mpm_reg->app_v2.alproto, mpm_reg->app_v2.tx_min_progress, |
406 | | pectx, PrefilterMpmFilemagicFree, mpm_reg->pname); |
407 | | } |
408 | | |
409 | | #endif /* HAVE_MAGIC */ |