Coverage Report

Created: 2025-12-14 06:39

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: 3ad88d6f595caac176e5d8f76c83cf24a3cb2e45 $
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
1.39k
{
48
1.39k
  return fr_pair_tlvs_from_network(ctx, out, parent, data, data_len, decode_ctx, decode_option, NULL, true);
49
1.39k
}
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
139k
{
56
139k
  if ((parent->type == FR_TYPE_STRING) && fr_dns_flag_dns_label(parent)) {
57
36.5k
    fr_dns_ctx_t    *packet_ctx = decode_ctx;
58
59
36.5k
    return fr_pair_dns_labels_from_network(ctx, out, parent, packet_ctx->packet, data, data_len, packet_ctx->lb, false);
60
36.5k
  }
61
62
103k
  return decode_value(ctx, out, parent, data, data_len, decode_ctx);
63
139k
}
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
118k
{
70
118k
  ssize_t     slen;
71
118k
  fr_pair_t   *vp;
72
118k
  uint8_t     prefix_len;
73
74
118k
  FR_PROTO_HEX_DUMP(data, data_len, "decode_value");
75
76
118k
  switch (parent->type) {
77
  /*
78
   *  Address MAY be shorter than 16 bytes.
79
   */
80
0
  case FR_TYPE_IPV6_PREFIX:
81
0
    if ((data_len == 0) || (data_len > (1 + sizeof(vp->vp_ipv6addr)))) {
82
5.34k
    raw:
83
5.34k
      return fr_pair_raw_from_network(ctx, out, parent, data, data_len);
84
85
0
    }
86
87
    /*
88
     *  Structs used fixed length fields
89
     */
90
0
    if (parent->parent->type == FR_TYPE_STRUCT) {
91
0
      if (data_len != (1 + sizeof(vp->vp_ipv6addr))) goto raw;
92
93
0
      vp = fr_pair_afrom_da(ctx, parent);
94
0
      if (!vp) return PAIR_DECODE_OOM;
95
96
0
      vp->vp_ip.af = AF_INET6;
97
0
      vp->vp_ip.prefix = data[0];
98
0
      memcpy(&vp->vp_ipv6addr, data + 1, data_len - 1);
99
0
      break;
100
0
    }
101
102
    /*
103
     *  No address, the prefix length MUST be zero.
104
     */
105
0
    if (data_len == 1) {
106
0
      if (data[0] != 0) goto raw;
107
108
0
      vp = fr_pair_afrom_da(ctx, parent);
109
0
      if (!vp) return PAIR_DECODE_OOM;
110
111
0
      vp->vp_ip.af = AF_INET6;
112
0
      break;
113
0
    }
114
115
0
    prefix_len = data[0];
116
117
    /*
118
     *  If we have a /64 prefix but only 7 bytes of
119
     *  address, that's an error.
120
     */
121
0
    if (fr_bytes_from_bits(prefix_len) > (data_len - 1)) goto raw;
122
123
0
    vp = fr_pair_afrom_da(ctx, parent);
124
0
    if (!vp) return PAIR_DECODE_OOM;
125
126
0
    vp->vp_ip.af = AF_INET6;
127
0
    vp->vp_ip.prefix = prefix_len;
128
0
    memcpy(&vp->vp_ipv6addr, data + 1, data_len - 1);
129
0
    break;
130
131
  /*
132
   *  A bool is encoded as an empty option if it's
133
   *  true.  A bool is omitted entirely if it's
134
   *  false.
135
   */
136
662
  case FR_TYPE_BOOL:
137
662
    if (data_len != 0) goto raw;
138
473
    vp = fr_pair_afrom_da(ctx, parent);
139
473
    if (!vp) return PAIR_DECODE_OOM;
140
473
    vp->vp_bool = true;
141
473
    break;
142
143
7.99k
  case FR_TYPE_STRUCT:
144
7.99k
    slen = fr_struct_from_network(ctx, out, parent, data, data_len,
145
7.99k
                decode_ctx, decode_value_trampoline, NULL);
146
7.99k
    if (slen < 0) return slen;
147
7.94k
    return data_len;
148
149
0
  case FR_TYPE_GROUP:
150
0
    return PAIR_DECODE_FATAL_ERROR; /* not supported */
151
152
109k
  default:
153
109k
    vp = fr_pair_afrom_da(ctx, parent);
154
109k
    if (!vp) return PAIR_DECODE_OOM;
155
156
109k
    if (fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
157
109k
                &FR_DBUFF_TMP(data, data_len), data_len, true) < 0) {
158
5.15k
      FR_PROTO_TRACE("failed decoding?");
159
5.15k
      talloc_free(vp);
160
5.15k
      goto raw;
161
5.15k
    }
162
104k
    break;
163
118k
  }
