/src/suricata7/src/detect-id.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 Pablo Rincon Crespo <pablo.rincon.crespo@gmail.com> |
22 | | * |
23 | | * Implements the id keyword |
24 | | */ |
25 | | |
26 | | #include "suricata-common.h" |
27 | | #include "decode.h" |
28 | | #include "detect.h" |
29 | | |
30 | | #include "detect-parse.h" |
31 | | #include "detect-engine.h" |
32 | | #include "detect-engine-mpm.h" |
33 | | #include "detect-engine-prefilter-common.h" |
34 | | |
35 | | #include "detect-id.h" |
36 | | #include "flow.h" |
37 | | #include "flow-var.h" |
38 | | |
39 | | #include "util-debug.h" |
40 | | #include "util-byte.h" |
41 | | #include "util-unittest.h" |
42 | | #include "util-unittest-helper.h" |
43 | | |
44 | | /** |
45 | | * \brief Regex for parsing "id" option, matching number or "number" |
46 | | */ |
47 | 73 | #define PARSE_REGEX "^\\s*([0-9]{1,5}|\"[0-9]{1,5}\")\\s*$" |
48 | | |
49 | | static DetectParseRegex parse_regex; |
50 | | |
51 | | static int DetectIdMatch (DetectEngineThreadCtx *, Packet *, |
52 | | const Signature *, const SigMatchCtx *); |
53 | | static int DetectIdSetup (DetectEngineCtx *, Signature *, const char *); |
54 | | #ifdef UNITTESTS |
55 | | static void DetectIdRegisterTests(void); |
56 | | #endif |
57 | | void DetectIdFree(DetectEngineCtx *, void *); |
58 | | |
59 | | static int PrefilterSetupId(DetectEngineCtx *de_ctx, SigGroupHead *sgh); |
60 | | static bool PrefilterIdIsPrefilterable(const Signature *s); |
61 | | |
62 | | /** |
63 | | * \brief Registration function for keyword: id |
64 | | */ |
65 | | void DetectIdRegister (void) |
66 | 73 | { |
67 | 73 | sigmatch_table[DETECT_ID].name = "id"; |
68 | 73 | sigmatch_table[DETECT_ID].desc = "match on a specific IP ID value"; |
69 | 73 | sigmatch_table[DETECT_ID].url = "/rules/header-keywords.html#id"; |
70 | 73 | sigmatch_table[DETECT_ID].Match = DetectIdMatch; |
71 | 73 | sigmatch_table[DETECT_ID].Setup = DetectIdSetup; |
72 | 73 | sigmatch_table[DETECT_ID].Free = DetectIdFree; |
73 | | #ifdef UNITTESTS |
74 | | sigmatch_table[DETECT_ID].RegisterTests = DetectIdRegisterTests; |
75 | | #endif |
76 | 73 | sigmatch_table[DETECT_ID].SupportsPrefilter = PrefilterIdIsPrefilterable; |
77 | 73 | sigmatch_table[DETECT_ID].SetupPrefilter = PrefilterSetupId; |
78 | | |
79 | 73 | DetectSetupParseRegexes(PARSE_REGEX, &parse_regex); |
80 | 73 | } |
81 | | |
82 | | /** |
83 | | * \brief This function is used to match the specified id on a packet |
84 | | * |
85 | | * \param t pointer to thread vars |
86 | | * \param det_ctx pointer to the pattern matcher thread |
87 | | * \param p pointer to the current packet |
88 | | * \param m pointer to the sigmatch that we will cast into DetectIdData |
89 | | * |
90 | | * \retval 0 no match |
91 | | * \retval 1 match |
92 | | */ |
93 | | static int DetectIdMatch (DetectEngineThreadCtx *det_ctx, Packet *p, |
94 | | const Signature *s, const SigMatchCtx *ctx) |
95 | 454 | { |
96 | 454 | const DetectIdData *id_d = (const DetectIdData *)ctx; |
97 | | |
98 | | /** |
99 | | * To match a ipv4 packet with a "id" rule |
100 | | */ |
101 | 454 | if (!PKT_IS_IPV4(p) || PKT_IS_PSEUDOPKT(p)) { |
102 | 43 | return 0; |
103 | 43 | } |
104 | | |
105 | 411 | if (id_d->id == IPV4_GET_IPID(p)) { |
106 | 227 | SCLogDebug("IPV4 Proto and matched with ip_id: %u.\n", |
107 | 227 | id_d->id); |
108 | 227 | return 1; |
109 | 227 | } |
110 | | |
111 | 184 | return 0; |
112 | 411 | } |
113 | | |
114 | | /** |
115 | | * \brief This function is used to parse IPV4 ip_id passed via keyword: "id" |
116 | | * |
117 | | * \param idstr Pointer to the user provided id option |
118 | | * |
119 | | * \retval id_d pointer to DetectIdData on success |
120 | | * \retval NULL on failure |
121 | | */ |
122 | | static DetectIdData *DetectIdParse (const char *idstr) |
123 | 3.43k | { |
124 | 3.43k | uint16_t temp; |
125 | 3.43k | DetectIdData *id_d = NULL; |
126 | 3.43k | int res = 0; |
127 | 3.43k | size_t pcre2len; |
128 | 3.43k | pcre2_match_data *match = NULL; |
129 | | |
130 | 3.43k | int ret = DetectParsePcreExec(&parse_regex, &match, idstr, 0, 0); |
131 | | |
132 | 3.43k | if (ret < 1 || ret > 3) { |
133 | 810 | SCLogError("invalid id option '%s'. The id option " |
134 | 810 | "value must be in the range %u - %u", |
135 | 810 | idstr, DETECT_IPID_MIN, DETECT_IPID_MAX); |
136 | 810 | goto error; |
137 | 810 | } |
138 | | |
139 | 2.62k | char copy_str[128] = ""; |
140 | 2.62k | char *tmp_str; |
141 | 2.62k | pcre2len = sizeof(copy_str); |
142 | 2.62k | res = pcre2_substring_copy_bynumber(match, 1, (PCRE2_UCHAR8 *)copy_str, &pcre2len); |
143 | 2.62k | if (res < 0) { |
144 | 0 | SCLogError("pcre2_substring_copy_bynumber failed"); |
145 | 0 | goto error; |
146 | 0 | } |
147 | 2.62k | tmp_str = copy_str; |
148 | | |
149 | | /* Let's see if we need to scape "'s */ |
150 | 2.62k | if (tmp_str[0] == '"') |
151 | 0 | { |
152 | 0 | tmp_str[strlen(tmp_str) - 1] = '\0'; |
153 | 0 | tmp_str += 1; |
154 | 0 | } |
155 | | |
156 | | /* ok, fill the id data */ |
157 | 2.62k | if (StringParseUint16(&temp, 10, 0, (const char *)tmp_str) <= 0) { |
158 | 51 | SCLogError("invalid id option '%s'", tmp_str); |
159 | 51 | goto error; |
160 | 51 | } |
161 | | |
162 | | /* We have a correct id option */ |
163 | 2.57k | id_d = SCMalloc(sizeof(DetectIdData)); |
164 | 2.57k | if (unlikely(id_d == NULL)) |
165 | 0 | goto error; |
166 | | |
167 | 2.57k | id_d->id = temp; |
168 | | |
169 | 2.57k | SCLogDebug("detect-id: will look for ip_id: %u\n", id_d->id); |
170 | 2.57k | pcre2_match_data_free(match); |
171 | 2.57k | return id_d; |
172 | | |
173 | 861 | error: |
174 | 861 | if (match) { |
175 | 861 | pcre2_match_data_free(match); |
176 | 861 | } |
177 | 861 | return NULL; |
178 | 2.57k | } |
179 | | |
180 | | /** |
181 | | * \brief this function is used to add the parsed "id" option |
182 | | * \brief into the current signature |
183 | | * |
184 | | * \param de_ctx pointer to the Detection Engine Context |
185 | | * \param s pointer to the Current Signature |
186 | | * \param idstr pointer to the user provided "id" option |
187 | | * |
188 | | * \retval 0 on Success |
189 | | * \retval -1 on Failure |
190 | | */ |
191 | | int DetectIdSetup (DetectEngineCtx *de_ctx, Signature *s, const char *idstr) |
192 | 5.82k | { |
193 | 5.82k | DetectIdData *id_d = NULL; |
194 | 5.82k | SigMatch *sm = NULL; |
195 | | |
196 | 5.82k | id_d = DetectIdParse(idstr); |
197 | 5.82k | if (id_d == NULL) |
198 | 1.64k | return -1; |
199 | | |
200 | | /* Okay so far so good, lets get this into a SigMatch |
201 | | * and put it in the Signature. */ |
202 | 4.17k | sm = SigMatchAlloc(); |
203 | 4.17k | if (sm == NULL) { |
204 | 0 | DetectIdFree(de_ctx, id_d); |
205 | 0 | return -1; |
206 | 0 | } |
207 | | |
208 | 4.17k | sm->type = DETECT_ID; |
209 | 4.17k | sm->ctx = (SigMatchCtx *)id_d; |
210 | | |
211 | 4.17k | SigMatchAppendSMToList(s, sm, DETECT_SM_LIST_MATCH); |
212 | 4.17k | s->flags |= SIG_FLAG_REQUIRE_PACKET; |
213 | 4.17k | return 0; |
214 | 4.17k | } |
215 | | |
216 | | /** |
217 | | * \brief this function will free memory associated with DetectIdData |
218 | | * |
219 | | * \param id_d pointer to DetectIdData |
220 | | */ |
221 | | void DetectIdFree(DetectEngineCtx *de_ctx, void *ptr) |
222 | 4.17k | { |
223 | 4.17k | DetectIdData *id_d = (DetectIdData *)ptr; |
224 | 4.17k | SCFree(id_d); |
225 | 4.17k | } |
226 | | |
227 | | /* prefilter code */ |
228 | | |
229 | | static void |
230 | | PrefilterPacketIdMatch(DetectEngineThreadCtx *det_ctx, Packet *p, const void *pectx) |
231 | 45 | { |
232 | 45 | const PrefilterPacketHeaderCtx *ctx = pectx; |
233 | | |
234 | 45 | if (!PKT_IS_IPV4(p) || PKT_IS_PSEUDOPKT(p)) { |
235 | 13 | return; |
236 | 13 | } |
237 | | |
238 | 32 | if (!PrefilterPacketHeaderExtraMatch(ctx, p)) |
239 | 32 | return; |
240 | | |
241 | 0 | if (IPV4_GET_IPID(p) == ctx->v1.u16[0]) |
242 | 0 | { |
243 | 0 | SCLogDebug("packet matches IP id %u", ctx->v1.u16[0]); |
244 | 0 | PrefilterAddSids(&det_ctx->pmq, ctx->sigs_array, ctx->sigs_cnt); |
245 | 0 | } |
246 | 0 | } |
247 | | |
248 | | static void |
249 | | PrefilterPacketIdSet(PrefilterPacketHeaderValue *v, void *smctx) |
250 | 216 | { |
251 | 216 | const DetectIdData *a = smctx; |
252 | 216 | v->u16[0] = a->id; |
253 | 216 | } |
254 | | |
255 | | static bool |
256 | | PrefilterPacketIdCompare(PrefilterPacketHeaderValue v, void *smctx) |
257 | 108 | { |
258 | 108 | const DetectIdData *a = smctx; |
259 | 108 | if (v.u16[0] == a->id) |
260 | 108 | return true; |
261 | 0 | return false; |
262 | 108 | } |
263 | | |
264 | | static int PrefilterSetupId(DetectEngineCtx *de_ctx, SigGroupHead *sgh) |
265 | 1.46k | { |
266 | 1.46k | return PrefilterSetupPacketHeader(de_ctx, sgh, DETECT_ID, |
267 | 1.46k | PrefilterPacketIdSet, |
268 | 1.46k | PrefilterPacketIdCompare, |
269 | 1.46k | PrefilterPacketIdMatch); |
270 | 1.46k | } |
271 | | |
272 | | static bool PrefilterIdIsPrefilterable(const Signature *s) |
273 | 0 | { |
274 | 0 | const SigMatch *sm; |
275 | 0 | for (sm = s->init_data->smlists[DETECT_SM_LIST_MATCH] ; sm != NULL; sm = sm->next) { |
276 | 0 | switch (sm->type) { |
277 | 0 | case DETECT_ID: |
278 | 0 | return true; |
279 | 0 | } |
280 | 0 | } |
281 | 0 | return false; |
282 | 0 | } |
283 | | |
284 | | #ifdef UNITTESTS /* UNITTESTS */ |
285 | | |
286 | | /** |
287 | | * \test DetectIdTestParse01 is a test to make sure that we parse the "id" |
288 | | * option correctly when given valid id option |
289 | | */ |
290 | | static int DetectIdTestParse01 (void) |
291 | | { |
292 | | DetectIdData *id_d = DetectIdParse(" 35402 "); |
293 | | |
294 | | FAIL_IF_NULL(id_d); |
295 | | FAIL_IF_NOT(id_d->id == 35402); |
296 | | |
297 | | DetectIdFree(NULL, id_d); |
298 | | |
299 | | PASS; |
300 | | } |
301 | | |
302 | | /** |
303 | | * \test DetectIdTestParse02 is a test to make sure that we parse the "id" |
304 | | * option correctly when given an invalid id option |
305 | | * it should return id_d = NULL |
306 | | */ |
307 | | static int DetectIdTestParse02 (void) |
308 | | { |
309 | | DetectIdData *id_d = DetectIdParse("65537"); |
310 | | |
311 | | FAIL_IF_NOT_NULL(id_d); |
312 | | |
313 | | PASS; |
314 | | } |
315 | | |
316 | | /** |
317 | | * \test DetectIdTestParse03 is a test to make sure that we parse the "id" |
318 | | * option correctly when given an invalid id option |
319 | | * it should return id_d = NULL |
320 | | */ |
321 | | static int DetectIdTestParse03 (void) |
322 | | { |
323 | | DetectIdData *id_d = DetectIdParse("12what?"); |
324 | | |
325 | | FAIL_IF_NOT_NULL(id_d); |
326 | | |
327 | | PASS; |
328 | | } |
329 | | |
330 | | /** |
331 | | * \test DetectIdTestParse04 is a test to make sure that we parse the "id" |
332 | | * option correctly when given valid id option but wrapped with "'s |
333 | | */ |
334 | | static int DetectIdTestParse04 (void) |
335 | | { |
336 | | /* yep, look if we trim blank spaces correctly and ignore "'s */ |
337 | | DetectIdData *id_d = DetectIdParse(" \"35402\" "); |
338 | | |
339 | | FAIL_IF_NULL(id_d); |
340 | | FAIL_IF_NOT(id_d->id == 35402); |
341 | | |
342 | | DetectIdFree(NULL, id_d); |
343 | | |
344 | | PASS; |
345 | | } |
346 | | |
347 | | /** |
348 | | * \test DetectIdTestSig01 |
349 | | * \brief Test to check "id" keyword with constructed packets |
350 | | */ |
351 | | static int DetectIdTestMatch01(void) |
352 | | { |
353 | | uint8_t *buf = (uint8_t *)"Hi all!"; |
354 | | uint16_t buflen = strlen((char *)buf); |
355 | | Packet *p[3]; |
356 | | p[0] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_TCP); |
357 | | p[1] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_UDP); |
358 | | p[2] = UTHBuildPacket((uint8_t *)buf, buflen, IPPROTO_ICMP); |
359 | | |
360 | | FAIL_IF_NULL(p[0]); |
361 | | FAIL_IF_NULL(p[1]); |
362 | | FAIL_IF_NULL(p[2]); |
363 | | |
364 | | /* TCP IP id = 1234 */ |
365 | | p[0]->ip4h->ip_id = htons(1234); |
366 | | |
367 | | /* UDP IP id = 5678 */ |
368 | | p[1]->ip4h->ip_id = htons(5678); |
369 | | |
370 | | /* UDP IP id = 91011 */ |
371 | | p[2]->ip4h->ip_id = htons(5101); |
372 | | |
373 | | const char *sigs[3]; |
374 | | sigs[0]= "alert ip any any -> any any (msg:\"Testing id 1\"; id:1234; sid:1;)"; |
375 | | sigs[1]= "alert ip any any -> any any (msg:\"Testing id 2\"; id:5678; sid:2;)"; |
376 | | sigs[2]= "alert ip any any -> any any (msg:\"Testing id 3\"; id:5101; sid:3;)"; |
377 | | |
378 | | uint32_t sid[3] = {1, 2, 3}; |
379 | | |
380 | | uint32_t results[3][3] = { |
381 | | /* packet 0 match sid 1 but should not match sid 2 */ |
382 | | {1, 0, 0}, |
383 | | /* packet 1 should not match */ |
384 | | {0, 1, 0}, |
385 | | /* packet 2 should not match */ |
386 | | {0, 0, 1} }; |
387 | | |
388 | | FAIL_IF_NOT(UTHGenericTest(p, 3, sigs, sid, (uint32_t *)results, 3)); |
389 | | |
390 | | UTHFreePackets(p, 3); |
391 | | |
392 | | PASS; |
393 | | } |
394 | | |
395 | | /** |
396 | | * \brief this function registers unit tests for DetectId |
397 | | */ |
398 | | void DetectIdRegisterTests(void) |
399 | | { |
400 | | UtRegisterTest("DetectIdTestParse01", DetectIdTestParse01); |
401 | | UtRegisterTest("DetectIdTestParse02", DetectIdTestParse02); |
402 | | UtRegisterTest("DetectIdTestParse03", DetectIdTestParse03); |
403 | | UtRegisterTest("DetectIdTestParse04", DetectIdTestParse04); |
404 | | UtRegisterTest("DetectIdTestMatch01", DetectIdTestMatch01); |
405 | | |
406 | | } |
407 | | #endif /* UNITTESTS */ |