Coverage Report

Created: 2026-11-12 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/protocols/dns/base.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: 9c2f827a54c3e52ee73d2fe242f9f440f897a826 $
19
 *
20
 * @file protocols/dns/base.c
21
 * @brief Functions to send/receive dns packets.
22
 *
23
 * @copyright 2008 The FreeRADIUS server project
24
 * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
25
 */
26
RCSID("$Id: 9c2f827a54c3e52ee73d2fe242f9f440f897a826 $")
27
28
#include "dns.h"
29
#include "attrs.h"
30
#include <freeradius-devel/protocol/dns/rfc1034.h>
31
32
static uint32_t instance_count = 0;
33
static bool instantiated = false;
34
35
typedef struct {
36
  uint16_t  code;
37
  uint16_t  length;
38
} dns_option_t;
39
40
fr_dict_t const *dict_dns;
41
42
static _Thread_local fr_dns_labels_t  fr_dns_labels;
43
static _Thread_local fr_dns_block_t fr_dns_blocks[256];
44
static _Thread_local uint8_t    fr_dns_marker[65536];
45
46
extern fr_dict_autoload_t dns_dict[];
47
fr_dict_autoload_t dns_dict[] = {
48
  { .out = &dict_dns, .proto = "dns" },
49
  DICT_AUTOLOAD_TERMINATOR
50
};
51
52
fr_dict_attr_t const *attr_dns_packet;
53
fr_dict_attr_t const *attr_dns_question;
54
fr_dict_attr_t const *attr_dns_rr;
55
fr_dict_attr_t const *attr_dns_ns;
56
fr_dict_attr_t const *attr_dns_ar;
57
58
extern fr_dict_attr_autoload_t dns_dict_attr[];
59
fr_dict_attr_autoload_t dns_dict_attr[] = {
60
  { .out = &attr_dns_packet, .name = "Header", .type = FR_TYPE_STRUCT, .dict = &dict_dns },
61
  { .out = &attr_dns_question, .name = "Question", .type = FR_TYPE_STRUCT, .dict = &dict_dns },
62
  { .out = &attr_dns_rr, .name = "Resource-Record", .type = FR_TYPE_STRUCT, .dict = &dict_dns },
63
  { .out = &attr_dns_ns, .name = "Name-Server", .type = FR_TYPE_STRUCT, .dict = &dict_dns },
64
  { .out = &attr_dns_ar, .name = "Additional-Record", .type = FR_TYPE_STRUCT, .dict = &dict_dns },
65
  DICT_AUTOLOAD_TERMINATOR
66
};
67
68
 char const *fr_dns_packet_names[FR_DNS_CODE_MAX] = {
69
  [FR_DNS_QUERY] = "Query",
70
  [FR_DNS_INVERSE_QUERY] = "Inverse-Query",
71
  [FR_DNS_STATUS] = "Status",
72
  [FR_DNS_NOTIFY] = "Notify",
73
  [FR_DNS_UPDATE] = "Update",
74
  [FR_DNS_STATEFUL_OPERATION] = "Stateful-Operation",
75
};
76
77
FR_DICT_ATTR_FLAG_FUNC(fr_dns_attr_flags_t, dns_label)
78
FR_DICT_ATTR_FLAG_FUNC(fr_dns_attr_flags_t, dns_label_uncompressed)
79
80
static fr_dict_flag_parser_t const dns_flags[] = {
81
  { L("dns_label"),   { .func = dict_flag_dns_label } },
82
  { L("dns_label_uncompressed"),  { .func = dict_flag_dns_label_uncompressed } }
83
};
84
85
2.60k
#define DECODE_FAIL(_reason) if (reason) *reason = FR_DNS_DECODE_FAIL_ ## _reason
86
87
static bool fr_dns_tlv_ok(uint8_t const *p, uint8_t const *end, fr_dns_decode_fail_t *reason)
88
2.21k
{
89
2.21k
  uint16_t len;
90
91
8.96k
  while (p < end) {
92
6.76k
    if ((p + 4) > end) {
93
9
      DECODE_FAIL(MISSING_TLV_HEADER);
94
9
      return false;
95
9
    }
96
97
6.75k
    len = fr_nbo_to_uint16(p + 2);
98
6.75k
    if ((p + 4 + len) > end) {
99
2
      DECODE_FAIL(TLV_OVERFLOWS_RR);
100
2
      return false;
101
2
    }
102
103
6.74k
    p += 4 + len;
104
6.74k
  }
105
106
2.20k
  return true;
107
2.21k
}
108
109
bool fr_dns_packet_ok(uint8_t const *packet, size_t packet_len, bool query, fr_dns_decode_fail_t *reason)
110
2.60k
{
111
2.60k
  uint8_t const *p, *end;
112
2.60k
  int qdcount, count, expected;
113
2.60k
  uint8_t opcode;
114
115
2.60k
  if (packet_len <= DNS_HDR_LEN) {
116
9
    DECODE_FAIL(MIN_LENGTH_PACKET);
117
9
    return false;
118
9
  }
119
120
2.59k
  if (packet_len > 65535) {
121
0
    DECODE_FAIL(MAX_LENGTH_PACKET);
122
0
    return false;
123
0
  }
124
125
  /*
126
   *  query=0, response=1
127
   */
128
2.59k
  if (((packet[2] & 0x80) == 0) != query) {
129
920
    DECODE_FAIL(UNEXPECTED);
130
920
    return false;
131
920
  }
132
133
  /*
134
   *  @todo - the truncation rules mean that the various counts below are wrong, and the caller
135
   *  should retry over TCP.  This is really an indication to us, that we need to fully implement
136
   *  the truncation checks.
137
   */
138
1.67k
  if ((packet[2] & 0x02) != 0) {
139
7
    DECODE_FAIL(TRUNCATED);
140
7
    return false;
141
7
  }
142
1.66k
  qdcount = fr_nbo_to_uint16(packet + 4);
143
144
1.66k
  opcode = (packet[2] >> 3) & 0x0f;
145
1.66k
  if ((opcode >= FR_DNS_CODE_MAX) || !fr_dns_packet_names[opcode]) {
146
3
    DECODE_FAIL(UNKNOWN_OPCODE);
147
3
    return false;
148
3
  }
149
150
  /*
151
   *  RFC 2136 (DNS update) defines the four "count" fields to have different meanings:
152
   *
153
   *  ZOCOUNT The number of RRs in the Zone Section.
154
   *  PRCOUNT The number of RRs in the Prerequisite Section.
155
   *  UPCOUNT The number of RRs in the Update Section.
156
   *  ADCOUNT The number of RRs in the Additional Data Section.
157
   *
158
   *  @todo - we can likely do more validation checks on input packets.
159
   */
160
1.66k
  if (query && (opcode != FR_OPCODE_VALUE_UPDATE)) {
161
    /*
162
     *  There should be at least one query, and no
163
     *  replies in the query.
164
     *
165
     *  @todo - unless it's an IQUERY, in which case
166
     *  there should be no questions, and at least one
167
     *  answer.
168
     */
169
284
    if (!qdcount) {
170
2
      DECODE_FAIL(NO_QUESTIONS);
171
2
      return false;
172
2
    }
173
174
282
    if (fr_nbo_to_uint16(packet + 6) != 0) {
175
21
      DECODE_FAIL(ANSWERS_IN_QUESTION);
176
21
      return false;
177
21
    }
178
179
261
    if (fr_nbo_to_uint16(packet + 8) != 0) {
180
16
      DECODE_FAIL(NS_IN_QUESTION);
181
16
      return false;
182
16
    }
183
    // additional records can exist!
184
185
1.38k
  } else {
186
    /*
187
     *  Replies _usually_ copy the query.  But not
188
     *  always And replies can have zero or more answers.
189
     */
190
1.38k
  }
191
192
1.62k
  expected = fr_nbo_to_uint16(packet + 4) + fr_nbo_to_uint16(packet + 6) + fr_nbo_to_uint16(packet + 8) + fr_nbo_to_uint16(packet + 10);
193
1.62k
  count = 0;
194
195
1.62k
  p = packet + DNS_HDR_LEN;
196
1.62k
  end = packet + packet_len;
197
198
  /*
199
   *  We track valid label targets in a simple array (up to
200
   *  2^14 bits of compressed pointer).
201
   *
202
   *  Note that some labels might appear in the RRDATA
203
   *  field, and we don't verify those here.  However, this
204
   *  function will verify the most common packets.  As a
205
   *  result, any issues with overflow, etc. are more
206
   *  difficult to exploit.
207
   */
208
1.62k
  memset(fr_dns_marker, 0, packet_len < (1 << 14) ? packet_len : (1 << 14));
209
210
  /*
211
   *  Check for wildly fake packets, by making rough
212
   *  estimations.  This way we don't actually have to walk
213
   *  the packet.
214
   */
215
1.62k
  if (p + (qdcount * 5) > end) {
216
29
    DECODE_FAIL(TOO_MANY_RRS);
217
29
    return false;
218
29
  }
219
1.59k
  p += (qdcount * 5);
220
221
1.59k
  if ((p + ((expected - qdcount) * (1 + 8 + 2))) > end) {
222
37
    DECODE_FAIL(TOO_MANY_RRS);
223
37
    return false;
224
37
  }
225
226
  /*
227
   *  The counts are at least vaguely OK, let's walk over the whole packet.
228
   */
229
1.56k
  p = packet + DNS_HDR_LEN;
230
231
  /*
232
   *  Check that lengths of RRs match.
233
   */
234
60.1k
  while (p < end) {
235
58.7k
    uint16_t len = 0;
236
58.7k
    uint8_t const *start = p;
237
58.7k
    bool is_opt = false;
238
239
    /*
240
     *  Simple DNS label decoder
241
     *
242
     *  @todo - move this to src/lib/util/dns.c,
243
     *  perhaps as fr_dns_label_verify(), and then
244
     *  have it also return a pointer to the next
245
     *  label?  fr_dns_label_uncompressed_length()
246
     *  does similar but slightly different things.
247
     */
248
66.1k
    while (p < end) {
249
      /*
250
       *  0x00 is "end of label"
251
       */
252
66.1k
      if (!*p) {
253
58.2k
        p++;
254
58.2k
        break;
255
58.2k
      }
256
257
      /*
258
       *  2 octets of 14-bit pointer, which must
259
       *  be at least somewhat sane.
260
       */
261
7.88k
      if (*p >= 0xc0) {
262
490
        ptrdiff_t offset;
263
264
490
        if ((p + 2) > end) {
265
4
          DECODE_FAIL(POINTER_OVERFLOWS_PACKET);
266
4
          return false;
267
4
        }
268
269
486
        offset = p[1];
270
486
        offset += ((*p & ~0xc0) << 8);
271
272
        /*
273
         *  Can't point to the header.
274
         */
275
486
        if (offset < 12) {
276
4
          DECODE_FAIL(POINTER_TO_HEADER);
277
4
          return false;
278
4
        }
279
280
        /*
281
         *  Can't point to the current label.
282
         */
283
482
        if (offset >= (start - packet)) {
284
25
          DECODE_FAIL(POINTER_LOOPS);
285
25
          return false;
286
25
        }
287
288
457
        if (!fr_dns_marker[offset]) {
289
4
          DECODE_FAIL(POINTER_TO_NON_LABEL);
290
4
          return false;
291
4
        }
292
293
        /*
294
         *  A compressed pointer is the end of the current label.
295
         */
296
453
        p += 2;
297
453
        break;
298
457
      }
299
300
      /*
301
       *  0b01 and 0b10 are forbidden
302
       */
303
7.39k
      if (*p > 63) {
304
16
        DECODE_FAIL(INVALID_POINTER);
305
16
        return false;
306
16
      }
307
308
      /*
309
       *  It must be a length byte, which doesn't cause overflow.
310
       */
311
7.38k
      if ((p + *p + 1) > end) {
312
12
        DECODE_FAIL(LABEL_OVERFLOWS_PACKET);
313
12
        return false;
314
12
      }
315
316
      /*
317
       *  Total length of labels can't be too high.
318
       */
319
7.37k
      len += *p;
320
7.37k
      if (len >= 256) {
321
1
        DECODE_FAIL(LABEL_TOO_LONG);
322
1
        return false;
323
1
      }
324
325
      /*
326
       *  Remember that this is where we have a
327
       *  label.
328
       */
329
7.37k
      fr_dns_marker[p - packet] = 1;
330
331
      /*
332
       *  Go to the next label.
333
       */
334
7.37k
      p += *p + 1;
335
7.37k
    }
336
337
58.6k
    if (qdcount) {
338
      /*
339
       *  qtype + qclass
340
       */
341
51.4k
      if ((p + 4) > end) {
342
5
        DECODE_FAIL(MISSING_QD_HEADER);
343
5
        return false;
344
5
      }
345
346
51.4k
      p += 4;
347
51.4k
      qdcount--;
348
51.4k
      goto next;
349
51.4k
    }
350
351
    /*
352
     *  type (2) + class (2) + TTL (4)
353
     *
354
     *  These are overloaded for the OPT RR
355
     *  and possibly others, but the basic
356
     *  idea is the same.
357
     */
358
7.24k
    if ((p + 8) > end) {
359
14
      DECODE_FAIL(MISSING_RR_HEADER);
360
14
      return false;
361
14
    }
362
7.23k
    is_opt = (p[0] == 0) && (p[1] == 41);
363
7.23k
    p += 8;
364
365
    /*
366
     *  rr_len
367
     */
368
7.23k
    if ((p + 2) > end) {
369
35
      DECODE_FAIL(MISSING_RR_LEN);
370
35
      return false;
371
35
    }
372
373
    /*
374
     *  @todo - RFC2136 allows RDLENGTH=0 for many cases.
375
     */
376
7.19k
    len = fr_nbo_to_uint16(p);
377
7.19k
    if (!is_opt && (len == 0)) {
378
4
      DECODE_FAIL(ZERO_RR_LEN);
379
4
      return false;
380
4
    }
381
382
7.19k
    p += 2;
383
7.19k
    if ((p + len) > end) {
384
29
      DECODE_FAIL(RR_OVERFLOWS_PACKET);
385
29
      return false;
386
29
    }
387
388
    /*
389
     *  Verify the TLVs, too.
390
     */
391
7.16k
    if (is_opt && !fr_dns_tlv_ok(p, p + len, reason)) {
392
11
      return false;
393
11
    }
394
395
7.15k
    p += len;
396
397
58.5k
next:
398
58.5k
    count++;
399
400
58.5k
    if (count > expected) {
401
1
      DECODE_FAIL(TOO_MANY_RRS);
402
1
      return false;
403
1
    }
404
58.5k
  }
405
406
1.39k
  if (count != expected) {
407
13
    DECODE_FAIL(TOO_FEW_RRS);
408
13
    return false;
409
13
  }
410
411
  /*
412
   *  @todo - save fr_dns_marker[] data, so that it can be used by fr_dns_labels_get().  This helps
413
   *  to reduce redundant work.
414
   */
415
416
1.38k
  DECODE_FAIL(NONE);
417
1.38k
  return true;
418
1.39k
}
419
420
fr_dns_labels_t *fr_dns_labels_get(uint8_t const *packet, size_t packet_len, bool init_mark)
421
1.38k
{
422
1.38k
  fr_dns_labels_t *lb = &fr_dns_labels;
423
424
1.38k
  lb->max = 256;
425
1.38k
  lb->mark = fr_dns_marker;
426
1.38k
  lb->blocks = fr_dns_blocks;
427
428
1.38k
  lb->start = packet;
429
1.38k
  lb->end = packet + packet_len;
430
431
1.38k
  lb->num = 1;
432
1.38k
  lb->blocks[0].start = DNS_HDR_LEN;
433
1.38k
  lb->blocks[0].end = DNS_HDR_LEN;
434
435
1.38k
  if (init_mark) {
436
1.38k
    fr_assert(packet_len <= 65535);
437
1.38k
    memset(lb->mark, 0, packet_len);
438
1.38k
  }
439
440
1.38k
  return lb;
441
1.38k
}
442
443
/** Resolve/cache attributes in the DNS dictionary
444
 *
445
 * @return
446
 *  - 0 on success.
447
 *  - -1 on failure.
448
 */