164
165
104k
  vp->vp_tainted = true;
166
104k
  fr_pair_append(out, vp);
167
104k
  return data_len;
168
118k
}
169
170
171
21.4k
#define DNS_GET_OPTION_NUM(_x)  fr_nbo_to_uint16(_x)
172
21.4k
#define DNS_GET_OPTION_LEN(_x)  fr_nbo_to_uint16((_x) + 2)
173
174
static ssize_t decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out,
175
            fr_dict_attr_t const *parent,
176
            uint8_t const *data, size_t const data_len, void *decode_ctx)
177
21.4k
{
178
21.4k
  unsigned int      option;
179
21.4k
  size_t      len;
180
21.4k
  ssize_t     slen;
181
21.4k
  fr_dict_attr_t const  *da;
182
21.4k
  fr_dns_ctx_t    *packet_ctx = decode_ctx;
183
184
#ifdef STATIC_ANALYZER
185
  if (!packet_ctx || !packet_ctx->tmp_ctx) return PAIR_DECODE_FATAL_ERROR;
186
#endif
187
188
  /*
189
   *  Must have at least an option header.
190
   */
191
21.4k
  if (data_len < 4) {
192
15
    fr_strerror_printf("%s: Insufficient data", __FUNCTION__);
193
15
    return -(data_len);
194
15
  }
195
196
21.4k
  option = DNS_GET_OPTION_NUM(data);
197
21.4k
  len = DNS_GET_OPTION_LEN(data);
198
21.4k
  if (len > (data_len - 4)) {
199
50
    fr_strerror_printf("%s: Option overflows input.  "
200
50
           "Optional length must be less than %zu bytes, got %zu bytes",
201
50
           __FUNCTION__, data_len - 4, len);
202
50
    return PAIR_DECODE_FATAL_ERROR;
203
50
  }
204
205
21.3k
  FR_PROTO_HEX_DUMP(data, len + 4, "decode_option");
206
207
21.3k
  da = fr_dict_attr_child_by_num(parent, option);
208
21.3k
  if (!da) {
209
7.60k
    da = fr_dict_attr_unknown_raw_afrom_num(packet_ctx->tmp_ctx, parent, option);
210
7.60k
    if (!da) return PAIR_DECODE_FATAL_ERROR;
211
7.60k
  }
212
21.3k
  FR_PROTO_TRACE("decode context changed %s -> %s",da->parent->name, da->name);
213
214
21.3k
  if ((da->type == FR_TYPE_STRING) && fr_dns_flag_dns_label(da)) {
215
0
    slen = fr_pair_dns_labels_from_network(ctx, out, da, packet_ctx->packet, data + 4, len, packet_ctx->lb, true);
216
217
21.3k
  } else if (da->flags.array) {
218
0
    slen = fr_pair_array_from_network(ctx, out, da, data + 4, len, decode_ctx, decode_value);
219
220
21.3k
  } else {
221
21.3k
    slen = decode_value(ctx, out, da, data + 4, len, decode_ctx);
222
21.3k
  }
223
21.3k
  fr_dict_attr_unknown_free(&da);
224
225
21.3k
  if (slen < 0) return slen;
226
227
21.3k
  return len + 4;
228
21.3k
}
229
230
static ssize_t decode_record(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *attr,
231
           uint8_t const *rr, uint8_t const *end,
232
           fr_dns_ctx_t *packet_ctx, uint8_t const *counter)
