Coverage Report

Created: 2026-06-30 07:16

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
1.23k
{
48
1.23k
  return fr_pair_tlvs_from_network(ctx, out, parent, data, data_len, decode_ctx, decode_option, NULL, true);
49
1.23k
}
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
169k
{
56
169k
  if ((parent->type == FR_TYPE_STRING) && fr_dns_flag_dns_label(parent)) {
57
43.7k
    fr_dns_ctx_t    *packet_ctx = decode_ctx;
58
59
43.7k
    return fr_pair_dns_labels_from_network(ctx, out, parent, packet_ctx->packet, data, data_len, packet_ctx->lb, false);
60
43.7k
  }
61
62
125k
  return decode_value(ctx, out, parent, data, data_len, decode_ctx);
63
169k
}
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
143k
{
70
143k
  ssize_t     slen;
71
143k
  fr_pair_t   *vp;
72
73
143k
  FR_PROTO_HEX_DUMP(data, data_len, "decode_value");
74
75
143k
  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
7.77k
    raw:
82
7.77k
      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
783
  case FR_TYPE_BOOL:
106
783
    if (data_len != 0) goto raw;
107
482
    vp = fr_pair_afrom_da(ctx, parent);
108
482
    if (!vp) return PAIR_DECODE_OOM;
109
482
    PAIR_ALLOCED(vp);
110
111
482
    vp->vp_bool = true;
112
482
    break;
113
114
9.79k
  case FR_TYPE_STRUCT:
115
9.79k
    slen = fr_struct_from_network(ctx, out, parent, data, data_len,
116
9.79k
                decode_ctx, decode_value_trampoline, NULL);
117
9.79k
    if (slen < 0) return slen;
118
9.72k
    return data_len;
119
120
0
  case FR_TYPE_GROUP:
121
0
    return PAIR_DECODE_FATAL_ERROR; /* not supported */
122
123
133k
  default:
124
133k
    vp = fr_pair_afrom_da(ctx, parent);
125
133k
    if (!vp) return PAIR_DECODE_OOM;
126
133k
    PAIR_ALLOCED(vp);
127
128
133k
    if (fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
129
133k
                &FR_DBUFF_TMP(data, data_len), data_len, true) < 0) {
130
7.47k
      FR_PROTO_TRACE("failed decoding?");
131
7.47k
      talloc_free(vp);
132
7.47k
      goto raw;
133
7.47k
    }
134
125k
    break;
135
143k
  }
136
137
126k
  vp->vp_tainted = true;
138
126k
  fr_pair_append(out, vp);
139
126k
  return data_len;
140
143k
}
141
142
143
31.5k
#define DNS_GET_OPTION_NUM(_x)  fr_nbo_to_uint16(_x)
144
31.5k
#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
31.5k
{
150
31.5k
  unsigned int      option;
151
31.5k
  size_t      len;
152
31.5k
  ssize_t     slen;
153
31.5k
  fr_dict_attr_t const  *da;
154
31.5k
  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
31.5k
  if (data_len < 4) {
164
10
    fr_strerror_printf("%s: Insufficient data", __FUNCTION__);
165
10
    return -(data_len);
166
10
  }
167
168
31.5k
  option = DNS_GET_OPTION_NUM(data);
169
31.5k
  len = DNS_GET_OPTION_LEN(data);
170
31.5k
  if (len > (data_len - 4)) {
171
59
    fr_strerror_printf("%s: Option overflows input.  "
172
59
           "Optional length must be less than %zu bytes, got %zu bytes",
173
59
           __FUNCTION__, data_len - 4, len);
174
59
    return PAIR_DECODE_FATAL_ERROR;
175
59
  }
176
177
31.5k
  FR_PROTO_HEX_DUMP(data, len + 4, "decode_option");
178
179
31.5k
  da = fr_dict_attr_child_by_num(parent, option);
180
31.5k
  if (!da) {
181
13.3k
    da = fr_dict_attr_unknown_raw_afrom_num(packet_ctx->tmp_ctx, parent, option);
182
13.3k
    if (!da) return PAIR_DECODE_FATAL_ERROR;
183
13.3k
  }
184
31.5k
  FR_PROTO_TRACE("decode context changed %s -> %s",da->parent->name, da->name);
185
186
31.5k
  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
31.5k
  } else {
190
31.5k
    slen = decode_value(ctx, out, da, data + 4, len, decode_ctx);
191
31.5k
  }
192
31.5k
  fr_dict_attr_unknown_free(&da);
193
194
31.5k
  if (slen < 0) return slen;
195
196
31.4k
  return len + 4;
