Coverage Report

Created: 2026-10-18 06:56

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.54k
#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.05k
{
89
2.05k
  uint16_t len;
90
91
8.44k
  while (p < end) {
92
6.40k
    if ((p + 4) > end) {
93
10
      DECODE_FAIL(MISSING_TLV_HEADER);
94
10
      return false;
95
10
    }
96
97
6.39k
    len = fr_nbo_to_uint16(p + 2);
98
6.39k
    if ((p + 4 + len) > end) {
99
2
      DECODE_FAIL(TLV_OVERFLOWS_RR);
100
2
      return false;
101
2
    }
102
103
6.39k
    p += 4 + len;
104
6.39k
  }
105
106
2.03k
  return true;
107
2.05k
}
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.54k
{
111
2.54k
  uint8_t const *p, *end;
112
2.54k
  int qdcount, count, expected;
113
2.54k
  uint8_t opcode;
114
115
2.54k
  if (packet_len <= DNS_HDR_LEN) {
116
9
    DECODE_FAIL(MIN_LENGTH_PACKET);
117
9
    return false;
118
9
  }
119
120
2.53k
  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.53k
  if (((packet[2] & 0x80) == 0) != query) {
129
868
    DECODE_FAIL(UNEXPECTED);
130
868
    return false;
131
868
  }
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.66k
  if ((packet[2] & 0x02) != 0) {
139
4
    DECODE_FAIL(TRUNCATED);
140
4
    return false;
141
4
  }
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
4
    DECODE_FAIL(UNKNOWN_OPCODE);
147
4
    return false;
148
4
  }
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.65k
  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
287
    if (!qdcount) {
170
2
      DECODE_FAIL(NO_QUESTIONS);
171
2
      return false;
172
2
    }
173
174
285
    if (fr_nbo_to_uint16(packet + 6) != 0) {
175
22
      DECODE_FAIL(ANSWERS_IN_QUESTION);
176
22
      return false;
177
22
    }
178
179
263
    if (fr_nbo_to_uint16(packet + 8) != 0) {
180
18
      DECODE_FAIL(NS_IN_QUESTION);
181
18
      return false;
182
18
    }
183
    // additional records can exist!
184
185
1.37k
  } else {
186
    /*
187
     *  Replies _usually_ copy the query.  But not
188
     *  always And replies can have zero or more answers.
189
     */
190
1.37k
  }
191
192
1.61k
  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.61k
  count = 0;
194
195
1.61k
  p = packet + DNS_HDR_LEN;
196
1.61k
  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.61k
  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.61k
  if (p + (qdcount * 5) > end) {
216
30
    DECODE_FAIL(TOO_MANY_RRS);
217
30
    return false;
218
30
  }
219
1.58k
  p += (qdcount * 5);
220
221
1.58k
  if ((p + ((expected - qdcount) * (1 + 8 + 2))) > end) {
222
38
    DECODE_FAIL(TOO_MANY_RRS);
223
38
    return false;
224
38
  }
225
226
  /*
227
   *  The counts are at least vaguely OK, let's walk over the whole packet.
228
   */
229
1.54k
  p = packet + DNS_HDR_LEN;
230
231
  /*
232
   *  Check that lengths of RRs match.
233
   */