233
5.31k
{
234
5.31k
  unsigned int i, count;
235
5.31k
  uint8_t const *p = rr;
236
237
  /*
238
   *  The header has a count of how many records we need to decode.
239
   */
240
5.31k
  count = fr_nbo_to_uint16(counter);
241
5.31k
  FR_PROTO_TRACE("Decoding %u of %s", count, attr->name);
242
243
  /* coverity[tainted_data] */
244
36.4k
  for (i = 0; (i < count) && (p < end); i++) {
245
31.1k
    ssize_t slen;
246
247
31.1k
    FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - %s %u/%u", attr->name, i, count);
248
249
31.1k
    slen = fr_struct_from_network(ctx, out, attr, p, end - p,
250
31.1k
                packet_ctx, decode_value_trampoline, decode_tlv_trampoline);
251
31.1k
    if (slen < 0) return slen;
252
31.1k
    if (!slen) break;
253
254
31.1k
    fr_assert(slen <= (end - p));
255
256
31.1k
    p += slen;
257
31.1k
  }
258
259
5.31k
  return p - rr;
260
5.31k
}
261
262
/** Decode a DNS packet
263
 *
264
 */
265
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)
266
1.33k
{
267
1.33k
  ssize_t     slen;
268
1.33k
  uint8_t const   *p, *end;
269
270
1.33k
  if (packet_len < DNS_HDR_LEN) return 0;
271
272
  /*
273
   *  @todo - synthesize Packet-Type from the various fields.
274
   */
275
276
1.33k
  FR_PROTO_HEX_DUMP(packet, packet_len, "fr_dns_decode");
277
278
  /*
279
   *  Decode the header.
280
   */
281
1.33k
  slen = fr_struct_from_network(ctx, out, attr_dns_packet, packet, DNS_HDR_LEN,
282
1.33k
              packet_ctx, decode_value_trampoline, NULL); /* no TLVs in the header */
283
1.33k
  if (slen < 0) {
284
0
    fr_strerror_printf("Failed decoding DNS header - %s", fr_strerror());
285
0
    return slen;
286
0
  }
287
1.33k
  fr_assert(slen == DNS_HDR_LEN);
288
289
1.33k
  p = packet + DNS_HDR_LEN;
290
1.33k
  end = packet + packet_len;
291
1.33k
  FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after header");
292
293
1.33k
  slen = decode_record(ctx, out, attr_dns_question, p, end, packet_ctx, packet + 4);
294
1.33k
  if (slen < 0) {
295
0
    fr_strerror_printf("Failed decoding questions - %s", fr_strerror());
296
0
    return slen;
297
0
  }
298
1.33k
  p += slen;
299
1.33k
  FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of questions", slen);
300
301
1.33k
  slen = decode_record(ctx, out, attr_dns_rr, p, end, packet_ctx, packet + 6);
302
1.33k
  if (slen < 0) {
303
1
    fr_strerror_printf("Failed decoding RRs - %s", fr_strerror());
304
1
    return slen - (p - packet);
305
1
  }
306
1.32k
  p += slen;
307
1.32k
  FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of RRs", slen);
308
309
1.32k
  slen = decode_record(ctx, out, attr_dns_ns, p, end, packet_ctx, packet + 8);
310
1.32k
  if (slen < 0) {
311
2
    fr_strerror_printf("Failed decoding NS - %s", fr_strerror());
312
2
    return slen - (p - packet);
313
2
  }
314
1.32k
  p += slen;
315
1.32k
  FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of NS", slen);
316
317
1.32k
  slen = decode_record(ctx, out, attr_dns_ar, p, end, packet_ctx, packet + 10);
318
1.32k
  if (slen < 0) {
319
2
    fr_strerror_printf("Failed decoding additional records - %s", fr_strerror());
320
2
    return slen - (p - packet);
321
2
  }
322
1.32k
  FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of additional records", slen);
323
324
//  p += slen;
325
326
1.32k
  return packet_len;
327
1.32k
}
328
329
/** Decode DNS RR
330
 *
331
 * @param[in] ctx context to alloc new attributes in.
332
 * @param[in,out] out   Where to write the decoded options.
333
 * @param[in] parent    to lookup attributes in.
334
 * @param[in] data    to parse.
335
 * @param[in] data_len    of data to parse.
336
 * @param[in] decode_ctx  Unused.
337
 */
