/src/suricata8/src/detect-icmp-seq.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 Breno Silva <breno.silva@gmail.com> |
22 | | * |
23 | | * Implements the icmp_seq keyword |
24 | | */ |
25 | | |
26 | | #include "suricata-common.h" |
27 | | #include "decode.h" |
28 | | |
29 | | #include "detect.h" |
30 | | #include "detect-parse.h" |
31 | | #include "detect-engine-prefilter-common.h" |
32 | | #include "detect-engine-build.h" |
33 | | |
34 | | #include "detect-icmp-seq.h" |
35 | | |
36 | | #include "util-byte.h" |
37 | | #include "util-unittest.h" |
38 | | #include "util-unittest-helper.h" |
39 | | #include "util-debug.h" |
40 | | |
41 | 79 | #define PARSE_REGEX "^\\s*(\"\\s*)?([0-9]+)(\\s*\")?\\s*$" |
42 | | |
43 | | static DetectParseRegex parse_regex; |
44 | | |
45 | | static int DetectIcmpSeqMatch(DetectEngineThreadCtx *, Packet *, |
46 | | const Signature *, const SigMatchCtx *); |
47 | | static int DetectIcmpSeqSetup(DetectEngineCtx *, Signature *, const char *); |
48 | | #ifdef UNITTESTS |
49 | | static void DetectIcmpSeqRegisterTests(void); |
50 | | #endif |
51 | | void DetectIcmpSeqFree(DetectEngineCtx *, void *); |
52 | | static int PrefilterSetupIcmpSeq(DetectEngineCtx *de_ctx, SigGroupHead *sgh); |
53 | | static bool PrefilterIcmpSeqIsPrefilterable(const Signature *s); |
54 | | |
55 | | /** |
56 | | * \brief Registration function for icmp_seq |
57 | | */ |
58 | | void DetectIcmpSeqRegister (void) |
59 | 79 | { |
60 | 79 | sigmatch_table[DETECT_ICMP_SEQ].name = "icmp_seq"; |
61 | 79 | sigmatch_table[DETECT_ICMP_SEQ].desc = "check for a ICMP sequence number"; |
62 | 79 | sigmatch_table[DETECT_ICMP_SEQ].url = "/rules/header-keywords.html#icmp-seq"; |
63 | 79 | sigmatch_table[DETECT_ICMP_SEQ].Match = DetectIcmpSeqMatch; |
64 | 79 | sigmatch_table[DETECT_ICMP_SEQ].Setup = DetectIcmpSeqSetup; |
65 | 79 | sigmatch_table[DETECT_ICMP_SEQ].Free = DetectIcmpSeqFree; |
66 | | #ifdef UNITTESTS |
67 | | sigmatch_table[DETECT_ICMP_SEQ].RegisterTests = DetectIcmpSeqRegisterTests; |
68 | | #endif |
69 | 79 | sigmatch_table[DETECT_ICMP_SEQ].SupportsPrefilter = PrefilterIcmpSeqIsPrefilterable; |
70 | 79 | sigmatch_table[DETECT_ICMP_SEQ].SetupPrefilter = PrefilterSetupIcmpSeq; |
71 | | |
72 | 79 | DetectSetupParseRegexes(PARSE_REGEX, &parse_regex); |
73 | 79 | } |
74 | | |
75 | | static inline bool GetIcmpSeq(Packet *p, uint16_t *seq) |
76 | 43.9k | { |
77 | 43.9k | uint16_t seqn; |
78 | | |
79 | 43.9k | if (PacketIsICMPv4(p)) { |
80 | 7.86k | switch (p->icmp_s.type) { |
81 | 1.88k | case ICMP_ECHOREPLY: |
82 | 5.55k | case ICMP_ECHO: |
83 | 5.65k | case ICMP_TIMESTAMP: |
84 | 5.65k | case ICMP_TIMESTAMPREPLY: |
85 | 5.67k | case ICMP_INFO_REQUEST: |
86 | 5.67k | case ICMP_INFO_REPLY: |
87 | 5.67k | case ICMP_ADDRESS: |
88 | 5.68k | case ICMP_ADDRESSREPLY: |
89 | 5.68k | SCLogDebug("ICMPV4_GET_SEQ(p) %"PRIu16" (network byte order), " |
90 | 5.68k | "%"PRIu16" (host byte order)", ICMPV4_GET_SEQ(p), |
91 | 5.68k | SCNtohs(ICMPV4_GET_SEQ(p))); |
92 | | |
93 | 5.68k | seqn = ICMPV4_GET_SEQ(p); |
94 | 5.68k | break; |
95 | 2.18k | default: |
96 | 2.18k | SCLogDebug("Packet has no seq field"); |
97 | 2.18k | return false; |
98 | 7.86k | } |
99 | 36.0k | } else if (PacketIsICMPv6(p)) { |
100 | 1.56k | switch (ICMPV6_GET_TYPE(PacketGetICMPv6(p))) { |
101 | 38 | case ICMP6_ECHO_REQUEST: |
102 | 282 | case ICMP6_ECHO_REPLY: |
103 | 282 | SCLogDebug("ICMPV6_GET_SEQ(p) %"PRIu16" (network byte order), " |
104 | 282 | "%"PRIu16" (host byte order)", ICMPV6_GET_SEQ(p), |
105 | 282 | SCNtohs(ICMPV6_GET_SEQ(p))); |
106 | | |
107 | 282 | seqn = ICMPV6_GET_SEQ(p); |
108 | 282 | break; |
109 | 1.28k | default: |
110 | 1.28k | SCLogDebug("Packet has no seq field"); |
111 | 1.28k | return false; |
112 | 1.56k | } |
113 | 34.4k | } else { |
114 | 34.4k | SCLogDebug("Packet not ICMPV4 nor ICMPV6"); |
115 | 34.4k | return false; |
116 | 34.4k | } |
117 | | |
118 | 5.96k | *seq = seqn; |
119 | 5.96k | return true; |
120 | 43.9k | } |
121 | | |
122 | | /** |
123 | | * \brief This function is used to match icmp_seq rule option set on a packet |
124 | | * |
125 | | * \param t pointer to thread vars |
126 | | * \param det_ctx pointer to the pattern matcher thread |
127 | | * \param p pointer to the current packet |
128 | | * \param m pointer to the sigmatch that we will cast into DetectIcmpSeqData |
129 | | * |
130 | | * \retval 0 no match |
131 | | * \retval 1 match |
132 | | */ |
133 | | static int DetectIcmpSeqMatch (DetectEngineThreadCtx *det_ctx, Packet *p, |
134 | | const Signature *s, const SigMatchCtx *ctx) |
135 | 141 | { |
136 | 141 | DEBUG_VALIDATE_BUG_ON(PKT_IS_PSEUDOPKT(p)); |
137 | 141 | uint16_t seqn; |
138 | | |
139 | 141 | if (!GetIcmpSeq(p, &seqn)) |
140 | 141 | return 0; |
141 | | |
142 | 0 | const DetectIcmpSeqData *iseq = (const DetectIcmpSeqData *)ctx; |
143 | 0 | if (seqn == iseq->seq) |
144 | 0 | return 1; |
145 | | |
146 | 0 | return 0; |
147 | 0 | } |
148 | | |
149 | | /** |
150 | | * \brief This function is used to parse icmp_seq option passed via icmp_seq: keyword |
151 | | * |
152 | | * \param de_ctx Pointer to the detection engine context |
153 | | * \param icmpseqstr Pointer to the user provided icmp_seq options |
154 | | * |
155 | | * \retval iseq pointer to DetectIcmpSeqData on success |
156 | | * \retval NULL on failure |
157 | | */ |
158 | | static DetectIcmpSeqData *DetectIcmpSeqParse (DetectEngineCtx *de_ctx, const char *icmpseqstr) |
159 | 2.03k | { |
160 | 2.03k | DetectIcmpSeqData *iseq = NULL; |
161 | 2.03k | char *substr[3] = {NULL, NULL, NULL}; |
162 | 2.03k | int res = 0; |
163 | 2.03k | size_t pcre2_len; |
164 | 2.03k | int i; |
165 | 2.03k | const char *str_ptr; |
166 | | |
167 | 2.03k | pcre2_match_data *match = NULL; |
168 | 2.03k | int ret = DetectParsePcreExec(&parse_regex, &match, icmpseqstr, 0, 0); |
169 | 2.03k | if (ret < 1 || ret > 4) { |
170 | 120 | SCLogError("Parse error %s", icmpseqstr); |
171 | 120 | goto error; |
172 | 120 | } |
173 | | |
174 | 5.75k | for (i = 1; i < ret; i++) { |
175 | 3.83k | res = SC_Pcre2SubstringGet(match, i, (PCRE2_UCHAR8 **)&str_ptr, &pcre2_len); |
176 | 3.83k | if (res < 0) { |
177 | 0 | SCLogError("pcre2_substring_get_bynumber failed"); |
178 | 0 | goto error; |
179 | 0 | } |
180 | 3.83k | substr[i-1] = (char *)str_ptr; |
181 | 3.83k | } |
182 | | |
183 | 1.91k | iseq = SCMalloc(sizeof(DetectIcmpSeqData)); |
184 | 1.91k | if (unlikely(iseq == NULL)) |
185 | 0 | goto error; |
186 | | |
187 | 1.91k | iseq->seq = 0; |
188 | | |
189 | 1.91k | if (substr[0] != NULL && strlen(substr[0]) != 0) { |
190 | 0 | if (substr[2] == NULL) { |
191 | 0 | SCLogError("Missing quote in input"); |
192 | 0 | goto error; |
193 | 0 | } |
194 | 1.91k | } else { |
195 | 1.91k | if (substr[2] != NULL) { |
196 | 0 | SCLogError("Missing quote in input"); |
197 | 0 | goto error; |
198 | 0 | } |
199 | 1.91k | } |
200 | | |
201 | 1.91k | uint16_t seq = 0; |
202 | 1.91k | if (StringParseUint16(&seq, 10, 0, substr[1]) < 0) { |
203 | 104 | SCLogError("specified icmp seq %s is not " |
204 | 104 | "valid", |
205 | 104 | substr[1]); |
206 | 104 | goto error; |
207 | 104 | } |
208 | 1.81k | iseq->seq = htons(seq); |
209 | | |
210 | 7.26k | for (i = 0; i < 3; i++) { |
211 | 5.44k | if (substr[i] != NULL) |
212 | 5.44k | pcre2_substring_free((PCRE2_UCHAR8 *)substr[i]); |
213 | 5.44k | } |
214 | | |
215 | 1.81k | pcre2_match_data_free(match); |
216 | 1.81k | return iseq; |
217 | | |
218 | 224 | error: |
219 | 224 | if (match) { |
220 | 224 | pcre2_match_data_free(match); |
221 | 224 | } |
222 | 896 | for (i = 0; i < 3; i++) { |
223 | 672 | if (substr[i] != NULL) |
224 | 672 | pcre2_substring_free((PCRE2_UCHAR8 *)substr[i]); |
225 | 672 | } |
226 | 224 | if (iseq != NULL) DetectIcmpSeqFree(de_ctx, iseq); |
227 | 224 | return NULL; |
228 | | |
229 | 1.91k | } |
230 | | |
231 | | /** |
232 | | * \brief this function is used to add the parsed icmp_seq data into the current signature |
233 | | * |
234 | | * \param de_ctx pointer to the Detection Engine Context |
235 | | * \param s pointer to the Current Signature |
236 | | * \param icmpseqstr pointer to the user provided icmp_seq option |
237 | | * |
238 | | * \retval 0 on Success |
239 | | * \retval -1 on Failure |
240 | | */ |
241 | | static int DetectIcmpSeqSetup (DetectEngineCtx *de_ctx, Signature *s, const char *icmpseqstr) |
242 | 2.03k | { |
243 | 2.03k | DetectIcmpSeqData *iseq = NULL; |
244 | | |
245 | 2.03k | iseq = DetectIcmpSeqParse(de_ctx, icmpseqstr); |
246 | 2.03k | if (iseq == NULL) goto error; |
247 | | |
248 | 1.81k | if (SCSigMatchAppendSMToList( |
249 | 1.81k | de_ctx, s, DETECT_ICMP_SEQ, (SigMatchCtx *)iseq, DETECT_SM_LIST_MATCH) == NULL) { |
250 | 0 | goto error; |
251 | 0 | } |
252 | 1.81k | s->flags |= SIG_FLAG_REQUIRE_PACKET; |
253 | | |
254 | 1.81k | return 0; |
255 | | |
256 | 224 | error: |
257 | 224 | if (iseq != NULL) |
258 | 0 | DetectIcmpSeqFree(de_ctx, iseq); |
259 | 224 | return -1; |
260 | | |
261 | 1.81k | } |
262 | | |
263 | | /** |
264 | | * \brief this function will free memory associated with DetectIcmpSeqData |
265 | | * |
266 | | * \param ptr pointer to DetectIcmpSeqData |
267 | | */ |
268 | | void DetectIcmpSeqFree (DetectEngineCtx *de_ctx, void *ptr) |
269 | 20.4k | { |
270 | 20.4k | DetectIcmpSeqData *iseq = (DetectIcmpSeqData *)ptr; |
271 | 20.4k | SCFree(iseq); |
272 | 20.4k | } |
273 | | |
274 | | /* prefilter code */ |
275 | | |
276 | | static void |
277 | | PrefilterPacketIcmpSeqMatch(DetectEngineThreadCtx *det_ctx, Packet *p, const void *pectx) |
278 | 22 | { |
279 | 22 | DEBUG_VALIDATE_BUG_ON(PKT_IS_PSEUDOPKT(p)); |
280 | | |
281 | 22 | const PrefilterPacketHeaderCtx *ctx = pectx; |
282 | 22 | uint16_t seqn; |
283 | | |
284 | 22 | if (!GetIcmpSeq(p, &seqn)) |
285 | 22 | return; |
286 | | |
287 | 0 | if (seqn == ctx->v1.u16[0]) |
288 | 0 | { |
289 | 0 | SCLogDebug("packet matches ICMP SEQ %u", ctx->v1.u16[0]); |
290 | 0 | PrefilterAddSids(&det_ctx->pmq, ctx->sigs_array, ctx->sigs_cnt); |
291 | 0 | } |
292 | 0 | } |
293 | | |
294 | | static void |
295 | | PrefilterPacketIcmpSeqSet(PrefilterPacketHeaderValue *v, void *smctx) |
296 | 1.01k | { |
297 | 1.01k | const DetectIcmpSeqData *a = smctx; |
298 | 1.01k | v->u16[0] = a->seq; |
299 | 1.01k | } |
300 | | |
301 | | static bool |
302 | | PrefilterPacketIcmpSeqCompare(PrefilterPacketHeaderValue v, void *smctx) |
303 | 721 | { |
304 | 721 | const DetectIcmpSeqData *a = smctx; |
305 | 721 | if (v.u16[0] == a->seq) |
306 | 677 | return true; |
307 | 44 | return false; |
308 | 721 | } |
309 | | |
310 | | static int PrefilterSetupIcmpSeq(DetectEngineCtx *de_ctx, SigGroupHead *sgh) |
311 | 5.17k | { |
312 | 5.17k | return PrefilterSetupPacketHeader(de_ctx, sgh, DETECT_ICMP_SEQ, SIG_MASK_REQUIRE_REAL_PKT, |
313 | 5.17k | PrefilterPacketIcmpSeqSet, PrefilterPacketIcmpSeqCompare, PrefilterPacketIcmpSeqMatch); |
314 | 5.17k | } |
315 | | |
316 | | static bool PrefilterIcmpSeqIsPrefilterable(const Signature *s) |
317 | 0 | { |
318 | 0 | const SigMatch *sm; |
319 | 0 | for (sm = s->init_data->smlists[DETECT_SM_LIST_MATCH] ; sm != NULL; sm = sm->next) { |
320 | 0 | switch (sm->type) { |
321 | 0 | case DETECT_ICMP_SEQ: |
322 | 0 | return true; |
323 | 0 | } |
324 | 0 | } |
325 | 0 | return false; |
326 | 0 | } |
327 | | |
328 | | #ifdef UNITTESTS |
329 | | #include "detect-engine.h" |
330 | | #include "detect-engine-mpm.h" |
331 | | #include "detect-engine-alert.h" |
332 | | |
333 | | /** |
334 | | * \test DetectIcmpSeqParseTest01 is a test for setting a valid icmp_seq value |
335 | | */ |
336 | | static int DetectIcmpSeqParseTest01 (void) |
337 | | { |
338 | | DetectIcmpSeqData *iseq = NULL; |
339 | | iseq = DetectIcmpSeqParse(NULL, "300"); |
340 | | FAIL_IF_NULL(iseq); |
341 | | FAIL_IF_NOT(htons(iseq->seq) == 300); |
342 | | DetectIcmpSeqFree(NULL, iseq); |
343 | | PASS; |
344 | | } |
345 | | |
346 | | /** |
347 | | * \test DetectIcmpSeqParseTest02 is a test for setting a valid icmp_seq value |
348 | | * with spaces all around |
349 | | */ |
350 | | static int DetectIcmpSeqParseTest02 (void) |
351 | | { |
352 | | DetectIcmpSeqData *iseq = NULL; |
353 | | iseq = DetectIcmpSeqParse(NULL, " 300 "); |
354 | | FAIL_IF_NULL(iseq); |
355 | | FAIL_IF_NOT(htons(iseq->seq) == 300); |
356 | | DetectIcmpSeqFree(NULL, iseq); |
357 | | PASS; |
358 | | } |
359 | | |
360 | | /** |
361 | | * \test DetectIcmpSeqParseTest03 is a test for setting an invalid icmp_seq value |
362 | | */ |
363 | | static int DetectIcmpSeqParseTest03 (void) |
364 | | { |
365 | | DetectIcmpSeqData *iseq = DetectIcmpSeqParse(NULL, "badc"); |
366 | | FAIL_IF_NOT_NULL(iseq); |
367 | | PASS; |
368 | | } |
369 | | |
370 | | static void DetectIcmpSeqRegisterTests (void) |
371 | | { |
372 | | UtRegisterTest("DetectIcmpSeqParseTest01", DetectIcmpSeqParseTest01); |
373 | | UtRegisterTest("DetectIcmpSeqParseTest02", DetectIcmpSeqParseTest02); |
374 | | UtRegisterTest("DetectIcmpSeqParseTest03", DetectIcmpSeqParseTest03); |
375 | | } |
376 | | #endif /* UNITTESTS */ |