Coverage Report

Created: 2026-11-12 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}