338
static ssize_t decode_rr(TALLOC_CTX *ctx, fr_pair_list_t *out, UNUSED fr_dict_attr_t const *parent,
339
       uint8_t const *data, size_t data_len, void *decode_ctx)
340
0
{
341
0
  ssize_t     slen;
342
0
  fr_dns_ctx_t  *packet_ctx = (fr_dns_ctx_t *) decode_ctx;
343
344
0
  FR_PROTO_TRACE("%s called to parse %zu byte(s)", __FUNCTION__, data_len);
345
346
0
  if (data_len == 0) return 0;
347
348
  /*
349
   *  This function is only used for testing, so update decode_ctx
350
   */
351
0
  packet_ctx->packet = data;
352
0
  packet_ctx->packet_len = data_len;
353
354
0
  FR_PROTO_HEX_DUMP(data, data_len, NULL);
355
356
  /*
357
   *  There should be at least room for the RR header
358
   */
359
0
  if (data_len < 9) {
360
0
    fr_strerror_printf("%s: Insufficient data", __FUNCTION__);
361
0
    return -1;
362
0
  }
363
364
0
  slen = fr_struct_from_network(ctx, out, attr_dns_rr, data, data_len,
365
0
              decode_ctx, decode_value_trampoline, decode_tlv_trampoline);
366
0
  if (slen < 0) return slen;
367
368
0
  FR_PROTO_TRACE("decoding option complete, returning %zd byte(s)", slen);
369
0
  return slen;
370
0
}
371
372
/*
373
 *  Test points
374
 */
375
static int decode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict,
376
         UNUSED fr_dict_attr_t const *root_da)
