Coverage Report

Created: 2026-02-26 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/protocols/dns/decode.c
Line
Count
Source
1
/*
2
 *   This library is free software; you can redistribute it and/or
3
 *   modify it under the terms of the GNU Lesser General Public
4
 *   License as published by the Free Software Foundation; either
5
 *   version 2.1 of the License, or (at your option) any later version.
6
 *
7
 *   This library 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 GNU
10
 *   Lesser General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU Lesser General Public
13
 *   License along with this library; if not, write to the Free Software
14
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15
 */
16
17
/**
18
 * $Id: 92a7088f3c23773e4746d8e63ef57de0795f5d51 $
19
 *
20
 * @file protocols/dns/decode.c
21
 * @brief Functions to decode DNS packets.
22
 *
23
 * @author Alan DeKok (aland@freeradius.org)
24
 *
25
 * @copyright 2021 The FreeRADIUS server project
26
 * @copyright 2021 NetworkRADIUS SARL (legal@networkradius.com)
27
 */
28
#include <freeradius-devel/io/test_point.h>
29
#include <freeradius-devel/util/dns.h>
30
#include <freeradius-devel/util/proto.h>
31
#include <freeradius-devel/util/struct.h>
32
33
#include "dns.h"
34
#include "attrs.h"
35
36
static ssize_t decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out,
37
            fr_dict_attr_t const *parent,
38
            uint8_t const *data, size_t const data_len, void *decode_ctx);
39
40
static ssize_t decode_value(TALLOC_CTX *ctx, fr_pair_list_t *out,
41
          fr_dict_attr_t const *parent,
42
          uint8_t const *data, size_t const data_len, void *decode_ctx);
43
44
static ssize_t decode_tlv_trampoline(TALLOC_CTX *ctx, fr_pair_list_t *out,
45
             fr_dict_attr_t const *parent,
46
             uint8_t const *data, size_t const data_len, void *decode_ctx)
47
691
{
48
691
  return fr_pair_tlvs_from_network(ctx, out, parent, data, data_len, decode_ctx, decode_option, NULL, true);
49
691
}
50
51
52
static ssize_t decode_value_trampoline(TALLOC_CTX *ctx, fr_pair_list_t *out,
53
               fr_dict_attr_t const *parent,
54
               uint8_t const *data, size_t const data_len, void *decode_ctx)
55
165k
{
56
165k
  if ((parent->type == FR_TYPE_STRING) && fr_dns_flag_dns_label(parent)) {
57
49.1k
    fr_dns_ctx_t    *packet_ctx = decode_ctx;
58
59
49.1k
    return fr_pair_dns_labels_from_network(ctx, out, parent, packet_ctx->packet, data, data_len, packet_ctx->lb, false);
60
49.1k
  }
61
62
116k
  return decode_value(ctx, out, parent, data, data_len, decode_ctx);
63
165k
}
64
65
66
static ssize_t decode_value(TALLOC_CTX *ctx, fr_pair_list_t *out,
67
          fr_dict_attr_t const *parent,
68
          uint8_t const *data, size_t const data_len, void *decode_ctx)