234
52.1k
  while (p < end) {
235
50.8k
    uint16_t len = 0;
236
50.8k
    uint8_t const *start = p;
237
50.8k
    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
58.1k
    while (p < end) {
249
      /*
250
       *  0x00 is "end of label"
251
       */
252
58.1k
      if (!*p) {
253
50.2k
        p++;
254
50.2k
        break;
255
50.2k
      }
256
257
      /*
258
       *  2 octets of 14-bit pointer, which must
259
       *  be at least somewhat sane.
260
       */
261
7.92k
      if (*p >= 0xc0) {
262
523
        ptrdiff_t offset;
263
264
523
        if ((p + 2) > end) {
265
4
          DECODE_FAIL(POINTER_OVERFLOWS_PACKET);
266
4
          return false;
267
4
        }
268
269
519
        offset = p[1];
270
519
        offset += ((*p & ~0xc0) << 8);
271
272
        /*
273
         *  Can't point to the header.
274
         */
275
519
        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
515
        if (offset >= (start - packet)) {
284
26
          DECODE_FAIL(POINTER_LOOPS);
285
26
          return false;
286
26
        }
287
288
489
        if (!fr_dns_marker[offset]) {
289
3
          DECODE_FAIL(POINTER_TO_NON_LABEL);
290
3
          return false;
291
3
        }
292
293
        /*
294
         *  A compressed pointer is the end of the current label.
295
         */
296
486
        p += 2;
297
486
        break;
298
489
      }
299
300
      /*
301
       *  0b01 and 0b10 are forbidden
302
       */
303
7.40k
      if (*p > 63) {
304
13
        DECODE_FAIL(INVALID_POINTER);
305
13
        return false;
306
13
      }
307
308
      /*
309
       *  It must be a length byte, which doesn't cause overflow.
310
       */
311
7.38k
      if ((p + *p + 1) > end) {
312
13
        DECODE_FAIL(LABEL_OVERFLOWS_PACKET);
313
13
        return false;
314
13
      }
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
50.7k
    if (qdcount) {
338
      /*
339
       *  qtype + qclass
340
       */
341
44.0k
      if ((p + 4) > end) {
342
5
        DECODE_FAIL(MISSING_QD_HEADER);
343
5
        return false;
344
5
      }
345
346
44.0k
      p += 4;
347
44.0k
      qdcount--;
348
44.0k
      goto next;
349
44.0k
    }
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
6.74k
    if ((p + 8) > end) {
359
14
      DECODE_FAIL(MISSING_RR_HEADER);
360
14
      return false;
361
14
    }
362
6.72k
    is_opt = (p[0] == 0) && (p[1] == 41);
363
6.72k
    p += 8;
364
365
    /*
366
     *  rr_len
367
     */
368
6.72k
    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
6.69k
    len = fr_nbo_to_uint16(p);
377
6.69k
    if (!is_opt && (len == 0)) {
378
5
      DECODE_FAIL(ZERO_RR_LEN);
379
5
      return false;
380
5
    }
381
382
6.68k
    p += 2;
383
6.68k
    if ((p + len) > end) {
384
27
      DECODE_FAIL(RR_OVERFLOWS_PACKET);
385
27
      return false;
386
27
    }
387
388
    /*
389
     *  Verify the TLVs, too.
390
     */
391
6.66k
    if (is_opt && !fr_dns_tlv_ok(p, p + len, reason)) {
392
12
      return false;
393
12
    }
394
395
6.64k
    p += len;
396
397
50.6k
next:
398
50.6k
    count++;
399
400
50.6k
    if (count > expected) {
401
2
      DECODE_FAIL(TOO_MANY_RRS);
402
2
      return false;
403
2
    }
404
50.6k
  }
405
406
1.38k
  if (count != expected) {
407
14
    DECODE_FAIL(TOO_FEW_RRS);
408
14
    return false;
409
14
  }
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.37k
  DECODE_FAIL(NONE);
417
1.37k
  return true;
418
1.38k
}
419
420
fr_dns_labels_t *fr_dns_labels_get(uint8_t const *packet, size_t packet_len, bool init_mark)
421
1.37k
{
422
1.37k
  fr_dns_labels_t *lb = &fr_dns_labels;
423
424
1.37k
  lb->max = 256;
425
1.37k
  lb->mark = fr_dns_marker;
426
1.37k
  lb->blocks = fr_dns_blocks;
427
428
1.37k
  lb->start = packet;
429
1.37k
  lb->end = packet + packet_len;
430
431
1.37k
  lb->num = 1;
432
1.37k
  lb->blocks[0].start = DNS_HDR_LEN;
433
1.37k
  lb->blocks[0].end = DNS_HDR_LEN;
434
435
1.37k
  if (init_mark) {
436
1.37k
    fr_assert(packet_len <= 65535);
437
1.37k
    memset(lb->mark, 0, packet_len);
438
1.37k
  }
439
440
1.37k
  return lb;
441
1.37k
}
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
};