/src/suricata7/src/detect-fragoffset.c
Line | Count | Source |
1 | | /* Copyright (C) 2007-2021 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 Breno Silva <breno.silva@gmail.com> |
22 | | * |
23 | | * Implements fragoffset keyword |
24 | | */ |
25 | | |
26 | | #include "suricata-common.h" |
27 | | #include "decode.h" |
28 | | #include "decode-ipv4.h" |
29 | | #include "decode-ipv6.h" |
30 | | |
31 | | #include "detect.h" |
32 | | #include "detect-parse.h" |
33 | | #include "detect-engine-prefilter-common.h" |
34 | | #include "detect-engine-build.h" |
35 | | |
36 | | #include "detect-fragoffset.h" |
37 | | |
38 | | #include "util-byte.h" |
39 | | #include "util-unittest.h" |
40 | | #include "util-debug.h" |
41 | | |
42 | 73 | #define PARSE_REGEX "^\\s*(?:(<|>))?\\s*([0-9]+)" |
43 | | |
44 | | static DetectParseRegex parse_regex; |
45 | | |
46 | | static int DetectFragOffsetMatch(DetectEngineThreadCtx *, |
47 | | Packet *, const Signature *, const SigMatchCtx *); |
48 | | static int DetectFragOffsetSetup(DetectEngineCtx *, Signature *, const char *); |
49 | | #ifdef UNITTESTS |
50 | | void DetectFragOffsetRegisterTests(void); |
51 | | #endif |
52 | | void DetectFragOffsetFree(DetectEngineCtx *, void *); |
53 | | |
54 | | static int PrefilterSetupFragOffset(DetectEngineCtx *de_ctx, SigGroupHead *sgh); |
55 | | static bool PrefilterFragOffsetIsPrefilterable(const Signature *s); |
56 | | |
57 | | /** |
58 | | * \brief Registration function for fragoffset |
59 | | */ |
60 | | void DetectFragOffsetRegister (void) |
61 | 73 | { |
62 | 73 | sigmatch_table[DETECT_FRAGOFFSET].name = "fragoffset"; |
63 | 73 | sigmatch_table[DETECT_FRAGOFFSET].desc = "match on specific decimal values of the IP fragment offset field"; |
64 | 73 | sigmatch_table[DETECT_FRAGOFFSET].url = "/rules/header-keywords.html#fragoffset"; |
65 | 73 | sigmatch_table[DETECT_FRAGOFFSET].Match = DetectFragOffsetMatch; |
66 | 73 | sigmatch_table[DETECT_FRAGOFFSET].Setup = DetectFragOffsetSetup; |
67 | 73 | sigmatch_table[DETECT_FRAGOFFSET].Free = DetectFragOffsetFree; |
68 | | #ifdef UNITTESTS |
69 | | sigmatch_table[DETECT_FRAGOFFSET].RegisterTests = DetectFragOffsetRegisterTests; |
70 | | #endif |
71 | 73 | sigmatch_table[DETECT_FRAGOFFSET].SupportsPrefilter = PrefilterFragOffsetIsPrefilterable; |
72 | 73 | sigmatch_table[DETECT_FRAGOFFSET].SetupPrefilter = PrefilterSetupFragOffset; |
73 | | |
74 | 73 | DetectSetupParseRegexes(PARSE_REGEX, &parse_regex); |
75 | 73 | } |
76 | | |
77 | | static inline int FragOffsetMatch(const uint16_t poffset, const uint8_t mode, |
78 | | const uint16_t doffset) |
79 | 20.0k | { |
80 | 20.0k | switch (mode) { |
81 | 312 | case FRAG_LESS: |
82 | 312 | if (poffset < doffset) |
83 | 311 | return 1; |
84 | 1 | break; |
85 | 10.2k | case FRAG_MORE: |
86 | 10.2k | if (poffset > doffset) |
87 | 13 | return 1; |
88 | 10.2k | break; |
89 | 10.2k | default: |
90 | 9.39k | if (poffset == doffset) |
91 | 4.50k | return 1; |
92 | 20.0k | } |
93 | 15.1k | return 0; |
94 | 20.0k | } |
95 | | |
96 | | /** |
97 | | * \brief This function is used to match fragoffset rule option set on a packet |
98 | | * |
99 | | * \param t pointer to thread vars |
100 | | * \param det_ctx pointer to the pattern matcher thread |
101 | | * \param p pointer to the current packet |
102 | | * \param m pointer to the sigmatch that we will cast into DetectFragOffsetData |
103 | | * |
104 | | * \retval 0 no match or frag is not set |
105 | | * \retval 1 match |
106 | | * |
107 | | */ |
108 | | static int DetectFragOffsetMatch (DetectEngineThreadCtx *det_ctx, |
109 | | Packet *p, const Signature *s, const SigMatchCtx *ctx) |
110 | 11.2k | { |
111 | 11.2k | uint16_t frag = 0; |
112 | 11.2k | const DetectFragOffsetData *fragoff = (const DetectFragOffsetData *)ctx; |
113 | | |
114 | 11.2k | if (PKT_IS_PSEUDOPKT(p)) |
115 | 382 | return 0; |
116 | | |
117 | 10.8k | if (PKT_IS_IPV4(p)) { |
118 | 9.48k | frag = IPV4_GET_IPOFFSET(p); |
119 | 9.48k | } else if (PKT_IS_IPV6(p)) { |
120 | 1.39k | if (IPV6_EXTHDR_ISSET_FH(p)) { |
121 | 480 | frag = IPV6_EXTHDR_GET_FH_OFFSET(p); |
122 | 914 | } else { |
123 | 914 | return 0; |
124 | 914 | } |
125 | 1.39k | } else { |
126 | 0 | SCLogDebug("No IPv4 or IPv6 packet"); |
127 | 0 | return 0; |
128 | 0 | } |
129 | | |
130 | 9.96k | return FragOffsetMatch(frag, fragoff->mode, fragoff->frag_off); |
131 | 10.8k | } |
132 | | |
133 | | /** |
134 | | * \brief This function is used to parse fragoffset option passed via fragoffset: keyword |
135 | | * |
136 | | * \param de_ctx Pointer to the detection engine context |
137 | | * \param fragoffsetstr Pointer to the user provided fragoffset options |
138 | | * |
139 | | * \retval fragoff pointer to DetectFragOffsetData on success |
140 | | * \retval NULL on failure |
141 | | */ |
142 | | static DetectFragOffsetData *DetectFragOffsetParse (DetectEngineCtx *de_ctx, const char *fragoffsetstr) |
143 | 5.49k | { |
144 | 5.49k | DetectFragOffsetData *fragoff = NULL; |
145 | 5.49k | char *substr[3] = {NULL, NULL, NULL}; |
146 | 5.49k | int res = 0; |
147 | 5.49k | size_t pcre2_len; |
148 | 5.49k | int i; |
149 | 5.49k | const char *str_ptr; |
150 | 5.49k | char *mode = NULL; |
151 | 5.49k | pcre2_match_data *match = NULL; |
152 | | |
153 | 5.49k | int ret = DetectParsePcreExec(&parse_regex, &match, fragoffsetstr, 0, 0); |
154 | 5.49k | if (ret < 1 || ret > 4) { |
155 | 871 | SCLogError("Parse error %s", fragoffsetstr); |
156 | 871 | goto error; |
157 | 871 | } |
158 | | |
159 | 13.8k | for (i = 1; i < ret; i++) { |
160 | 9.24k | res = SC_Pcre2SubstringGet(match, i, (PCRE2_UCHAR8 **)&str_ptr, &pcre2_len); |
161 | 9.24k | if (res < 0) { |
162 | 0 | SCLogError("pcre2_substring_get_bynumber failed"); |
163 | 0 | goto error; |
164 | 0 | } |
165 | 9.24k | substr[i-1] = (char *)str_ptr; |
166 | 9.24k | } |
167 | | |
168 | 4.62k | fragoff = SCMalloc(sizeof(DetectFragOffsetData)); |
169 | 4.62k | if (unlikely(fragoff == NULL)) |
170 | 0 | goto error; |
171 | | |
172 | 4.62k | fragoff->frag_off = 0; |
173 | 4.62k | fragoff->mode = 0; |
174 | | |
175 | 4.62k | mode = substr[0]; |
176 | | |
177 | 4.62k | if(mode != NULL) { |
178 | | |
179 | 6.78k | while(*mode != '\0') { |
180 | 3.39k | switch(*mode) { |
181 | 2.79k | case '>': |
182 | 2.79k | fragoff->mode = FRAG_MORE; |
183 | 2.79k | break; |
184 | 601 | case '<': |
185 | 601 | fragoff->mode = FRAG_LESS; |
186 | 601 | break; |
187 | 3.39k | } |
188 | 3.39k | mode++; |
189 | 3.39k | } |
190 | 3.39k | } |
191 | | |
192 | 4.62k | if (StringParseUint16(&fragoff->frag_off, 10, 0, substr[1]) < 0) { |
193 | 44 | SCLogError("specified frag offset %s is not " |
194 | 44 | "valid", |
195 | 44 | substr[1]); |
196 | 44 | goto error; |
197 | 44 | } |
198 | | |
199 | 18.3k | for (i = 0; i < 3; i++) { |
200 | 13.7k | if (substr[i] != NULL) |
201 | 13.7k | pcre2_substring_free((PCRE2_UCHAR8 *)substr[i]); |
202 | 13.7k | } |
203 | | |
204 | 4.58k | pcre2_match_data_free(match); |
205 | 4.58k | return fragoff; |
206 | | |
207 | 915 | error: |
208 | 915 | if (match) { |
209 | 915 | pcre2_match_data_free(match); |
210 | 915 | } |
211 | 3.66k | for (i = 0; i < 3; i++) { |
212 | 2.74k | if (substr[i] != NULL) |
213 | 2.74k | pcre2_substring_free((PCRE2_UCHAR8 *)substr[i]); |
214 | 2.74k | } |
215 | 915 | if (fragoff != NULL) DetectFragOffsetFree(de_ctx, fragoff); |
216 | 915 | return NULL; |
217 | | |
218 | 4.62k | } |
219 | | |
220 | | /** |
221 | | * \brief this function is used to add the parsed fragoffset data into the current signature |
222 | | * |
223 | | * \param de_ctx pointer to the Detection Engine Context |
224 | | * \param s pointer to the Current Signature |
225 | | * \param fragoffsetstr pointer to the user provided fragoffset option |
226 | | * |
227 | | * \retval 0 on Success |
228 | | * \retval -1 on Failure |
229 | | */ |
230 | | static int DetectFragOffsetSetup (DetectEngineCtx *de_ctx, Signature *s, const char *fragoffsetstr) |
231 | 5.49k | { |
232 | 5.49k | DetectFragOffsetData *fragoff = NULL; |
233 | 5.49k | SigMatch *sm = NULL; |
234 | | |
235 | 5.49k | fragoff = DetectFragOffsetParse(de_ctx, fragoffsetstr); |
236 | 5.49k | if (fragoff == NULL) goto error; |
237 | | |
238 | 4.58k | sm = SigMatchAlloc(); |
239 | 4.58k | if (sm == NULL) goto error; |
240 | | |
241 | 4.58k | sm->type = DETECT_FRAGOFFSET; |
242 | 4.58k | sm->ctx = (SigMatchCtx *)fragoff; |
243 | | |
244 | 4.58k | SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_MATCH); |
245 | 4.58k | s->flags |= SIG_FLAG_REQUIRE_PACKET; |
246 | | |
247 | 4.58k | return 0; |
248 | | |
249 | 915 | error: |
250 | 915 | if (fragoff != NULL) DetectFragOffsetFree(de_ctx, fragoff); |
251 | 915 | if (sm != NULL) SCFree(sm); |
252 | 915 | return -1; |
253 | | |
254 | 4.58k | } |
255 | | |
256 | | /** |
257 | | * \brief this function will free memory associated with DetectFragOffsetData |
258 | | * |
259 | | * \param ptr pointer to DetectFragOffsetData |
260 | | */ |
261 | | void DetectFragOffsetFree (DetectEngineCtx *de_ctx, void *ptr) |
262 | 7.89k | { |
263 | 7.89k | DetectFragOffsetData *fragoff = (DetectFragOffsetData *)ptr; |
264 | 7.89k | SCFree(fragoff); |
265 | 7.89k | } |
266 | | |
267 | | static void |
268 | | PrefilterPacketFragOffsetMatch(DetectEngineThreadCtx *det_ctx, Packet *p, const void *pectx) |
269 | 11.2k | { |
270 | 11.2k | if (PKT_IS_PSEUDOPKT(p)) |
271 | 1.04k | return; |
272 | | |
273 | 10.1k | uint16_t frag; |
274 | | |
275 | 10.1k | if (PKT_IS_IPV4(p)) { |
276 | 10.0k | frag = IPV4_GET_IPOFFSET(p); |
277 | 10.0k | } else if (PKT_IS_IPV6(p)) { |
278 | 119 | if (IPV6_EXTHDR_ISSET_FH(p)) { |
279 | 0 | frag = IPV6_EXTHDR_GET_FH_OFFSET(p); |
280 | 119 | } else { |
281 | 119 | return; |
282 | 119 | } |
283 | 119 | } else { |
284 | 0 | SCLogDebug("No IPv4 or IPv6 packet"); |
285 | 0 | return; |
286 | 0 | } |
287 | | |
288 | 10.0k | const PrefilterPacketHeaderCtx *ctx = pectx; |
289 | 10.0k | if (FragOffsetMatch(frag, ctx->v1.u8[0], ctx->v1.u16[1])) |
290 | 312 | { |
291 | 312 | PrefilterAddSids(&det_ctx->pmq, ctx->sigs_array, ctx->sigs_cnt); |
292 | 312 | } |
293 | 10.0k | } |
294 | | |
295 | | static void |
296 | | PrefilterPacketFragOffsetSet(PrefilterPacketHeaderValue *v, void *smctx) |
297 | 24.9k | { |
298 | 24.9k | const DetectFragOffsetData *fb = smctx; |
299 | 24.9k | v->u8[0] = fb->mode; |
300 | 24.9k | v->u16[1] = fb->frag_off; |
301 | 24.9k | } |
302 | | |
303 | | static bool |
304 | | PrefilterPacketFragOffsetCompare(PrefilterPacketHeaderValue v, void *smctx) |
305 | 23.6k | { |
306 | 23.6k | const DetectFragOffsetData *fb = smctx; |
307 | 23.6k | if (v.u8[0] == fb->mode && |
308 | 23.4k | v.u16[1] == fb->frag_off) |
309 | 23.1k | { |
310 | 23.1k | return true; |
311 | 23.1k | } |
312 | 456 | return false; |
313 | 23.6k | } |
314 | | |
315 | | static int PrefilterSetupFragOffset(DetectEngineCtx *de_ctx, SigGroupHead *sgh) |
316 | 13.2k | { |
317 | 13.2k | return PrefilterSetupPacketHeader(de_ctx, sgh, DETECT_FRAGOFFSET, |
318 | 13.2k | PrefilterPacketFragOffsetSet, |
319 | 13.2k | PrefilterPacketFragOffsetCompare, |
320 | 13.2k | PrefilterPacketFragOffsetMatch); |
321 | 13.2k | } |
322 | | |
323 | | static bool PrefilterFragOffsetIsPrefilterable(const Signature *s) |
324 | 0 | { |
325 | 0 | const SigMatch *sm; |
326 | 0 | for (sm = s->init_data->smlists[DETECT_SM_LIST_MATCH] ; sm != NULL; sm = sm->next) { |
327 | 0 | switch (sm->type) { |
328 | 0 | case DETECT_FRAGOFFSET: |
329 | 0 | return true; |
330 | 0 | } |
331 | 0 | } |
332 | 0 | return false; |
333 | 0 | } |
334 | | |
335 | | #ifdef UNITTESTS |
336 | | #include "util-unittest-helper.h" |
337 | | #include "detect-engine.h" |
338 | | #include "detect-engine-alert.h" |
339 | | |
340 | | /** |
341 | | * \test DetectFragOffsetParseTest01 is a test for setting a valid fragoffset value |
342 | | */ |
343 | | static int DetectFragOffsetParseTest01 (void) |
344 | | { |
345 | | DetectFragOffsetData *fragoff = DetectFragOffsetParse(NULL, "300"); |
346 | | |
347 | | FAIL_IF_NULL(fragoff); |
348 | | FAIL_IF_NOT(fragoff->frag_off == 300); |
349 | | |
350 | | DetectFragOffsetFree(NULL, fragoff); |
351 | | |
352 | | PASS; |
353 | | } |
354 | | |
355 | | /** |
356 | | * \test DetectFragOffsetParseTest02 is a test for setting a valid fragoffset value |
357 | | * with spaces all around |
358 | | */ |
359 | | static int DetectFragOffsetParseTest02 (void) |
360 | | { |
361 | | DetectFragOffsetData *fragoff = DetectFragOffsetParse(NULL, ">300"); |
362 | | |
363 | | FAIL_IF_NULL(fragoff); |
364 | | FAIL_IF_NOT(fragoff->frag_off == 300); |
365 | | FAIL_IF_NOT(fragoff->mode == FRAG_MORE); |
366 | | |
367 | | DetectFragOffsetFree(NULL, fragoff); |
368 | | |
369 | | PASS; |
370 | | } |
371 | | |
372 | | /** |
373 | | * \test DetectFragOffsetParseTest03 is a test for setting an invalid fragoffset value |
374 | | */ |
375 | | static int DetectFragOffsetParseTest03 (void) |
376 | | { |
377 | | DetectFragOffsetData *fragoff = DetectFragOffsetParse(NULL, "badc"); |
378 | | |
379 | | FAIL_IF_NOT_NULL(fragoff); |
380 | | |
381 | | PASS; |
382 | | } |
383 | | |
384 | | /** |
385 | | * \test DetectFragOffsetMatchTest01 is a test for checking the working of |
386 | | * fragoffset keyword by creating 2 rules and matching a crafted packet |
387 | | * against them. Only the first one shall trigger. |
388 | | */ |
389 | | static int DetectFragOffsetMatchTest01 (void) |
390 | | { |
391 | | Packet *p = PacketGetFromAlloc(); |
392 | | |
393 | | FAIL_IF_NULL(p); |
394 | | Signature *s = NULL; |
395 | | DecodeThreadVars dtv; |
396 | | ThreadVars th_v; |
397 | | DetectEngineThreadCtx *det_ctx = NULL; |
398 | | IPV4Hdr ip4h; |
399 | | |
400 | | memset(&ip4h, 0, sizeof(IPV4Hdr)); |
401 | | memset(&dtv, 0, sizeof(DecodeThreadVars)); |
402 | | memset(&th_v, 0, sizeof(ThreadVars)); |
403 | | |
404 | | FlowInitConfig(FLOW_QUIET); |
405 | | |
406 | | p->src.family = AF_INET; |
407 | | p->dst.family = AF_INET; |
408 | | p->src.addr_data32[0] = 0x01020304; |
409 | | p->dst.addr_data32[0] = 0x04030201; |
410 | | |
411 | | ip4h.s_ip_src.s_addr = p->src.addr_data32[0]; |
412 | | ip4h.s_ip_dst.s_addr = p->dst.addr_data32[0]; |
413 | | ip4h.ip_off = 0x2222; |
414 | | p->ip4h = &ip4h; |
415 | | |
416 | | DetectEngineCtx *de_ctx = DetectEngineCtxInit(); |
417 | | FAIL_IF_NULL(de_ctx); |
418 | | |
419 | | de_ctx->flags |= DE_QUIET; |
420 | | |
421 | | s = DetectEngineAppendSig(de_ctx, "alert ip any any -> any any (fragoffset:546; sid:1;)"); |
422 | | FAIL_IF_NULL(s); |
423 | | |
424 | | s = DetectEngineAppendSig(de_ctx, "alert ip any any -> any any (fragoffset:5000; sid:2;)"); |
425 | | FAIL_IF_NULL(s); |
426 | | |
427 | | SigGroupBuild(de_ctx); |
428 | | DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); |
429 | | |
430 | | SigMatchSignatures(&th_v, de_ctx, det_ctx, p); |
431 | | |
432 | | FAIL_IF(PacketAlertCheck(p, 1) == 0); |
433 | | FAIL_IF(PacketAlertCheck(p, 2)); |
434 | | |
435 | | DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); |
436 | | DetectEngineCtxFree(de_ctx); |
437 | | |
438 | | FlowShutdown(); |
439 | | |
440 | | SCFree(p); |
441 | | PASS; |
442 | | } |
443 | | |
444 | | void DetectFragOffsetRegisterTests (void) |
445 | | { |
446 | | UtRegisterTest("DetectFragOffsetParseTest01", DetectFragOffsetParseTest01); |
447 | | UtRegisterTest("DetectFragOffsetParseTest02", DetectFragOffsetParseTest02); |
448 | | UtRegisterTest("DetectFragOffsetParseTest03", DetectFragOffsetParseTest03); |
449 | | UtRegisterTest("DetectFragOffsetMatchTest01", DetectFragOffsetMatchTest01); |
450 | | } |
451 | | #endif /* UNITTESTS */ |