69
123k
{
70
123k
  ssize_t     slen;
71
123k
  fr_pair_t   *vp;
72
73
123k
  FR_PROTO_HEX_DUMP(data, data_len, "decode_value");
74
75
123k
  switch (parent->type) {
76
  /*
77
   *  Address MAY be shorter than 16 bytes.
78
   */
79
0
  case FR_TYPE_IPV6_PREFIX:
80
0
    if (data_len == 0) {
81
3.92k
    raw:
82
3.92k
      return fr_pair_raw_from_network(ctx, out, parent, data, data_len);
83
0
    }
84
85
0
    vp = fr_pair_afrom_da(ctx, parent);
86
0
    if (!vp) return PAIR_DECODE_OOM;
87
0
    PAIR_ALLOCED(vp);
88
89
    /*
90
     *  Check values of prefix length, data lengths, etc.
91
     */
92
0
    if (fr_value_box_ipaddr_from_network(&vp->data, parent->type, parent,
93
0
                 data[0], data + 1, data_len - 1,
94
0
                 (parent->parent->type == FR_TYPE_STRUCT), true) < 0) {
95
0
      talloc_free(vp);
96
0
      goto raw;
97
0
    }
98
0
    break;
99
100
  /*
101
   *  A bool is encoded as an empty option if it's
102
   *  true.  A bool is omitted entirely if it's
103
   *  false.
104
   */
105
657
  case FR_TYPE_BOOL:
106
657
    if (data_len != 0) goto raw;
107
365
    vp = fr_pair_afrom_da(ctx, parent);
108
365
    if (!vp) return PAIR_DECODE_OOM;
109
365
    PAIR_ALLOCED(vp);
110
111
365
    vp->vp_bool = true;
112
365
    break;
113
114
3.09k
  case FR_TYPE_STRUCT:
115
3.09k
    slen = fr_struct_from_network(ctx, out, parent, data, data_len,
116
3.09k
                decode_ctx, decode_value_trampoline, NULL);
117
3.09k
    if (slen < 0) return slen;
118
3.02k
    return data_len;
119
120
0
  case FR_TYPE_GROUP:
121
0
    return PAIR_DECODE_FATAL_ERROR; /* not supported */
122
123
119k
  default:
124
119k
    vp = fr_pair_afrom_da(ctx, parent);
125
119k
    if (!vp) return PAIR_DECODE_OOM;
126
119k
    PAIR_ALLOCED(vp);
127
128
119k
    if (fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
129
119k
                &FR_DBUFF_TMP(data, data_len), data_len, true) < 0) {
130
3.63k
      FR_PROTO_TRACE("failed decoding?");
131
3.63k
      talloc_free(vp);
132
3.63k
      goto raw;
133
3.63k
    }
134
116k
    break;
135
123k
  }
136
137
116k
  vp->vp_tainted = true;
138
116k
  fr_pair_append(out, vp);
139
116k
  return data_len;
140
123k
}
141
142
143
11.4k
#define DNS_GET_OPTION_NUM(_x)  fr_nbo_to_uint16(_x)
144
11.4k
#define DNS_GET_OPTION_LEN(_x)  fr_nbo_to_uint16((_x) + 2)
145
146
static ssize_t decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out,
147
            fr_dict_attr_t const *parent,
148
            uint8_t const *data, size_t const data_len, void *decode_ctx)
149
11.4k
{
150
11.4k
  unsigned int      option;
151
11.4k
  size_t      len;
152
11.4k
  ssize_t     slen;
153
11.4k
  fr_dict_attr_t const  *da;
154
11.4k
  fr_dns_ctx_t    *packet_ctx = decode_ctx;
155
156
#ifdef STATIC_ANALYZER
157
  if (!packet_ctx || !packet_ctx->tmp_ctx) return PAIR_DECODE_FATAL_ERROR;
158
#endif
159
160
  /*
161
   *  Must have at least an option header.
162
   */
163
11.4k
  if (data_len < 4) {
164
7
    fr_strerror_printf("%s: Insufficient data", __FUNCTION__);
165
7
    return -(data_len);
166
7
  }
167
168
11.4k
  option = DNS_GET_OPTION_NUM(data);
169
11.4k
  len = DNS_GET_OPTION_LEN(data);
170
11.4k
  if (len > (data_len - 4)) {
171
26
    fr_strerror_printf("%s: Option overflows input.  "
172
26
           "Optional length must be less than %zu bytes, got %zu bytes",
173
26
           __FUNCTION__, data_len - 4, len);
174
26
    return PAIR_DECODE_FATAL_ERROR;
175
26
  }
176
177
11.4k
  FR_PROTO_HEX_DUMP(data, len + 4, "decode_option");
178
179
11.4k
  da = fr_dict_attr_child_by_num(parent, option);
180
11.4k
  if (!da) {
181
4.10k
    da = fr_dict_attr_unknown_raw_afrom_num(packet_ctx->tmp_ctx, parent, option);
182
4.10k
    if (!da) return PAIR_DECODE_FATAL_ERROR;
183
4.10k
  }
184
11.4k
  FR_PROTO_TRACE("decode context changed %s -> %s",da->parent->name, da->name);
185
186
11.4k
  if ((da->type == FR_TYPE_STRING) && fr_dns_flag_dns_label(da)) {
187
0
    slen = fr_pair_dns_labels_from_network(ctx, out, da, packet_ctx->packet, data + 4, len, packet_ctx->lb, true);
188
189
11.4k
  } else {
190
11.4k
    slen = decode_value(ctx, out, da, data + 4, len, decode_ctx);
191
11.4k
  }
192
11.4k
  fr_dict_attr_unknown_free(&da);
193
194
11.4k
  if (slen < 0) return slen;
195
196
11.3k
  return len + 4;
197
11.4k
}
198
199
static ssize_t decode_record(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *attr,
200
           uint8_t const *rr, uint8_t const *end,
201
           fr_dns_ctx_t *packet_ctx, uint8_t const *counter)
