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