/src/suricata/src/detect-file-data.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 | | * |
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 | | |
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-prefilter.h" |
37 | | #include "detect-engine-content-inspection.h" |
38 | | #include "detect-engine-file.h" |
39 | | #include "detect-file-data.h" |
40 | | |
41 | | #include "app-layer.h" |
42 | | #include "app-layer-parser.h" |
43 | | #include "app-layer-htp.h" |
44 | | #include "app-layer-smtp.h" |
45 | | |
46 | | #include "flow.h" |
47 | | #include "flow-var.h" |
48 | | #include "flow-util.h" |
49 | | |
50 | | #include "util-debug.h" |
51 | | #include "util-spm-bm.h" |
52 | | #include "util-unittest.h" |
53 | | #include "util-unittest-helper.h" |
54 | | #include "util-file-decompression.h" |
55 | | #include "util-profiling.h" |
56 | | |
57 | | static int DetectFiledataSetup (DetectEngineCtx *, Signature *, const char *); |
58 | | #ifdef UNITTESTS |
59 | | static void DetectFiledataRegisterTests(void); |
60 | | #endif |
61 | | static void DetectFiledataSetupCallback( |
62 | | const DetectEngineCtx *de_ctx, Signature *s, const DetectBufferType *map); |
63 | | static int g_file_data_buffer_id = 0; |
64 | | |
65 | | /* file API */ |
66 | | int PrefilterMpmFiledataRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, MpmCtx *mpm_ctx, |
67 | | const DetectBufferMpmRegistry *mpm_reg, int list_id); |
68 | | |
69 | | // file protocols with common file handling |
70 | | typedef struct { |
71 | | AppProto alproto; |
72 | | int direction; |
73 | | int to_client_progress; |
74 | | int to_server_progress; |
75 | | } DetectFileHandlerProtocol_t; |
76 | | |
77 | | /* Table with all filehandler registrations */ |
78 | | DetectFileHandlerTableElmt filehandler_table[DETECT_TBLSIZE_STATIC]; |
79 | | |
80 | 0 | #define ALPROTO_WITHFILES_MAX 16 |
81 | | |
82 | | // file protocols with common file handling |
83 | | DetectFileHandlerProtocol_t al_protocols[ALPROTO_WITHFILES_MAX] = { |
84 | | { .alproto = ALPROTO_NFS, .direction = SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT }, |
85 | | { .alproto = ALPROTO_SMB, .direction = SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT }, |
86 | | { .alproto = ALPROTO_FTP, .direction = SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT }, |
87 | | { .alproto = ALPROTO_FTPDATA, .direction = SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT }, |
88 | | { .alproto = ALPROTO_HTTP1, |
89 | | .direction = SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT, |
90 | | .to_client_progress = HTP_RESPONSE_PROGRESS_BODY, |
91 | | .to_server_progress = HTP_REQUEST_PROGRESS_BODY }, |
92 | | { .alproto = ALPROTO_HTTP2, |
93 | | .direction = SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT, |
94 | | .to_client_progress = HTTP2StateDataServer, |
95 | | .to_server_progress = HTTP2StateDataClient }, |
96 | | { .alproto = ALPROTO_SMTP, .direction = SIG_FLAG_TOSERVER }, { .alproto = ALPROTO_UNKNOWN } |
97 | | }; |
98 | | |
99 | | void DetectFileRegisterProto( |
100 | | AppProto alproto, int direction, int to_client_progress, int to_server_progress) |
101 | 0 | { |
102 | 0 | size_t i = 0; |
103 | 0 | while (i < ALPROTO_WITHFILES_MAX && al_protocols[i].alproto != ALPROTO_UNKNOWN) { |
104 | 0 | i++; |
105 | 0 | } |
106 | 0 | if (i == ALPROTO_WITHFILES_MAX) { |
107 | 0 | return; |
108 | 0 | } |
109 | 0 | al_protocols[i].alproto = alproto; |
110 | 0 | al_protocols[i].direction = direction; |
111 | 0 | al_protocols[i].to_client_progress = to_client_progress; |
112 | 0 | al_protocols[i].to_server_progress = to_server_progress; |
113 | 0 | if (i + 1 < ALPROTO_WITHFILES_MAX) { |
114 | 0 | al_protocols[i + 1].alproto = ALPROTO_UNKNOWN; |
115 | 0 | } |
116 | 0 | } |
117 | | |
118 | | void DetectFileRegisterFileProtocols(DetectFileHandlerTableElmt *reg) |
119 | 117 | { |
120 | 936 | for (size_t i = 0; i < g_alproto_max; i++) { |
121 | 936 | if (al_protocols[i].alproto == ALPROTO_UNKNOWN) { |
122 | 117 | break; |
123 | 117 | } |
124 | 819 | int direction = al_protocols[i].direction == 0 |
125 | 819 | ? (int)(SIG_FLAG_TOSERVER | SIG_FLAG_TOCLIENT) |
126 | 819 | : al_protocols[i].direction; |
127 | | |
128 | 819 | if (direction & SIG_FLAG_TOCLIENT) { |
129 | 702 | DetectAppLayerMpmRegister(reg->name, SIG_FLAG_TOCLIENT, reg->priority, reg->PrefilterFn, |
130 | 702 | reg->GetData, al_protocols[i].alproto, al_protocols[i].to_client_progress); |
131 | 702 | DetectAppLayerInspectEngineRegister(reg->name, al_protocols[i].alproto, |
132 | 702 | SIG_FLAG_TOCLIENT, al_protocols[i].to_client_progress, reg->Callback, |
133 | 702 | reg->GetData); |
134 | 702 | } |
135 | 819 | if (direction & SIG_FLAG_TOSERVER) { |
136 | 819 | DetectAppLayerMpmRegister(reg->name, SIG_FLAG_TOSERVER, reg->priority, reg->PrefilterFn, |
137 | 819 | reg->GetData, al_protocols[i].alproto, al_protocols[i].to_server_progress); |
138 | 819 | DetectAppLayerInspectEngineRegister(reg->name, al_protocols[i].alproto, |
139 | 819 | SIG_FLAG_TOSERVER, al_protocols[i].to_server_progress, reg->Callback, |
140 | 819 | reg->GetData); |
141 | 819 | } |
142 | 819 | } |
143 | 117 | } |
144 | | |
145 | | /** |
146 | | * \brief Registration function for keyword: file_data |
147 | | */ |
148 | | void DetectFiledataRegister(void) |
149 | 73 | { |
150 | 73 | sigmatch_table[DETECT_FILE_DATA].name = "file.data"; |
151 | 73 | sigmatch_table[DETECT_FILE_DATA].alias = "file_data"; |
152 | 73 | sigmatch_table[DETECT_FILE_DATA].desc = "make content keywords match on file data"; |
153 | 73 | sigmatch_table[DETECT_FILE_DATA].url = "/rules/file-keywords.html#file-data"; |
154 | 73 | sigmatch_table[DETECT_FILE_DATA].Setup = DetectFiledataSetup; |
155 | | #ifdef UNITTESTS |
156 | | sigmatch_table[DETECT_FILE_DATA].RegisterTests = DetectFiledataRegisterTests; |
157 | | #endif |
158 | 73 | sigmatch_table[DETECT_FILE_DATA].flags = |
159 | 73 | SIGMATCH_OPTIONAL_OPT | SIGMATCH_SUPPORT_DIR | SIGMATCH_INFO_MULTI_BUFFER; |
160 | | |
161 | 73 | filehandler_table[DETECT_FILE_DATA].name = "file_data"; |
162 | 73 | filehandler_table[DETECT_FILE_DATA].priority = 2; |
163 | 73 | filehandler_table[DETECT_FILE_DATA].PrefilterFn = PrefilterMpmFiledataRegister; |
164 | 73 | filehandler_table[DETECT_FILE_DATA].Callback = DetectEngineInspectFiledata; |
165 | | |
166 | 73 | DetectBufferTypeRegisterSetupCallback("file_data", DetectFiledataSetupCallback); |
167 | | |
168 | 73 | DetectBufferTypeSetDescriptionByName("file_data", "data from tracked files"); |
169 | 73 | DetectBufferTypeSupportsMultiInstance("file_data"); |
170 | | |
171 | 73 | g_file_data_buffer_id = DetectBufferTypeGetByName("file_data"); |
172 | 73 | } |
173 | | |
174 | 61.2k | static void SetupDetectEngineConfig(DetectEngineCtx *de_ctx) { |
175 | 61.2k | if (de_ctx->filedata_config) |
176 | 57.8k | return; |
177 | | |
178 | 3.41k | de_ctx->filedata_config = SCMalloc(g_alproto_max * sizeof(DetectFileDataCfg)); |
179 | 3.41k | if (unlikely(de_ctx->filedata_config == NULL)) |
180 | 0 | return; |
181 | | /* initialize default */ |
182 | 140k | for (AppProto i = 0; i < g_alproto_max; i++) { |
183 | 136k | de_ctx->filedata_config[i].content_limit = FILEDATA_CONTENT_LIMIT; |
184 | 136k | de_ctx->filedata_config[i].content_inspect_min_size = FILEDATA_CONTENT_INSPECT_MIN_SIZE; |
185 | 136k | } |
186 | | |
187 | | /* add protocol specific settings here */ |
188 | | |
189 | | /* SMTP */ |
190 | 3.41k | de_ctx->filedata_config[ALPROTO_SMTP].content_limit = smtp_config.content_limit; |
191 | 3.41k | de_ctx->filedata_config[ALPROTO_SMTP].content_inspect_min_size = |
192 | 3.41k | smtp_config.content_inspect_min_size; |
193 | 3.41k | } |
194 | | |
195 | | /** |
196 | | * \brief this function is used to parse filedata options |
197 | | * \brief into the current signature |
198 | | * |
199 | | * \param de_ctx pointer to the Detection Engine Context |
200 | | * \param s pointer to the Current Signature |
201 | | * \param str pointer to the user provided "filestore" option |
202 | | * |
203 | | * \retval 0 on Success |
204 | | * \retval -1 on Failure |
205 | | */ |
206 | | static int DetectFiledataSetup (DetectEngineCtx *de_ctx, Signature *s, const char *str) |
207 | 61.4k | { |
208 | 61.4k | SCEnter(); |
209 | | |
210 | 61.4k | if (s->alproto != ALPROTO_UNKNOWN && !AppLayerParserSupportsFiles(IPPROTO_TCP, s->alproto) && |
211 | 106 | !AppLayerParserSupportsFiles(IPPROTO_UDP, s->alproto)) { |
212 | 106 | SCLogError("The 'file_data' keyword cannot be used with protocol %s", |
213 | 106 | AppLayerGetProtoName(s->alproto)); |
214 | 106 | return -1; |
215 | 106 | } |
216 | | |
217 | 61.3k | if (s->alproto == ALPROTO_SMTP && (s->init_data->init_flags & SIG_FLAG_INIT_FLOW) && |
218 | 217 | !(s->flags & SIG_FLAG_TOSERVER) && (s->flags & SIG_FLAG_TOCLIENT)) { |
219 | 0 | SCLogError("The 'file-data' keyword cannot be used with SMTP flow:to_client or " |
220 | 0 | "flow:from_server."); |
221 | 0 | return -1; |
222 | 0 | } |
223 | | |
224 | 61.3k | if (SCDetectBufferSetActiveList(de_ctx, s, DetectBufferTypeGetByName("file_data")) < 0) |
225 | 97 | return -1; |
226 | | |
227 | 61.2k | s->init_data->init_flags |= SIG_FLAG_INIT_FILEDATA; |
228 | 61.2k | if ((s->init_data->init_flags & SIG_FLAG_INIT_FORCE_TOCLIENT) == 0) { |
229 | | // we cannot use a transactional rule with a fast pattern to client and this |
230 | 61.2k | if (s->init_data->init_flags & SIG_FLAG_INIT_TXDIR_FAST_TOCLIENT) { |
231 | 1 | SCLogError("fast_pattern cannot be used on to_client keyword for " |
232 | 1 | "transactional rule with a streaming buffer to server %u", |
233 | 1 | s->id); |
234 | 1 | return -1; |
235 | 1 | } |
236 | 61.2k | s->init_data->init_flags |= SIG_FLAG_INIT_TXDIR_STREAMING_TOSERVER; |
237 | 61.2k | } |
238 | | |
239 | 61.2k | SetupDetectEngineConfig(de_ctx); |
240 | 61.2k | return 0; |
241 | 61.2k | } |
242 | | |
243 | | static void DetectFiledataSetupCallback( |
244 | | const DetectEngineCtx *de_ctx, Signature *s, const DetectBufferType *map) |
245 | 59.0k | { |
246 | 59.0k | if (s->alproto == ALPROTO_HTTP1 || s->alproto == ALPROTO_UNKNOWN || |
247 | 55.4k | s->alproto == ALPROTO_HTTP) { |
248 | 55.4k | AppLayerHtpEnableResponseBodyCallback(); |
249 | 55.4k | } |
250 | | |
251 | | /* server body needs to be inspected in sync with stream if possible */ |
252 | 59.0k | s->init_data->init_flags |= SIG_FLAG_INIT_NEED_FLUSH; |
253 | | |
254 | 59.0k | SCLogDebug("callback invoked by %u", s->id); |
255 | 59.0k | } |
256 | | |
257 | | /* common */ |
258 | | |
259 | | static void PrefilterMpmFiledataFree(void *ptr) |
260 | 144k | { |
261 | 144k | SCFree(ptr); |
262 | 144k | } |
263 | | |
264 | | /* file API based inspection */ |
265 | | |
266 | | static inline InspectionBuffer *FiledataWithXformsGetDataCallback(DetectEngineThreadCtx *det_ctx, |
267 | | const DetectEngineTransforms *transforms, const int list_id, int local_file_id, |
268 | | InspectionBuffer *base_buffer) |
269 | 16.0k | { |
270 | 16.0k | InspectionBuffer *buffer = InspectionBufferMultipleForListGet(det_ctx, list_id, local_file_id); |
271 | 16.0k | if (buffer == NULL) { |
272 | 0 | SCLogDebug("list_id: %d: no buffer", list_id); |
273 | 0 | return NULL; |
274 | 0 | } |
275 | 16.0k | if (buffer->initialized) { |
276 | 63 | SCLogDebug("list_id: %d: returning %p", list_id, buffer); |
277 | 63 | return buffer; |
278 | 63 | } |
279 | | |
280 | 16.0k | InspectionBufferSetupMulti( |
281 | 16.0k | det_ctx, buffer, transforms, base_buffer->inspect, base_buffer->inspect_len); |
282 | 16.0k | buffer->inspect_offset = base_buffer->inspect_offset; |
283 | 16.0k | SCLogDebug("xformed buffer %p size %u", buffer, buffer->inspect_len); |
284 | 16.0k | SCReturnPtr(buffer, "InspectionBuffer"); |
285 | 16.0k | } |
286 | | |
287 | | static InspectionBuffer *FiledataGetDataCallback(DetectEngineThreadCtx *det_ctx, |
288 | | const DetectEngineTransforms *transforms, Flow *f, uint8_t flow_flags, File *cur_file, |
289 | | const int list_id, const int base_id, int local_file_id, void *txv) |
290 | 28.2k | { |
291 | 28.2k | SCEnter(); |
292 | 28.2k | SCLogDebug("starting: list_id %d base_id %d", list_id, base_id); |
293 | | |
294 | 28.2k | InspectionBuffer *buffer = InspectionBufferMultipleForListGet(det_ctx, base_id, local_file_id); |
295 | 28.2k | SCLogDebug("base: buffer %p", buffer); |
296 | 28.2k | if (buffer == NULL) |
297 | 0 | return NULL; |
298 | 28.2k | if (base_id != list_id && buffer->inspect != NULL) { |
299 | 1.26k | SCLogDebug("handle xform %s", (list_id != base_id) ? "true" : "false"); |
300 | 1.26k | return FiledataWithXformsGetDataCallback( |
301 | 1.26k | det_ctx, transforms, list_id, local_file_id, buffer); |
302 | 1.26k | } |
303 | 27.0k | if (buffer->initialized) { |
304 | 2.21k | SCLogDebug("base_id: %d, not first: use %p", base_id, buffer); |
305 | 2.21k | return buffer; |
306 | 2.21k | } |
307 | | |
308 | 24.7k | const uint64_t file_size = FileDataSize(cur_file); |
309 | 24.7k | const DetectEngineCtx *de_ctx = det_ctx->de_ctx; |
310 | 24.7k | uint32_t content_limit = FILEDATA_CONTENT_LIMIT; |
311 | 24.7k | uint32_t content_inspect_min_size = FILEDATA_CONTENT_INSPECT_MIN_SIZE; |
312 | 24.7k | if (de_ctx->filedata_config) { |
313 | 24.4k | content_limit = de_ctx->filedata_config[f->alproto].content_limit; |
314 | 24.4k | content_inspect_min_size = de_ctx->filedata_config[f->alproto].content_inspect_min_size; |
315 | 24.4k | } |
316 | | |
317 | 24.7k | SCLogDebug("[list %d] content_limit %u, content_inspect_min_size %u", list_id, content_limit, |
318 | 24.7k | content_inspect_min_size); |
319 | | |
320 | 24.7k | SCLogDebug("[list %d] file %p size %" PRIu64 ", state %d", list_id, cur_file, file_size, |
321 | 24.7k | cur_file->state); |
322 | | |
323 | | /* no new data */ |
324 | 24.7k | if (cur_file->content_inspected == file_size) { |
325 | 378 | SCLogDebug("no new data"); |
326 | 378 | goto empty_return; |
327 | 378 | } |
328 | | |
329 | 24.4k | if (file_size == 0) { |
330 | 0 | SCLogDebug("no data to inspect for this transaction"); |
331 | 0 | goto empty_return; |
332 | 0 | } |
333 | | |
334 | 24.4k | SCLogDebug("offset %" PRIu64, StreamingBufferGetOffset(cur_file->sb)); |
335 | 24.4k | SCLogDebug("size %" PRIu64, cur_file->size); |
336 | 24.4k | SCLogDebug("content_inspected %" PRIu64, cur_file->content_inspected); |
337 | 24.4k | SCLogDebug("inspect_window %" PRIu32, cur_file->inspect_window); |
338 | 24.4k | SCLogDebug("inspect_min_size %" PRIu32, cur_file->inspect_min_size); |
339 | | |
340 | 24.4k | bool ips = false; |
341 | 24.4k | uint64_t offset = 0; |
342 | 24.4k | if (f->alproto == ALPROTO_HTTP1) { |
343 | | |
344 | 17.3k | htp_tx_t *tx = txv; |
345 | 17.3k | HtpState *htp_state = f->alstate; |
346 | 17.3k | ips = htp_state->cfg->http_body_inline; |
347 | | |
348 | 17.3k | const bool body_done = AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP1, tx, |
349 | 17.3k | flow_flags) > HTP_RESPONSE_PROGRESS_BODY; |
350 | | |
351 | 17.3k | SCLogDebug("response.body_limit %u file_size %" PRIu64 |
352 | 17.3k | ", cur_file->inspect_min_size %" PRIu32 ", EOF %s, progress > body? %s", |
353 | 17.3k | htp_state->cfg->response.body_limit, file_size, cur_file->inspect_min_size, |
354 | 17.3k | flow_flags & STREAM_EOF ? "true" : "false", BOOL2STR(body_done)); |
355 | | |
356 | 17.3k | if (!htp_state->cfg->http_body_inline) { |
357 | | /* inspect the body if the transfer is complete or we have hit |
358 | | * our body size limit */ |
359 | 17.3k | if ((htp_state->cfg->response.body_limit == 0 || |
360 | 17.3k | file_size < htp_state->cfg->response.body_limit) && |
361 | 17.3k | file_size < cur_file->inspect_min_size && !body_done && |
362 | 5.40k | !(flow_flags & STREAM_EOF)) { |
363 | 5.19k | SCLogDebug("we still haven't seen the entire response body. " |
364 | 5.19k | "Let's defer body inspection till we see the " |
365 | 5.19k | "entire body."); |
366 | 5.19k | goto empty_return; |
367 | 5.19k | } |
368 | 12.1k | SCLogDebug("inline and we're continuing"); |
369 | 12.1k | } |
370 | | |
371 | 17.3k | bool force = (flow_flags & STREAM_EOF) || (cur_file->state > FILE_STATE_OPENED) || |
372 | 2.57k | body_done || htp_state->cfg->http_body_inline; |
373 | | /* get the inspect buffer |
374 | | * |
375 | | * make sure that we have at least the configured inspect_win size. |
376 | | * If we have more, take at least 1/4 of the inspect win size before |
377 | | * the new data. |
378 | | */ |
379 | 12.1k | if (cur_file->content_inspected == 0) { |
380 | 9.63k | if (!force && file_size < cur_file->inspect_min_size) { |
381 | 0 | SCLogDebug("skip as file_size %" PRIu64 " < inspect_min_size %u", file_size, |
382 | 0 | cur_file->inspect_min_size); |
383 | 0 | goto empty_return; |
384 | 0 | } |
385 | 9.63k | } else { |
386 | 2.52k | uint64_t new_data = file_size - cur_file->content_inspected; |
387 | 2.52k | DEBUG_VALIDATE_BUG_ON(new_data == 0); |
388 | 2.52k | if (new_data < cur_file->inspect_window) { |
389 | 2.37k | uint64_t inspect_short = cur_file->inspect_window - new_data; |
390 | 2.37k | if (cur_file->content_inspected < inspect_short) { |
391 | 0 | offset = 0; |
392 | 0 | SCLogDebug("offset %" PRIu64, offset); |
393 | 2.37k | } else { |
394 | 2.37k | offset = cur_file->content_inspected - inspect_short; |
395 | 2.37k | SCLogDebug("offset %" PRIu64, offset); |
396 | 2.37k | } |
397 | 2.37k | } else { |
398 | 149 | BUG_ON(cur_file->content_inspected == 0); |
399 | 149 | uint32_t margin = cur_file->inspect_window / 4; |
400 | 149 | if ((uint64_t)margin <= cur_file->content_inspected) { |
401 | 149 | offset = cur_file->content_inspected - (cur_file->inspect_window / 4); |
402 | 149 | } else { |
403 | 0 | offset = 0; |
404 | 0 | } |
405 | 149 | SCLogDebug("offset %" PRIu64 " (data from offset %" PRIu64 ")", offset, |
406 | 149 | file_size - offset); |
407 | 149 | } |
408 | 2.52k | } |
409 | | |
410 | 12.1k | } else { |
411 | 7.05k | if ((content_limit == 0 || file_size < content_limit) && |
412 | 6.74k | file_size < content_inspect_min_size && !(flow_flags & STREAM_EOF) && |
413 | 6.38k | !(cur_file->state > FILE_STATE_OPENED)) { |
414 | 2.78k | SCLogDebug("we still haven't seen the entire content. " |
415 | 2.78k | "Let's defer content inspection till we see the " |
416 | 2.78k | "entire content. We've seen %ld and need at least %d", |
417 | 2.78k | file_size, content_inspect_min_size); |
418 | 2.78k | goto empty_return; |
419 | 2.78k | } |
420 | 4.26k | offset = cur_file->content_inspected; |
421 | 4.26k | } |
422 | | |
423 | 16.4k | const uint8_t *data; |
424 | 16.4k | uint32_t data_len; |
425 | | |
426 | 16.4k | SCLogDebug("Fetching data at offset: %ld", offset); |
427 | 16.4k | StreamingBufferGetDataAtOffset(cur_file->sb, &data, &data_len, offset); |
428 | 16.4k | SCLogDebug("data_len %u", data_len); |
429 | | /* update inspected tracker */ |
430 | 16.4k | buffer->inspect_offset = offset; |
431 | | |
432 | 16.4k | if (ips && file_size < cur_file->inspect_min_size) { |
433 | | // don't update content_inspected yet |
434 | 16.4k | } else { |
435 | 16.4k | SCLogDebug("content inspected: %" PRIu64, cur_file->content_inspected); |
436 | 16.4k | cur_file->content_inspected = MAX(cur_file->content_inspected, offset + data_len); |
437 | 16.4k | SCLogDebug("content inspected: %" PRIu64, cur_file->content_inspected); |
438 | 16.4k | } |
439 | | |
440 | 16.4k | InspectionBufferSetupMulti(det_ctx, buffer, NULL, data, data_len); |
441 | 16.4k | SCLogDebug("[list %d] [before] buffer offset %" PRIu64 "; buffer len %" PRIu32 |
442 | 16.4k | "; data_len %" PRIu32 "; file_size %" PRIu64, |
443 | 16.4k | list_id, buffer->inspect_offset, buffer->inspect_len, data_len, file_size); |
444 | | |
445 | 16.4k | if (f->alproto == ALPROTO_HTTP1 && flow_flags & STREAM_TOCLIENT) { |
446 | 11.5k | HtpState *htp_state = f->alstate; |
447 | | /* built-in 'transformation' */ |
448 | 11.5k | if (htp_state->cfg->swf_decompression_enabled) { |
449 | 11.5k | int swf_file_type = FileIsSwfFile(data, data_len); |
450 | 11.5k | if (swf_file_type == FILE_SWF_ZLIB_COMPRESSION || |
451 | 11.5k | swf_file_type == FILE_SWF_LZMA_COMPRESSION) { |
452 | 1 | SCLogDebug("decompressing ..."); |
453 | 1 | (void)FileSwfDecompression(data, data_len, det_ctx, buffer, |
454 | 1 | htp_state->cfg->swf_compression_type, htp_state->cfg->swf_decompress_depth, |
455 | 1 | htp_state->cfg->swf_compress_depth); |
456 | 1 | SCLogDebug("uncompressed buffer %p size %u; buf: \"%s\"", buffer, |
457 | 1 | buffer->inspect_len, (char *)buffer->inspect); |
458 | 1 | } |
459 | 11.5k | } |
460 | 11.5k | } |
461 | | |
462 | 16.4k | SCLogDebug("content inspected: %" PRIu64, cur_file->content_inspected); |
463 | | |
464 | | /* get buffer for the list id if it is different from the base id */ |
465 | 16.4k | if (list_id != base_id) { |
466 | 13.2k | SCLogDebug("regular %d has been set up: now handle xforms id %d", base_id, list_id); |
467 | 13.2k | InspectionBuffer *tbuffer = FiledataWithXformsGetDataCallback( |
468 | 13.2k | det_ctx, transforms, list_id, local_file_id, buffer); |
469 | 13.2k | SCReturnPtr(tbuffer, "InspectionBuffer"); |
470 | 13.2k | } |
471 | 16.4k | SCReturnPtr(buffer, "InspectionBuffer"); |
472 | | |
473 | 8.35k | empty_return: |
474 | 8.35k | InspectionBufferSetupMultiEmpty(buffer); |
475 | 8.35k | return NULL; |
476 | 16.4k | } |
477 | | |
478 | | uint8_t DetectEngineInspectFiledata(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, |
479 | | const DetectEngineAppInspectionEngine *engine, const Signature *s, Flow *f, uint8_t flags, |
480 | | void *alstate, void *txv, uint64_t tx_id) |
481 | 41.6k | { |
482 | 41.6k | const DetectEngineTransforms *transforms = NULL; |
483 | 41.6k | if (!engine->mpm) { |
484 | 39.7k | transforms = engine->v2.transforms; |
485 | 39.7k | } |
486 | | |
487 | 41.6k | AppLayerGetFileState files = AppLayerParserGetTxFiles(f, txv, flags); |
488 | 41.6k | FileContainer *ffc = files.fc; |
489 | 41.6k | if (ffc == NULL) { |
490 | 2.15k | return DETECT_ENGINE_INSPECT_SIG_CANT_MATCH_FILES; |
491 | 2.15k | } |
492 | 39.4k | if (ffc->head == NULL) { |
493 | 25.9k | const bool eof = (AppLayerParserGetStateProgress(f->proto, f->alproto, txv, flags) > |
494 | 25.9k | engine->progress); |
495 | 25.9k | if (eof && engine->match_on_null) { |
496 | 18 | return DETECT_ENGINE_INSPECT_SIG_MATCH; |
497 | 18 | } |
498 | 25.9k | return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; |
499 | 25.9k | } |
500 | | |
501 | 13.4k | int local_file_id = 0; |
502 | 13.4k | File *file = ffc->head; |
503 | 19.2k | for (; file != NULL; file = file->next) { |
504 | 13.4k | InspectionBuffer *buffer = FiledataGetDataCallback(det_ctx, transforms, f, flags, file, |
505 | 13.4k | engine->sm_list, engine->sm_list_base, local_file_id, txv); |
506 | 13.4k | if (buffer == NULL) { |
507 | 3.25k | local_file_id++; |
508 | 3.25k | continue; |
509 | 3.25k | } |
510 | | |
511 | 13.4k | bool eof = (file->state == FILE_STATE_CLOSED); |
512 | 10.2k | uint8_t ciflags = eof ? DETECT_CI_FLAGS_END : 0; |
513 | 10.2k | if (buffer->inspect_offset == 0) |
514 | 8.86k | ciflags |= DETECT_CI_FLAGS_START; |
515 | | |
516 | 10.2k | const bool match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd, NULL, f, |
517 | 10.2k | buffer->inspect, buffer->inspect_len, buffer->inspect_offset, ciflags, |
518 | 10.2k | DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE); |
519 | 10.2k | if (match) { |
520 | 7.73k | return DETECT_ENGINE_INSPECT_SIG_MATCH; |
521 | 7.73k | } |
522 | 2.49k | local_file_id++; |
523 | 2.49k | } |
524 | | |
525 | 5.73k | return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; |
526 | 13.4k | } |
527 | | |
528 | | /** \brief Filedata Filedata Mpm prefilter callback |
529 | | * |
530 | | * \param det_ctx detection engine thread ctx |
531 | | * \param pectx inspection context |
532 | | * \param p packet to inspect |
533 | | * \param f flow to inspect |
534 | | * \param txv tx to inspect |
535 | | * \param idx transaction id |
536 | | * \param flags STREAM_* flags including direction |
537 | | */ |
538 | | static void PrefilterTxFiledata(DetectEngineThreadCtx *det_ctx, const void *pectx, Packet *p, |
539 | | Flow *f, void *txv, const uint64_t idx, const AppLayerTxData *txd, const uint8_t flags) |
540 | 82.8k | { |
541 | 82.8k | SCEnter(); |
542 | | |
543 | 82.8k | if (!AppLayerParserHasFilesInDir(txd, flags)) |
544 | 65.9k | return; |
545 | | |
546 | 16.9k | const PrefilterMpmFiledata *ctx = (const PrefilterMpmFiledata *)pectx; |
547 | 16.9k | const MpmCtx *mpm_ctx = ctx->mpm_ctx; |
548 | 16.9k | const int list_id = ctx->list_id; |
549 | | |
550 | 16.9k | AppLayerGetFileState files = AppLayerParserGetTxFiles(f, txv, flags); |
551 | 16.9k | FileContainer *ffc = files.fc; |
552 | 16.9k | if (ffc != NULL) { |
553 | 16.9k | int local_file_id = 0; |
554 | 31.7k | for (File *file = ffc->head; file != NULL; file = file->next) { |
555 | 14.7k | InspectionBuffer *buffer = FiledataGetDataCallback(det_ctx, ctx->transforms, f, flags, |
556 | 14.7k | file, list_id, ctx->base_list_id, local_file_id, txv); |
557 | 14.7k | if (buffer == NULL) { |
558 | 5.09k | local_file_id++; |
559 | 5.09k | continue; |
560 | 5.09k | } |
561 | 9.69k | SCLogDebug("[%" PRIu64 "] buffer size %u", PcapPacketCntGet(p), buffer->inspect_len); |
562 | | |
563 | 9.69k | if (buffer->inspect_len >= mpm_ctx->minlen) { |
564 | 6.51k | uint32_t prev_rule_id_array_cnt = det_ctx->pmq.rule_id_array_cnt; |
565 | 6.51k | (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx, &det_ctx->mtc, &det_ctx->pmq, |
566 | 6.51k | buffer->inspect, buffer->inspect_len); |
567 | 6.51k | PREFILTER_PROFILING_ADD_BYTES(det_ctx, buffer->inspect_len); |
568 | | |
569 | 6.51k | if (det_ctx->pmq.rule_id_array_cnt > prev_rule_id_array_cnt) { |
570 | 781 | SCLogDebug( |
571 | 781 | "%u matches", det_ctx->pmq.rule_id_array_cnt - prev_rule_id_array_cnt); |
572 | 781 | } |
573 | 6.51k | } |
574 | 9.69k | local_file_id++; |
575 | 9.69k | } |
576 | 16.9k | } |
577 | 16.9k | } |
578 | | |
579 | | int PrefilterMpmFiledataRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, MpmCtx *mpm_ctx, |
580 | | const DetectBufferMpmRegistry *mpm_reg, int list_id) |
581 | 145k | { |
582 | 145k | PrefilterMpmFiledata *pectx = SCCalloc(1, sizeof(*pectx)); |
583 | 145k | if (pectx == NULL) |
584 | 0 | return -1; |
585 | 145k | pectx->list_id = list_id; |
586 | 145k | pectx->base_list_id = mpm_reg->sm_list_base; |
587 | 145k | pectx->mpm_ctx = mpm_ctx; |
588 | 145k | pectx->transforms = &mpm_reg->transforms; |
589 | | |
590 | 145k | return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxFiledata, |
591 | 145k | mpm_reg->app_v2.alproto, mpm_reg->app_v2.tx_min_progress, |
592 | 145k | pectx, PrefilterMpmFiledataFree, mpm_reg->pname); |
593 | 145k | } |
594 | | |
595 | | #ifdef UNITTESTS |
596 | | #include "tests/detect-file-data.c" |
597 | | #endif |