202
4.21k
{
203
4.21k
  unsigned int i, count;
204
4.21k
  uint8_t const *p = rr;
205
206
  /*
207
   *  The header has a count of how many records we need to decode.
208
   */
209
4.21k
  count = fr_nbo_to_uint16(counter);
210
4.21k
  FR_PROTO_TRACE("Decoding %u of %s", count, attr->name);
211
212
  /* coverity[tainted_data] */
213
52.3k
  for (i = 0; (i < count) && (p < end); i++) {
214
48.1k
    ssize_t slen;
215
216
48.1k
    FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - %s %u/%u", attr->name, i, count);
217
218
48.1k
    slen = fr_struct_from_network(ctx, out, attr, p, end - p,
219
48.1k
                packet_ctx, decode_value_trampoline, decode_tlv_trampoline);
220
48.1k
    if (slen < 0) return slen;
221
48.1k
    if (!slen) break;
222
223
48.1k
    fr_assert(slen <= (end - p));
224
225
48.1k
    p += slen;
226
48.1k
  }
227
228
4.19k
  return p - rr;
229
4.21k
}
230
231
/** Decode a DNS packet
232
 *
233
 */
234
ssize_t fr_dns_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *packet, size_t packet_len, fr_dns_ctx_t *packet_ctx)
235
1.05k
{
236
1.05k
  ssize_t     slen;
237
1.05k
  uint8_t const   *p, *end;
238
239
1.05k
  if (packet_len < DNS_HDR_LEN) return 0;
240
241
  /*
242
   *  @todo - synthesize Packet-Type from the various fields.
243
   */
244
245
1.05k
  FR_PROTO_HEX_DUMP(packet, packet_len, "fr_dns_decode");
246
247
  /*
248
   *  Decode the header.
249
   */
250
1.05k
  slen = fr_struct_from_network(ctx, out, attr_dns_packet, packet, DNS_HDR_LEN,
251
1.05k
              packet_ctx, decode_value_trampoline, NULL); /* no TLVs in the header */
252
1.05k
  if (slen < 0) {
253
0
    fr_strerror_printf("Failed decoding DNS header - %s", fr_strerror());
254
0
    return slen;
255
0
  }
256
1.05k
  fr_assert(slen == DNS_HDR_LEN);
257
258
1.05k
  p = packet + DNS_HDR_LEN;
259
1.05k
  end = packet + packet_len;
260
1.05k
  FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after header");
261
262
1.05k
  slen = decode_record(ctx, out, attr_dns_question, p, end, packet_ctx, packet + 4);
263
1.05k
  if (slen < 0) {
264
0
    fr_strerror_printf("Failed decoding questions - %s", fr_strerror());
265
    /* coverity[return_overflow] */
266
0
    return slen - (p - packet);
267
0
  }
268
1.05k
  p += slen;
269
1.05k
  FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of questions", slen);
270
271
1.05k
  slen = decode_record(ctx, out, attr_dns_rr, p, end, packet_ctx, packet + 6);
272
1.05k
  if (slen < 0) {
273
2
    fr_strerror_printf("Failed decoding RRs - %s", fr_strerror());
274
    /* coverity[return_overflow] */
275
2
    return slen - (p - packet);
276
2
  }
277
1.05k
  p += slen;
278
1.05k
  FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of RRs", slen);
279
280
1.05k
  slen = decode_record(ctx, out, attr_dns_ns, p, end, packet_ctx, packet + 8);
281
1.05k
  if (slen < 0) {
282
8
    fr_strerror_printf("Failed decoding NS - %s", fr_strerror());
283
    /* coverity[return_overflow] */
284
8
    return slen - (p - packet);
285
8
  }
286
1.04k
  p += slen;
287
1.04k
  FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of NS", slen);
288
289
1.04k
  slen = decode_record(ctx, out, attr_dns_ar, p, end, packet_ctx, packet + 10);
290
1.04k
  if (slen < 0) {
291
8
    fr_strerror_printf("Failed decoding additional records - %s", fr_strerror());
292
    /* coverity[return_overflow] */
293
8
    return slen - (p - packet);
294
8
  }
295
1.03k
  FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of additional records", slen);
296
297
//  p += slen;
298
299
1.03k
  return packet_len;
300
1.04k
}
301
302
/** Decode DNS RR
303
 *
304
 * @param[in] ctx context to alloc new attributes in.
305
 * @param[in,out] out   Where to write the decoded options.
306
 * @param[in] parent    to lookup attributes in.
307
 * @param[in] data    to parse.
308
 * @param[in] data_len    of data to parse.
309
 * @param[in] decode_ctx  Unused.
310
 */