449
int fr_dns_global_init(void)
450
4
{
451
4
  if (instance_count > 0) {
452
2
    instance_count++;
453
2
    return 0;
454
2
  }
455
456
2
  instance_count++;
457
458
2
  if (fr_dict_autoload(dns_dict) < 0) {
459
0
  fail:
460
0
    instance_count--;
461
0
    return -1;
462
0
  }
463
2
  if (fr_dict_attr_autoload(dns_dict_attr) < 0) {
464
0
    fr_dict_autofree(dns_dict);
465
0
    goto fail;
466
0
  }
467
468
2
  instantiated = true;
469
2
  return 0;
470
2
}
471
472
void fr_dns_global_free(void)
473
4
{
474
4
  if (!instantiated) return;
475
476
4
  fr_assert(instance_count > 0);
477
478
4
  if (--instance_count > 0) return;
479
480
2
  fr_dict_autofree(dns_dict);
481
2
  instantiated = false;
482
2
}
483
484
static bool attr_valid(fr_dict_attr_t *da)
485
174
{
486
174
  if (da->flags.array) {
487
0
    fr_strerror_const("The 'array' flag cannot be used with DNS");
488
0
    return false;
489
0
  }
490
491
174
  if (da->type == FR_TYPE_ATTR) {
492
0
    fr_strerror_const("The 'attribute' data type cannot be used with DNS");
493
0
    return false;
494
0
  }
495
496
174
  if (fr_dns_flag_dns_label_any(da)) {
497
18
    if (da->type != FR_TYPE_STRING) {
498
0
      fr_strerror_const("The 'dns_label' flag can only be used with attributes of type 'string'");
499
0
      return false;
500
0
    }
501
18
    da->flags.is_known_width = true;  /* Lie so we don't trip up the main validation checks */
502
18
  }
503
504
174
  switch (da->type) {
505
6
  case FR_TYPE_IP:
506
6
    da->flags.is_known_width = true;
507
6
    break;
508
509
168
  default:
510
168
    break;
511
174
  }
512
513
174
  return true;
514
174
}
515
516
fr_dict_protocol_t libfreeradius_dns_dict_protocol = {
517
  .name = "dns",
518
  .default_type_size = 2,
519
  .default_type_length = 2,
520
  .attr = {
521
    .flags = {
522
      .table = dns_flags,
523
      .table_len = NUM_ELEMENTS(dns_flags),
524
      .len = sizeof(fr_dns_attr_flags_t)
525
    },
526
    .valid = attr_valid
527
  },
528
529
  .init = fr_dns_global_init,
530
  .free = fr_dns_global_free,
531
};