377
3.24k
{
378
3.24k
  fr_dns_ctx_t *test_ctx;
379
380
3.24k
  test_ctx = talloc_zero(ctx, fr_dns_ctx_t);
381
382
3.24k
  test_ctx->tmp_ctx = talloc(test_ctx, uint8_t);
383
3.24k
  *out = test_ctx;
384
385
3.24k
  return 0;
386
3.24k
}
387
388
fr_table_num_ordered_t fr_dns_reason_fail_table[] = {
389
  { L("none"),            FR_DNS_DECODE_FAIL_NONE   },
390
  { L("packet is smaller than DNS header"),   FR_DNS_DECODE_FAIL_MIN_LENGTH_PACKET  },
391
  { L("packet is larger than 65535"),     FR_DNS_DECODE_FAIL_MAX_LENGTH_PACKET  },
392
  { L("expected query / answer, got answer / query"), FR_DNS_DECODE_FAIL_UNEXPECTED   },
393
  { L("no 'questions' in query packet"),      FR_DNS_DECODE_FAIL_NO_QUESTIONS },
394
  { L("unexprected answers in query packet"),   FR_DNS_DECODE_FAIL_ANSWERS_IN_QUESTION  },
395
  { L("unexpected NS records in query packet"),   FR_DNS_DECODE_FAIL_NS_IN_QUESTION },
396
  { L("invalid label for resource record"),         FR_DNS_DECODE_FAIL_INVALID_RR_LABEL },
397
  { L("missing resource record header"),      FR_DNS_DECODE_FAIL_MISSING_RR_HEADER  },
398
  { L("missing resource record length field"),    FR_DNS_DECODE_FAIL_MISSING_RR_LEN },
399
  { L("resource record length field is zero"),    FR_DNS_DECODE_FAIL_ZERO_RR_LEN  },
400
  { L("resource record length overflows the packet"), FR_DNS_DECODE_FAIL_RR_OVERFLOWS_PACKET  },
401
  { L("more resource records than indicated in header"),  FR_DNS_DECODE_FAIL_TOO_MANY_RRS },
402
  { L("fewer resource records than indicated in header"), FR_DNS_DECODE_FAIL_TOO_FEW_RRS    },
403
  { L("pointer overflows packet"),      FR_DNS_DECODE_FAIL_POINTER_OVERFLOWS_PACKET     },
404
  { L("pointer points to packet header"),     FR_DNS_DECODE_FAIL_POINTER_TO_HEADER    },
405
  { L("pointer does not point to a label"),         FR_DNS_DECODE_FAIL_POINTER_TO_NON_LABEL         },
406
  { L("pointer creates a loop"),        FR_DNS_DECODE_FAIL_POINTER_LOOPS    },
407
  { L("invalid pointer"),         FR_DNS_DECODE_FAIL_INVALID_POINTER    },
408
  { L("label overflows the packet"),      FR_DNS_DECODE_FAIL_LABEL_OVERFLOWS_PACKET       },
409
  { L("too many characters in label"),      FR_DNS_DECODE_FAIL_LABEL_TOO_LONG   },
410
  { L("query record header is missing"),      FR_DNS_DECODE_FAIL_MISSING_QD_HEADER    },
411
  { L("missing TLV header in OPT RR"),      FR_DNS_DECODE_FAIL_MISSING_TLV_HEADER   },
412
  { L("TLV overflows enclosing RR"),      FR_DNS_DECODE_FAIL_TLV_OVERFLOWS_RR   },
413
};
414
size_t fr_dns_reason_fail_table_len = NUM_ELEMENTS(fr_dns_reason_fail_table);
415
416
static ssize_t decode_proto(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *data, size_t data_len, void *proto_ctx)
417
1.61k
{
418
1.61k
  fr_dns_ctx_t *packet_ctx = proto_ctx;
419
1.61k
  fr_dns_decode_fail_t reason;
420
421
1.61k
  if (data_len > 65535) return -1; /* packet is too big */
422
423
  /*
424
   *  Allow queries or answers
425
   */
426
1.60k
  if (!fr_dns_packet_ok(data, data_len, true, &reason)) {
427
1.48k
    if (reason != FR_DNS_DECODE_FAIL_UNEXPECTED) goto fail;
428
429
1.41k
    if (!fr_dns_packet_ok(data, data_len, false, &reason)) {
430
274
    fail:
431
274
      fr_strerror_printf("DNS packet malformed - %s",
432
274
             fr_table_str_by_value(fr_dns_reason_fail_table, reason, "<INVALID>"));
433
274
      return -1;
434
202
    }
435
1.41k
  }
436
437
1.33k
  packet_ctx->packet = data;
438
1.33k
  packet_ctx->packet_len = data_len;
439
1.33k
  packet_ctx->lb = fr_dns_labels_get(data, data_len, true);
440
1.33k
  fr_assert(packet_ctx->lb != NULL);
441
442
1.33k
  return fr_dns_decode(ctx, out, data, data_len,  packet_ctx);
443
1.60k
}
444
445
/*
446
 *  Test points
447
 */
448
extern fr_test_point_pair_decode_t dns_tp_decode_pair;
449
fr_test_point_pair_decode_t dns_tp_decode_pair = {
450
  .test_ctx = decode_test_ctx,
451
  .func   = decode_rr
452
};
453
454
extern fr_test_point_proto_decode_t dns_tp_decode_proto;
455
fr_test_point_proto_decode_t dns_tp_decode_proto = {
456
  .test_ctx = decode_test_ctx,
457
  .func   = decode_proto
458
};