311
static ssize_t decode_rr(TALLOC_CTX *ctx, fr_pair_list_t *out, UNUSED fr_dict_attr_t const *parent,
312
       uint8_t const *data, size_t data_len, void *decode_ctx)
313
0
{
314
0
  ssize_t     slen;
315
0
  fr_dns_ctx_t  *packet_ctx = (fr_dns_ctx_t *) decode_ctx;
316
317
0
  FR_PROTO_TRACE("%s called to parse %zu byte(s)", __FUNCTION__, data_len);
318
319
0
  if (data_len == 0) return 0;
320
321
  /*
322
   *  This function is only used for testing, so update decode_ctx
323
   */
324
0
  packet_ctx->packet = data;
325
0
  packet_ctx->packet_len = data_len;
326
327
0
  FR_PROTO_HEX_DUMP(data, data_len, NULL);
328
329
  /*
330
   *  There should be at least room for the RR header
331
   */
332
0
  if (data_len < 9) {
333
0
    fr_strerror_printf("%s: Insufficient data", __FUNCTION__);
334
0
    return -1;
335
0
  }
336
337
0
  slen = fr_struct_from_network(ctx, out, attr_dns_rr, data, data_len,
338
0
              decode_ctx, decode_value_trampoline, decode_tlv_trampoline);
339
0
  if (slen < 0) return slen;
340
341
0
  FR_PROTO_TRACE("decoding option complete, returning %zd byte(s)", slen);
342
0
  return slen;
343
0
}
344
345
/*
346
 *  Test points
347
 */
348
static int decode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict,
349
         UNUSED fr_dict_attr_t const *root_da)
