/src/suricata8/src/detect-dns-response.c
Line | Count | Source |
1 | | /* Copyright (C) 2025 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 | | * Detect keyword for DNS response: dns.response.rrname |
22 | | */ |
23 | | |
24 | | #include "detect.h" |
25 | | #include "detect-parse.h" |
26 | | #include "detect-engine.h" |
27 | | #include "detect-engine-buffer.h" |
28 | | #include "detect-engine-mpm.h" |
29 | | #include "detect-engine-prefilter.h" |
30 | | #include "detect-engine-content-inspection.h" |
31 | | #include "detect-engine-helper.h" |
32 | | #include "detect-dns-response.h" |
33 | | #include "util-profiling.h" |
34 | | #include "rust.h" |
35 | | |
36 | | static int detect_buffer_id = 0; |
37 | | static int mdns_detect_buffer_id = 0; |
38 | | |
39 | | typedef struct PrefilterMpm { |
40 | | int list_id; |
41 | | const MpmCtx *mpm_ctx; |
42 | | const DetectEngineTransforms *transforms; |
43 | | } PrefilterMpm; |
44 | | |
45 | | enum DnsResponseSection { |
46 | | DNS_RESPONSE_QUERY = 0, |
47 | | DNS_RESPONSE_ANSWER, |
48 | | DNS_RESPONSE_AUTHORITY, |
49 | | DNS_RESPONSE_ADDITIONAL, |
50 | | |
51 | | /* always last */ |
52 | | DNS_RESPONSE_MAX, |
53 | | }; |
54 | | |
55 | | struct DnsResponseGetDataArgs { |
56 | | enum DnsResponseSection response_section; /**< query, answer, authority, additional */ |
57 | | uint32_t response_id; /**< index into response resource records */ |
58 | | uint32_t local_id; /**< used as index into thread inspect array */ |
59 | | }; |
60 | | |
61 | | static int DetectSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str) |
62 | 157 | { |
63 | 157 | if (SCDetectBufferSetActiveList(de_ctx, s, detect_buffer_id) < 0) { |
64 | 2 | return -1; |
65 | 2 | } |
66 | 155 | if (SCDetectSignatureSetAppProto(s, ALPROTO_DNS) < 0) { |
67 | 3 | return -1; |
68 | 3 | } |
69 | | |
70 | 152 | return 0; |
71 | 155 | } |
72 | | |
73 | | static int MdnsDetectSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str) |
74 | 92 | { |
75 | 92 | if (SCDetectBufferSetActiveList(de_ctx, s, mdns_detect_buffer_id) < 0) { |
76 | 2 | return -1; |
77 | 2 | } |
78 | 90 | if (SCDetectSignatureSetAppProto(s, ALPROTO_MDNS) < 0) { |
79 | 2 | return -1; |
80 | 2 | } |
81 | | |
82 | 88 | return 0; |
83 | 90 | } |
84 | | |
85 | | static InspectionBuffer *GetBuffer(DetectEngineThreadCtx *det_ctx, uint8_t flags, |
86 | | const DetectEngineTransforms *transforms, void *txv, struct DnsResponseGetDataArgs *cbdata, |
87 | | int list_id, bool get_rdata) |
88 | 19 | { |
89 | 19 | InspectionBuffer *buffer = |
90 | 19 | InspectionBufferMultipleForListGet(det_ctx, list_id, cbdata->local_id); |
91 | 19 | if (buffer == NULL) { |
92 | 0 | return NULL; |
93 | 0 | } |
94 | 19 | if (buffer->initialized) { |
95 | 5 | return buffer; |
96 | 5 | } |
97 | | |
98 | 14 | const uint8_t *data = NULL; |
99 | 14 | uint32_t data_len = 0; |
100 | | |
101 | 14 | if (get_rdata) { |
102 | | /* Get rdata values that are formatted as resource names. */ |
103 | 2 | switch (cbdata->response_section) { |
104 | 2 | case DNS_RESPONSE_ANSWER: |
105 | 2 | if (!SCDnsTxGetAnswerRdata(txv, cbdata->response_id, &data, &data_len)) { |
106 | 2 | InspectionBufferSetupMultiEmpty(buffer); |
107 | 2 | return NULL; |
108 | 2 | } |
109 | 0 | break; |
110 | 0 | case DNS_RESPONSE_AUTHORITY: |
111 | 0 | if (!SCDnsTxGetAuthorityRdata(txv, cbdata->response_id, &data, &data_len)) { |
112 | 0 | InspectionBufferSetupMultiEmpty(buffer); |
113 | 0 | return NULL; |
114 | 0 | } |
115 | 0 | break; |
116 | 0 | case DNS_RESPONSE_ADDITIONAL: |
117 | 0 | if (!SCDnsTxGetAdditionalRdata(txv, cbdata->response_id, &data, &data_len)) { |
118 | 0 | InspectionBufferSetupMultiEmpty(buffer); |
119 | 0 | return NULL; |
120 | 0 | } |
121 | 0 | break; |
122 | 0 | default: |
123 | 0 | InspectionBufferSetupMultiEmpty(buffer); |
124 | 0 | return NULL; |
125 | 2 | } |
126 | 12 | } else { |
127 | | /* Get name values. */ |
128 | 12 | switch (cbdata->response_section) { |
129 | 4 | case DNS_RESPONSE_QUERY: |
130 | 4 | if (!SCDnsTxGetQueryName( |
131 | 4 | det_ctx, txv, STREAM_TOCLIENT, cbdata->response_id, &data, &data_len)) { |
132 | 2 | InspectionBufferSetupMultiEmpty(buffer); |
133 | 2 | return NULL; |
134 | 2 | } |
135 | 2 | break; |
136 | 4 | case DNS_RESPONSE_ANSWER: |
137 | 4 | if (!SCDnsTxGetAnswerName( |
138 | 4 | det_ctx, txv, STREAM_TOCLIENT, cbdata->response_id, &data, &data_len)) { |
139 | 2 | InspectionBufferSetupMultiEmpty(buffer); |
140 | 2 | return NULL; |
141 | 2 | } |
142 | 2 | break; |
143 | 2 | case DNS_RESPONSE_AUTHORITY: |
144 | 2 | if (!SCDnsTxGetAuthorityName( |
145 | 2 | det_ctx, txv, 0, cbdata->response_id, &data, &data_len)) { |
146 | 2 | InspectionBufferSetupMultiEmpty(buffer); |
147 | 2 | return NULL; |
148 | 2 | } |
149 | 0 | break; |
150 | 2 | case DNS_RESPONSE_ADDITIONAL: |
151 | 2 | if (!SCDnsTxGetAdditionalName( |
152 | 2 | det_ctx, txv, 0, cbdata->response_id, &data, &data_len)) { |
153 | 2 | InspectionBufferSetupMultiEmpty(buffer); |
154 | 2 | return NULL; |
155 | 2 | } |
156 | 0 | break; |
157 | 0 | default: |
158 | 0 | InspectionBufferSetupMultiEmpty(buffer); |
159 | 0 | return NULL; |
160 | 12 | } |
161 | 12 | } |
162 | | |
163 | 4 | InspectionBufferSetupMulti(det_ctx, buffer, transforms, data, data_len); |
164 | 4 | buffer->flags = DETECT_CI_FLAGS_SINGLE; |
165 | 4 | return buffer; |
166 | 14 | } |
167 | | |
168 | | static inline uint8_t CheckSectionRecords(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, |
169 | | const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f, |
170 | | uint8_t flags, void *txv, const DetectEngineTransforms *transforms, uint32_t *local_id, |
171 | | enum DnsResponseSection section) |
172 | 4 | { |
173 | 4 | uint32_t response_id = 0; |
174 | | |
175 | | /* loop through each record in DNS response section inspecting "name" and "rdata" */ |
176 | 5 | while (1) { |
177 | 5 | struct DnsResponseGetDataArgs cbdata = { section, response_id, *local_id }; |
178 | | |
179 | | /* do inspection for resource record "name" */ |
180 | 5 | InspectionBuffer *buffer = |
181 | 5 | GetBuffer(det_ctx, flags, transforms, txv, &cbdata, engine->sm_list, false); |
182 | 5 | if (buffer == NULL || buffer->inspect == NULL) { |
183 | 1 | (*local_id)++; |
184 | 1 | break; |
185 | 1 | } |
186 | | |
187 | 4 | if (DetectEngineContentInspectionBuffer(de_ctx, det_ctx, s, engine->smd, NULL, f, buffer, |
188 | 4 | DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE)) { |
189 | 3 | return DETECT_ENGINE_INSPECT_SIG_MATCH; |
190 | 3 | } |
191 | | |
192 | 1 | (*local_id)++; |
193 | 1 | if (section == DNS_RESPONSE_QUERY) { |
194 | | /* no rdata to inspect for query section, move on to next record */ |
195 | 1 | response_id++; |
196 | 1 | continue; |
197 | 1 | } |
198 | | |
199 | | /* do inspection for resource record "rdata" */ |
200 | 0 | cbdata.local_id = *local_id; |
201 | 0 | buffer = GetBuffer(det_ctx, flags, transforms, txv, &cbdata, engine->sm_list, true); |
202 | 0 | if (buffer == NULL || buffer->inspect == NULL) { |
203 | 0 | (*local_id)++; |
204 | 0 | response_id++; |
205 | 0 | continue; |
206 | 0 | } |
207 | | |
208 | 0 | if (DetectEngineContentInspectionBuffer(de_ctx, det_ctx, s, engine->smd, NULL, f, buffer, |
209 | 0 | DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE)) { |
210 | 0 | return DETECT_ENGINE_INSPECT_SIG_MATCH; |
211 | 0 | } |
212 | 0 | (*local_id)++; |
213 | 0 | response_id++; |
214 | 0 | } |
215 | 1 | return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; |
216 | 4 | } |
217 | | |
218 | | static inline void CheckSectionRecordsPrefilter(DetectEngineThreadCtx *det_ctx, const void *pectx, |
219 | | void *txv, const uint8_t flags, uint32_t *local_id, enum DnsResponseSection section) |
220 | 8 | { |
221 | 8 | const PrefilterMpm *ctx = (const PrefilterMpm *)pectx; |
222 | 8 | const MpmCtx *mpm_ctx = ctx->mpm_ctx; |
223 | 8 | const int list_id = ctx->list_id; |
224 | 8 | uint32_t response_id = 0; |
225 | | |
226 | 12 | while (1) { |
227 | 12 | struct DnsResponseGetDataArgs cbdata = { section, response_id, *local_id }; |
228 | | |
229 | | /* extract resource record "name" */ |
230 | 12 | InspectionBuffer *buffer = |
231 | 12 | GetBuffer(det_ctx, flags, ctx->transforms, txv, &cbdata, list_id, false); |
232 | 12 | if (buffer == NULL) { |
233 | 8 | (*local_id)++; |
234 | 8 | break; |
235 | 8 | } |
236 | | |
237 | 4 | if (buffer->inspect_len >= mpm_ctx->minlen) { |
238 | 4 | (void)mpm_table[mpm_ctx->mpm_type].Search( |
239 | 4 | mpm_ctx, &det_ctx->mtc, &det_ctx->pmq, buffer->inspect, buffer->inspect_len); |
240 | 4 | PREFILTER_PROFILING_ADD_BYTES(det_ctx, buffer->inspect_len); |
241 | 4 | } |
242 | | |
243 | 4 | (*local_id)++; |
244 | 4 | if (section == DNS_RESPONSE_QUERY) { |
245 | | /* no rdata to inspect for query section, move on to next name entry */ |
246 | 2 | response_id++; |
247 | 2 | continue; |
248 | 2 | } |
249 | | |
250 | | /* extract resource record "rdata" */ |
251 | 2 | cbdata.local_id = *local_id; |
252 | 2 | buffer = GetBuffer(det_ctx, flags, ctx->transforms, txv, &cbdata, list_id, true); |
253 | 2 | if (buffer == NULL) { |
254 | 2 | (*local_id)++; |
255 | 2 | response_id++; |
256 | 2 | continue; |
257 | 2 | } |
258 | | |
259 | 0 | if (buffer->inspect_len >= mpm_ctx->minlen) { |
260 | 0 | (void)mpm_table[mpm_ctx->mpm_type].Search( |
261 | 0 | mpm_ctx, &det_ctx->mtc, &det_ctx->pmq, buffer->inspect, buffer->inspect_len); |
262 | 0 | PREFILTER_PROFILING_ADD_BYTES(det_ctx, buffer->inspect_len); |
263 | 0 | } |
264 | 0 | (*local_id)++; |
265 | 0 | response_id++; |
266 | 0 | } |
267 | 8 | } |
268 | | |
269 | | static uint8_t DetectEngineInspectCb(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, |
270 | | const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f, |
271 | | uint8_t flags, void *alstate, void *txv, uint64_t tx_id) |
272 | 3 | { |
273 | 3 | const DetectEngineTransforms *transforms = NULL; |
274 | 3 | if (!engine->mpm) { |
275 | 0 | transforms = engine->v2.transforms; |
276 | 0 | } |
277 | | |
278 | 3 | uint32_t local_id = 0; |
279 | 3 | uint8_t ret_match = DETECT_ENGINE_INSPECT_SIG_NO_MATCH; |
280 | | |
281 | | /* loop through each possible DNS response section */ |
282 | 3 | for (enum DnsResponseSection section = DNS_RESPONSE_QUERY; |
283 | 7 | section < DNS_RESPONSE_MAX && ret_match == DETECT_ENGINE_INSPECT_SIG_NO_MATCH; |
284 | 4 | section++) { |
285 | | |
286 | | /* check each record in section inspecting "name" and "rdata" */ |
287 | 4 | ret_match = CheckSectionRecords( |
288 | 4 | de_ctx, det_ctx, engine, s, f, flags, txv, transforms, &local_id, section); |
289 | 4 | } |
290 | 3 | return ret_match; |
291 | 3 | } |
292 | | |
293 | | static void DetectDnsResponsePrefilterTx(DetectEngineThreadCtx *det_ctx, const void *pectx, |
294 | | Packet *p, Flow *f, void *txv, const uint64_t idx, const AppLayerTxData *_txd, |
295 | | const uint8_t flags) |
296 | 2 | { |
297 | 2 | SCEnter(); |
298 | | |
299 | 2 | uint32_t local_id = 0; |
300 | | /* loop through each possible DNS response section */ |
301 | 10 | for (enum DnsResponseSection section = DNS_RESPONSE_QUERY; section < DNS_RESPONSE_MAX; |
302 | 8 | section++) { |
303 | | /* check each record in section inspecting "name" and "rdata" */ |
304 | 8 | CheckSectionRecordsPrefilter(det_ctx, pectx, txv, flags, &local_id, section); |
305 | 8 | } |
306 | 2 | } |
307 | | |
308 | | static void DetectDnsResponsePrefilterMpmFree(void *ptr) |
309 | 92 | { |
310 | 92 | SCFree(ptr); |
311 | 92 | } |
312 | | |
313 | | static int DetectDnsResponsePrefilterMpmRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, |
314 | | MpmCtx *mpm_ctx, const DetectBufferMpmRegistry *mpm_reg, int list_id) |
315 | 92 | { |
316 | 92 | PrefilterMpm *pectx = SCCalloc(1, sizeof(*pectx)); |
317 | 92 | if (pectx == NULL) { |
318 | 0 | return -1; |
319 | 0 | } |
320 | 92 | pectx->list_id = list_id; |
321 | 92 | pectx->mpm_ctx = mpm_ctx; |
322 | 92 | pectx->transforms = &mpm_reg->transforms; |
323 | | |
324 | 92 | return PrefilterAppendTxEngine(de_ctx, sgh, DetectDnsResponsePrefilterTx, |
325 | 92 | mpm_reg->app_v2.alproto, mpm_reg->app_v2.tx_min_progress, pectx, |
326 | 92 | DetectDnsResponsePrefilterMpmFree, mpm_reg->pname); |
327 | 92 | } |
328 | | |
329 | | static void SCDetectMdnsResponseRrnameRegister(void) |
330 | 12 | { |
331 | 12 | static const char *keyword = "mdns.response.rrname"; |
332 | 12 | int keyword_id = SCDetectHelperNewKeywordId(); |
333 | 12 | sigmatch_table[keyword_id].name = keyword; |
334 | 12 | sigmatch_table[keyword_id].desc = "mDNS response rrname buffer"; |
335 | 12 | sigmatch_table[keyword_id].url = "/rules/mdns-keywords.html#mdns-response-rrname"; |
336 | 12 | sigmatch_table[keyword_id].Setup = MdnsDetectSetup; |
337 | 12 | sigmatch_table[keyword_id].flags |= SIGMATCH_NOOPT; |
338 | 12 | sigmatch_table[keyword_id].flags |= SIGMATCH_INFO_STICKY_BUFFER; |
339 | | |
340 | | /* Register in the TO_SERVER direction, as all mDNS is toserver. */ |
341 | 12 | DetectAppLayerInspectEngineRegister( |
342 | 12 | keyword, ALPROTO_MDNS, SIG_FLAG_TOSERVER, 1, DetectEngineInspectCb, NULL); |
343 | 12 | DetectAppLayerMpmRegister(keyword, SIG_FLAG_TOSERVER, 2, DetectDnsResponsePrefilterMpmRegister, |
344 | 12 | NULL, ALPROTO_MDNS, 1); |
345 | | |
346 | 12 | DetectBufferTypeSetDescriptionByName(keyword, "mdns response rdata"); |
347 | 12 | DetectBufferTypeSupportsMultiInstance(keyword); |
348 | | |
349 | 12 | mdns_detect_buffer_id = DetectBufferTypeGetByName(keyword); |
350 | 12 | } |
351 | | |
352 | | void DetectDnsResponseRegister(void) |
353 | 12 | { |
354 | 12 | static const char *keyword = "dns.response.rrname"; |
355 | 12 | sigmatch_table[DETECT_DNS_RESPONSE].name = keyword; |
356 | 12 | sigmatch_table[DETECT_DNS_RESPONSE].desc = "DNS response sticky buffer"; |
357 | 12 | sigmatch_table[DETECT_DNS_RESPONSE].url = "/rules/dns-keywords.html#dns-response-rrname"; |
358 | 12 | sigmatch_table[DETECT_DNS_RESPONSE].Setup = DetectSetup; |
359 | 12 | sigmatch_table[DETECT_DNS_RESPONSE].flags |= SIGMATCH_NOOPT; |
360 | 12 | sigmatch_table[DETECT_DNS_RESPONSE].flags |= SIGMATCH_INFO_STICKY_BUFFER; |
361 | | |
362 | | /* Register in the TO_CLIENT direction. */ |
363 | 12 | DetectAppLayerInspectEngineRegister( |
364 | 12 | keyword, ALPROTO_DNS, SIG_FLAG_TOCLIENT, 1, DetectEngineInspectCb, NULL); |
365 | 12 | DetectAppLayerMpmRegister(keyword, SIG_FLAG_TOCLIENT, 2, DetectDnsResponsePrefilterMpmRegister, |
366 | 12 | NULL, ALPROTO_DNS, 1); |
367 | | |
368 | 12 | DetectBufferTypeSetDescriptionByName(keyword, "dns response rrname"); |
369 | 12 | DetectBufferTypeSupportsMultiInstance(keyword); |
370 | | |
371 | 12 | detect_buffer_id = DetectBufferTypeGetByName(keyword); |
372 | | |
373 | 12 | SCDetectMdnsResponseRrnameRegister(); |
374 | 12 | } |