197
31.5k
}
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
5.14k
{
203
5.14k
  unsigned int i, count;
204
5.14k
  uint8_t const *p = rr;
205
206
  /*
207
   *  The header has a count of how many records we need to decode.
208
   */
209
5.14k
  count = fr_nbo_to_uint16(counter);
210
5.14k
  FR_PROTO_TRACE("Decoding %u of %s", count, attr->name);
211
212
  /* coverity[tainted_data] */
213
43.7k
  for (i = 0; (i < count) && (p < end); i++) {
214
38.6k
    ssize_t slen;
215
216
38.6k
    FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - %s %u/%u", attr->name, i, count);
217
218
38.6k
    slen = fr_struct_from_network(ctx, out, attr, p, end - p,
219
38.6k
                packet_ctx, decode_value_trampoline, decode_tlv_trampoline);
220
38.6k
    if (slen < 0) return slen;
221
38.6k
    if (!slen) break;
222
223
38.6k
    fr_assert(slen <= (end - p));
224
225
38.6k
    p += slen;
226
38.6k
  }
227
228
5.12k
  return p - rr;
229
5.14k
}
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.28k
{
236
1.28k
  ssize_t     slen;
237
1.28k
  uint8_t const   *p, *end;
238
239
1.28k
  if (packet_len < DNS_HDR_LEN) return 0;
240
241
  /*
242
   *  @todo - synthesize Packet-Type from the various fields.
243
   */
244
245
1.28k
  FR_PROTO_HEX_DUMP(packet, packet_len, "fr_dns_decode");
246
247
  /*
248
   *  Decode the header.
249
   */
250
1.28k
  slen = fr_struct_from_network(ctx, out, attr_dns_packet, packet, DNS_HDR_LEN,
251
1.28k
              packet_ctx, decode_value_trampoline, NULL); /* no TLVs in the header */
252
1.28k
  if (slen < 0) {
253
0
    fr_strerror_printf("Failed decoding DNS header - %s", fr_strerror());
254
0
    return slen;
255
0
  }
256
1.28k
  fr_assert(slen == DNS_HDR_LEN);
257
258
1.28k
  p = packet + DNS_HDR_LEN;
259
1.28k
  end = packet + packet_len;
260
1.28k
  FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after header");
261
262
1.28k
  slen = decode_record(ctx, out, attr_dns_question, p, end, packet_ctx, packet + 4);
263
1.28k
  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.28k
  p += slen;
269
1.28k
  FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of questions", slen);
270
271
1.28k
  slen = decode_record(ctx, out, attr_dns_rr, p, end, packet_ctx, packet + 6);
272
1.28k
  if (slen < 0) {
273
3
    fr_strerror_printf("Failed decoding RRs - %s", fr_strerror());
274
    /* coverity[return_overflow] */
275
3
    return slen - (p - packet);
276
3
  }
277
1.28k
  p += slen;
278
1.28k
  FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of RRs", slen);
279
280
1.28k
  slen = decode_record(ctx, out, attr_dns_ns, p, end, packet_ctx, packet + 8);
281
1.28k
  if (slen < 0) {
282
7
    fr_strerror_printf("Failed decoding NS - %s", fr_strerror());
283
    /* coverity[return_overflow] */
284
7
    return slen - (p - packet);
285
7
  }
286
1.27k
  p += slen;
287
1.27k
  FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of NS", slen);
288
289
1.27k
  slen = decode_record(ctx, out, attr_dns_ar, p, end, packet_ctx, packet + 10);
290
1.27k
  if (slen < 0) {
291
11
    fr_strerror_printf("Failed decoding additional records - %s", fr_strerror());
292
    /* coverity[return_overflow] */
293
11
    return slen - (p - packet);
294
11
  }
295
1.26k
  FR_PROTO_HEX_DUMP(p, end - p, "fr_dns_decode - after %zd bytes of additional records", slen);
296
297
//  p += slen;
298
299
1.26k
  return packet_len;
300
1.27k
}
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
3.20k
{
351
3.20k
  fr_dns_ctx_t *test_ctx;
352
353
3.20k
  test_ctx = talloc_zero(ctx, fr_dns_ctx_t);
354
355
3.20k
  test_ctx->tmp_ctx = talloc(test_ctx, uint8_t);
356
3.20k
  *out = test_ctx;
357
358
3.20k
  return 0;
359
3.20k
}
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.62k
{
393
1.62k
  fr_dns_ctx_t *packet_ctx = proto_ctx;
394
1.62k
  fr_dns_decode_fail_t reason;
395
396
1.62k
  if (data_len > 65535) return -1; /* packet is too big */
397
398
  /*
399
   *  Allow queries or answers
400
   */
401
1.62k
  if (!fr_dns_packet_ok(data, data_len, true, &reason)) {
402
1.08k
    if (reason != FR_DNS_DECODE_FAIL_UNEXPECTED) goto fail;
403
404
918
    if (!fr_dns_packet_ok(data, data_len, false, &reason)) {
405
333
    fail:
406
333
      fr_strerror_printf("DNS packet malformed - %s",
407
333
             fr_table_str_by_value(fr_dns_reason_fail_table, reason, "<INVALID>"));
408
333
      return -1;
409
169
    }
410
918
  }
411
412
1.28k
  packet_ctx->packet = data;
413
1.28k
  packet_ctx->packet_len = data_len;
414
1.28k
  packet_ctx->lb = fr_dns_labels_get(data, data_len, true);
415
1.28k
  fr_assert(packet_ctx->lb != NULL);
416
417
1.28k
  return fr_dns_decode(ctx, out, data, data_len,  packet_ctx);
418
1.62k
}
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
};