350
2.90k
{
351
2.90k
  fr_dns_ctx_t *test_ctx;
352
353
2.90k
  test_ctx = talloc_zero(ctx, fr_dns_ctx_t);
354
355
2.90k
  test_ctx->tmp_ctx = talloc(test_ctx, uint8_t);
356
2.90k
  *out = test_ctx;
357
358
2.90k
  return 0;
359
2.90k
}
360
361
fr_table_num_ordered_t fr_dns_reason_fail_table[] = {
362
  { L("none"),            FR_DNS_DECODE_FAIL_NONE   },
363
  { L("unknown opcode"),          FR_DNS_DECODE_FAIL_UNKNOWN_OPCODE },
364
  { L("packet is smaller than DNS header"),   FR_DNS_DECODE_FAIL_MIN_LENGTH_PACKET  },
365
  { L("packet is larger than 65535"),     FR_DNS_DECODE_FAIL_MAX_LENGTH_PACKET  },
366
  { L("expected query / answer, got answer / query"), FR_DNS_DECODE_FAIL_UNEXPECTED   },
367
  { L("no 'questions' in query packet"),      FR_DNS_DECODE_FAIL_NO_QUESTIONS },
368
  { L("unexpected answers in query packet"),    FR_DNS_DECODE_FAIL_ANSWERS_IN_QUESTION  },
369
  { L("unexpected NS records in query packet"),   FR_DNS_DECODE_FAIL_NS_IN_QUESTION },
370
  { L("invalid label for resource record"),         FR_DNS_DECODE_FAIL_INVALID_RR_LABEL },
371
  { L("missing resource record header"),      FR_DNS_DECODE_FAIL_MISSING_RR_HEADER  },
372
  { L("missing resource record length field"),    FR_DNS_DECODE_FAIL_MISSING_RR_LEN },
373
  { L("resource record length field is zero"),    FR_DNS_DECODE_FAIL_ZERO_RR_LEN  },
374
  { L("resource record length overflows the packet"), FR_DNS_DECODE_FAIL_RR_OVERFLOWS_PACKET  },
375
  { L("more resource records than indicated in header"),  FR_DNS_DECODE_FAIL_TOO_MANY_RRS },
376
  { L("fewer resource records than indicated in header"), FR_DNS_DECODE_FAIL_TOO_FEW_RRS    },
377
  { L("pointer overflows packet"),      FR_DNS_DECODE_FAIL_POINTER_OVERFLOWS_PACKET     },
378
  { L("pointer points to packet header"),     FR_DNS_DECODE_FAIL_POINTER_TO_HEADER    },
379
  { L("pointer does not point to a label"),         FR_DNS_DECODE_FAIL_POINTER_TO_NON_LABEL         },
380
  { L("pointer creates a loop"),        FR_DNS_DECODE_FAIL_POINTER_LOOPS    },
381
  { L("invalid pointer"),         FR_DNS_DECODE_FAIL_INVALID_POINTER    },
382
  { L("label overflows the packet"),      FR_DNS_DECODE_FAIL_LABEL_OVERFLOWS_PACKET       },
383
  { L("too many characters in label"),      FR_DNS_DECODE_FAIL_LABEL_TOO_LONG   },
384
  { L("query record header is missing"),      FR_DNS_DECODE_FAIL_MISSING_QD_HEADER    },
385
  { L("missing TLV header in OPT RR"),      FR_DNS_DECODE_FAIL_MISSING_TLV_HEADER   },
386
  { L("TLV overflows enclosing RR"),      FR_DNS_DECODE_FAIL_TLV_OVERFLOWS_RR   },
387
  { L("TC bit indicates truncation"),     FR_DNS_DECODE_FAIL_TRUNCATED    },
388
};
389
size_t fr_dns_reason_fail_table_len = NUM_ELEMENTS(fr_dns_reason_fail_table);
390
391
static ssize_t decode_proto(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *data, size_t data_len, void *proto_ctx)
392
1.38k
{
393
1.38k
  fr_dns_ctx_t *packet_ctx = proto_ctx;
394
1.38k
  fr_dns_decode_fail_t reason;
395
396
1.38k
  if (data_len > 65535) return -1; /* packet is too big */
397
398
  /*
399
   *  Allow queries or answers
400
   */
401
1.37k
  if (!fr_dns_packet_ok(data, data_len, true, &reason)) {
402
1.05k
    if (reason != FR_DNS_DECODE_FAIL_UNEXPECTED) goto fail;
403
404
930
    if (!fr_dns_packet_ok(data, data_len, false, &reason)) {
405
317
    fail:
406
317
      fr_strerror_printf("DNS packet malformed - %s",
407
317
             fr_table_str_by_value(fr_dns_reason_fail_table, reason, "<INVALID>"));
408
317
      return -1;
409
197
    }
410
930
  }
411
412
1.05k
  packet_ctx->packet = data;
413
1.05k
  packet_ctx->packet_len = data_len;
414
1.05k
  packet_ctx->lb = fr_dns_labels_get(data, data_len, true);
415
1.05k
  fr_assert(packet_ctx->lb != NULL);
416
417
1.05k
  return fr_dns_decode(ctx, out, data, data_len,  packet_ctx);
418
1.37k
}
419
420
/*
421
 *  Test points
422
 */
423
extern fr_test_point_pair_decode_t dns_tp_decode_pair;
424
fr_test_point_pair_decode_t dns_tp_decode_pair = {
425
  .test_ctx = decode_test_ctx,
426
  .func   = decode_rr
427
};
428
429
extern fr_test_point_proto_decode_t dns_tp_decode_proto;
430
fr_test_point_proto_decode_t dns_tp_decode_proto = {
431
  .test_ctx = decode_test_ctx,
432
  .func   = decode_proto
433
};