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/dhcpv6/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: 046738f83460ed03b8d0c27582387190bf17c710 $
19
 *
20
 * @file protocols/dhcpv6/decode.c
21
 * @brief Functions to decode DHCP options.
22
 *
23
 * @author Arran Cudbard-Bell (a.cudbardb@freeradius.org)
24
 *
25
 * @copyright 2018 The FreeRADIUS server project
26
 * @copyright 2018 NetworkRADIUS SARL (legal@networkradius.com)
27
 */
28
#include <stdint.h>
29
#include <stddef.h>
30
31
#include <freeradius-devel/io/test_point.h>
32
#include <freeradius-devel/util/dns.h>
33
#include <freeradius-devel/util/proto.h>
34
#include <freeradius-devel/util/struct.h>
35
36
#include "dhcpv6.h"
37
#include "attrs.h"
38
39
static ssize_t decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out,
40
           fr_dict_attr_t const *parent,
41
           uint8_t const *data, size_t const data_len, void *decode_ctx);
42
43
static ssize_t decode_tlv_trampoline(TALLOC_CTX *ctx, fr_pair_list_t *out,
44
             fr_dict_attr_t const *parent,
45
             uint8_t const *data, size_t const data_len, void *decode_ctx)
46
691
{
47
691
  return fr_pair_tlvs_from_network(ctx, out, parent, data, data_len, decode_ctx, decode_option, NULL, true);
48
691
}
49
50
static ssize_t decode_value(TALLOC_CTX *ctx, fr_pair_list_t *out,
51
          fr_dict_attr_t const *parent,
52
          uint8_t const *data, size_t const data_len, void *decode_ctx);
53
54
/** Handle arrays of DNS labels for fr_struct_from_network()
55
 *
56
 */
57
static ssize_t decode_value_trampoline(TALLOC_CTX *ctx, fr_pair_list_t *out,
58
               fr_dict_attr_t const *parent,
59
               uint8_t const *data, size_t const data_len, void *decode_ctx)
60
165k
{
61
165k
  if ((parent->type == FR_TYPE_STRING) && fr_dhcpv6_flag_any_dns_label(parent)) {
62
49.1k
    return fr_pair_dns_labels_from_network(ctx, out, parent, data, data, data_len, NULL, false);
63
49.1k
  }
64
65
116k
  return decode_value(ctx, out, parent, data, data_len, decode_ctx);
66
165k
}
67
68
69
static ssize_t decode_value(TALLOC_CTX *ctx, fr_pair_list_t *out,
70
          fr_dict_attr_t const *parent,
71
          uint8_t const *data, size_t const data_len, void *decode_ctx)
72
20.3k
{
73
20.3k
  ssize_t     slen;
74
20.3k
  fr_pair_t   *vp = NULL;
75
20.3k
  fr_dict_attr_t const  *ref;
76
77
20.3k
  FR_PROTO_HEX_DUMP(data, data_len, "decode_value");
78
79
20.3k
  switch (parent->type) {
80
614
  case FR_TYPE_ATTR:
81
    /*
82
     *  Force the length of the data to be two,
83
     *  otherwise the "from network" call complains.
84
     *  Because we pass in the enumv as the _parent_
85
     *  and not the da.  The da is marked as "array",
86
     *  but the parent is not.
87
     */
88
614
    if (data_len < 2) goto raw;
89
90
565
    fr_assert(parent->parent->flags.is_root);
91
92
565
    vp = fr_pair_afrom_da(ctx, parent);
93
565
    if (!vp) return PAIR_DECODE_OOM;
94
565
    PAIR_ALLOCED(vp);
95
96
565
    slen = fr_value_box_from_network(vp, &vp->data, vp->vp_type, parent->parent,
97
565
             &FR_DBUFF_TMP(data, 2), 2, true);
98
565
    if (slen <= 0) {
99
0
      TALLOC_FREE(vp);
100
0
      goto raw;
101
0
    }
102
103
565
    vp->vp_tainted = true;
104
565
    fr_pair_append(out, vp);
105
565
    return 2;
106
107
  /*
108
   *  Address MAY be shorter than 16 bytes.
109
   */
110
606
  case FR_TYPE_IPV6_PREFIX:
111
606
    if (data_len == 0) {
112
5.74k
    raw:
113
5.74k
      return fr_pair_raw_from_network(ctx, out, parent, data, data_len);
114
115
7
    }
116
117
599
    vp = fr_pair_afrom_da(ctx, parent);
118
599
    if (!vp) return PAIR_DECODE_OOM;
119
599
    PAIR_ALLOCED(vp);
120
121
599
    slen = fr_value_box_ipaddr_from_network(&vp->data, parent->type, parent,
122
599
              data[0], data + 1, data_len - 1,
123
599
              (parent->parent->type == FR_TYPE_STRUCT), true);
124
599
    if (slen < 0) goto raw_free;
125
126
286
    slen++;   /* account for the prefix */
127
286
    break;
128
129
  /*
130
   *  A bool is encoded as an empty option if it's
131
   *  true.  A bool is omitted entirely if it's
132
   *  false.
133
   */
134
124
  case FR_TYPE_BOOL:
135
124
    if (data_len != 0) goto raw;
136
40
    vp = fr_pair_afrom_da(ctx, parent);
137
40
    if (!vp) return PAIR_DECODE_OOM;
138
40
    PAIR_ALLOCED(vp);
139
140
40
    vp->vp_bool = true;
141
40
    slen = 0;
142
40
    break;
143
144
  /*
145
   *  A standard 32bit integer, but unlike normal UNIX timestamps
146
   *  starts from the 1st of January 2000.
147
   *
148
   *  In the encoder we subtract 30 years to any values, so
149
   *  here we need to add that to the time here.
150
   */
151
546
  case FR_TYPE_DATE:
152
546
    vp = fr_pair_afrom_da(ctx, parent);
153
546
    if (!vp) return PAIR_DECODE_OOM;
154
546
    PAIR_ALLOCED(vp);
155
156
546
    slen = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
157
546
             &FR_DBUFF_TMP(data, data_len), data_len, true);
158
546
    if (slen < 0) {
159
181
      talloc_free(vp);
160
181
      goto raw;
161
181
    }
162
365
    vp->vp_date = fr_unix_time_add(vp->vp_date, fr_time_delta_from_sec(DHCPV6_DATE_OFFSET));
163
365
    break;
164
165
2.82k
  case FR_TYPE_STRUCT:
166
2.82k
    slen = fr_struct_from_network(ctx, out, parent, data, data_len,
167
2.82k
                decode_ctx, decode_value_trampoline, decode_tlv_trampoline);
168
2.82k
    if (slen < 0) goto raw;
169
170
1.55k
    if (parent->flags.array) return slen;
171
1.55k
    return data_len;
172
173
3.83k
  case FR_TYPE_GROUP:
174
3.83k
    vp = fr_pair_afrom_da(ctx, parent);
175
3.83k
    if (!vp) return PAIR_DECODE_OOM;
176
3.83k
    PAIR_ALLOCED(vp);
177
178
3.83k
    ref = fr_dict_attr_ref(parent);
179
3.83k
    if (ref && (ref->dict != dict_dhcpv6)) {
180
3.05k
      fr_dict_protocol_t const *proto;
181
182
3.05k
      proto = fr_dict_protocol(ref->dict);
183
3.05k
      fr_assert(proto != NULL);
184
185
3.05k
      if (!proto->decode) {
186
4.15k
      raw_free:
187
4.15k
        talloc_free(vp);
188
4.15k
        goto raw;
189
0
      }
190
191
3.05k
      slen = proto->decode(vp, &vp->vp_group, data, data_len);
192
3.05k
      if (slen < 0) goto raw_free;
193
194
596
      vp->vp_tainted = true;
195
196
789
    } else {
197
789
      if (!ref) ref = fr_dict_root(dict_dhcpv6);
198
199
      /*
200
       *  Child VPs go into the child group, not in the main parent list.  BUT, we start
201
       *  decoding attributes from the ref, and not from the group parent.
202
       */
203
789
      slen = fr_pair_tlvs_from_network(vp, &vp->vp_group, ref, data, data_len, decode_ctx, decode_option, NULL, false);
204
789
      if (slen < 0) goto raw_free;
205
789
    }
206
207
724
    fr_pair_append(out, vp);
208
724
    return data_len;
209
210
630
  case FR_TYPE_IPV6_ADDR:
211
630
    vp = fr_pair_afrom_da(ctx, parent);
212
630
    if (!vp) return PAIR_DECODE_OOM;
213
630
    PAIR_ALLOCED(vp);
214
215
630
    slen = fr_value_box_ipaddr_from_network(&vp->data, parent->type, parent,
216
630
              128, data, data_len,
217
630
              true, true);
218
630
    if (slen < 0) goto raw_free;
219
291
    break;
220
221
11.1k
  default:
222
11.1k
    vp = fr_pair_afrom_da(ctx, parent);
223
11.1k
    if (!vp) return PAIR_DECODE_OOM;
224
11.1k
    PAIR_ALLOCED(vp);
225
226
11.1k
    slen = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
227
11.1k
             &FR_DBUFF_TMP(data, data_len), data_len, true);
228
11.1k
    if (slen < 0) goto raw_free;
229
10.7k
    break;
230
20.3k
  }
231
232
  /*
233
   *  The input is larger than the decoded value, re-do it as a raw attribute.
234
   */
235
11.7k
  if (!parent->flags.array && ((size_t) slen < data_len)) {
236
0
    talloc_free(vp);
237
0
    goto raw;
238
0
  }
239
240
11.7k
  fr_assert(vp != NULL);
241
242
11.7k
  vp->vp_tainted = true;
243
11.7k
  fr_pair_append(out, vp);
244
245
11.7k
  if (parent->flags.array) return slen;
246
247
8.56k
  return data_len;
248
11.7k
}
249
250
251
static ssize_t decode_vsa(TALLOC_CTX *ctx, fr_pair_list_t *out,
252
        fr_dict_attr_t const *parent,
253
        uint8_t const *data, size_t const data_len, void *decode_ctx)
254
994
{
255
994
  uint32_t    pen;
256
994
  fr_dict_attr_t const  *da;
257
994
  fr_pair_t   *vp;
258
994
  fr_dhcpv6_decode_ctx_t  *packet_ctx = decode_ctx;
259
260
994
  FR_PROTO_HEX_DUMP(data, data_len, "decode_vsa");
261
262
994
  if (!fr_cond_assert_msg(parent->type == FR_TYPE_VSA,
263
994
        "%s: Internal sanity check failed, attribute \"%s\" is not of type 'vsa'",
264
994
        __FUNCTION__, parent->name)) return PAIR_DECODE_FATAL_ERROR;
265
266
  /*
267
   *  Enterprise code plus at least one option header
268
   */
269
994
  if (data_len < 8) return fr_pair_raw_from_network(ctx, out, parent, data, data_len);
270
271
811
  memcpy(&pen, data, sizeof(pen));
272
811
  pen = htonl(pen);
273
274
  /*
275
   *  Verify that the parent (which should be a VSA)
276
   *  contains a fake attribute representing the vendor.
277
   *
278
   *  If it doesn't then this vendor is unknown, but we know
279
   *  vendor attributes have a standard format, so we can
280
   *  decode the data anyway.
281
   */
282
811
  da = fr_dict_attr_child_by_num(parent, pen);
283
811
  if (!da) {
284
351
    fr_dict_attr_t *n;
285
286
351
    n = fr_dict_attr_unknown_vendor_afrom_num(packet_ctx->tmp_ctx, parent, pen);
287
351
    if (!n) return PAIR_DECODE_OOM;
288
351
    da = n;
289
351
  }
290
291
811
  FR_PROTO_TRACE("decode context %s -> %s", parent->name, da->name);
292
293
811
  vp = fr_pair_find_by_da(out, NULL, da);
294
811
  if (vp) {
295
161
    return fr_pair_tlvs_from_network(vp, &vp->vp_group, da, data + 4, data_len - 4, decode_ctx, decode_option, NULL, false);
296
161
  }
297
298
650
  return fr_pair_tlvs_from_network(ctx, out, da, data + 4, data_len - 4, decode_ctx, decode_option, NULL, true);
299
811
}
300
301
static ssize_t decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out,
302
            fr_dict_attr_t const *parent,
303
            uint8_t const *data, size_t const data_len, void *decode_ctx)
304
19.7k
{
305
19.7k
  unsigned int      option;
306
19.7k
  size_t      len;
307
19.7k
  ssize_t     slen;
308
19.7k
  fr_dict_attr_t const  *da;
309
19.7k
  fr_dhcpv6_decode_ctx_t  *packet_ctx = decode_ctx;
310
311
#ifdef STATIC_ANALYZER
312
  if (!packet_ctx || !packet_ctx->tmp_ctx) return PAIR_DECODE_FATAL_ERROR;
313
#endif
314
315
  /*
316
   *  Must have at least an option header.
317
   */
318
19.7k
  if (data_len < 4) {
319
1.10k
    fr_strerror_printf("%s: Insufficient data", __FUNCTION__);
320
1.10k
    return -(data_len);
321
1.10k
  }
322
323
18.6k
  option = DHCPV6_GET_OPTION_NUM(data);
324
18.6k
  len = DHCPV6_GET_OPTION_LEN(data);
325
18.6k
  if (len > (data_len - 4)) {
326
3.51k
    fr_strerror_printf("%s: Option overflows input.  "
327
3.51k
           "Optional length must be less than %zu bytes, got %zu bytes",
328
3.51k
           __FUNCTION__, data_len - 4, len);
329
3.51k
    return PAIR_DECODE_FATAL_ERROR;
330
3.51k
  }
331
332
15.0k
  da = fr_dict_attr_child_by_num(parent, option);
333
15.0k
  if (!da) {
334
4.21k
    da = fr_dict_attr_unknown_raw_afrom_num(packet_ctx->tmp_ctx, parent, option);
335
4.21k
    if (!da) return PAIR_DECODE_FATAL_ERROR;
336
4.21k
  }
337
15.0k
  FR_PROTO_TRACE("decode context changed %s -> %s",da->parent->name, da->name);
338
339
  /*
340
   *  Relay messages are weird, and contain complete DHCPv6
341
   *  packets, copied verbatim from the DHCPv6 client.
342
   */
343
15.0k
  if (da == attr_relay_message) {
344
1.38k
    fr_pair_t *vp;
345
346
1.38k
    vp = fr_pair_afrom_da(ctx, attr_relay_message);
347
1.38k
    if (!vp) return PAIR_DECODE_FATAL_ERROR;
348
1.38k
    PAIR_ALLOCED(vp);
349
350
1.38k
    slen = fr_dhcpv6_decode(vp, &vp->vp_group, data + 4, len);
351
1.38k
    if (slen < 0) {
352
885
      talloc_free(vp);
353
1.81k
    raw:
354
1.81k
      slen = fr_pair_raw_from_network(ctx, out, da, data + 4, len);
355
1.81k
      if (slen < 0) return slen;
356
1.81k
      return 4 + slen;
357
1.81k
    }
358
359
495
    fr_pair_append(out, vp);
360
361
13.7k
  } else if ((da->type == FR_TYPE_STRING) && fr_dhcpv6_flag_any_dns_label(da)) {
362
457
    slen = fr_pair_dns_labels_from_network(ctx, out, da, data + 4, data + 4, len, NULL, true);
363
457
    if (slen < 0) return slen;
364
365
13.2k
  } else if (da->flags.array) {
366
419
    slen = fr_pair_array_from_network(ctx, out, da, data + 4, len, decode_ctx, decode_value);
367
368
12.8k
  } else if (da->type == FR_TYPE_VSA) {
369
994
    bool append = false;
370
994
    fr_pair_t *vp;
371
372
994
    vp = fr_pair_find_by_da(out, NULL, da);
373
994
    if (!vp) {
374
678
      vp = fr_pair_afrom_da(ctx, da);
375
678
      if (!vp) return PAIR_DECODE_FATAL_ERROR;
376
678
      PAIR_ALLOCED(vp);
377
378
678
      append = true;
379
678
    }
380
381
994
    slen = decode_vsa(vp, &vp->vp_group, da, data + 4, len, decode_ctx);
382
994
    if (append) {
383
678
      if (slen < 0) {
384
491
        TALLOC_FREE(vp);
385
491
      } else {
386
187
        fr_pair_append(out, vp);
387
187
      }
388
678
    }
389
390
11.8k
  } else if (da->type == FR_TYPE_TLV) {
391
190
    slen = fr_pair_tlvs_from_network(ctx, out, da, data + 4, len, decode_ctx, decode_option, NULL, true);
392
393
11.6k
  } else {
394
11.6k
    slen = decode_value(ctx, out, da, data + 4, len, decode_ctx);
395
11.6k
  }
396
397
14.2k
  if (slen < 0) goto raw;
398
399
13.2k
  return len + 4;
400
14.2k
}
401
402
403
/** Create a "normal" fr_pair_t from the given data
404
 *
405
 *    0                   1                   2                   3
406
 *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
407
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
408
 *   |          option-code          |           option-len          |
409
 *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
410
 */
411
ssize_t fr_dhcpv6_decode_option(TALLOC_CTX *ctx, fr_pair_list_t *out,
412
        uint8_t const *data, size_t data_len, void *decode_ctx)
413
16.6k
{
414
16.6k
  FR_PROTO_HEX_DUMP(data, data_len, "fr_dhcpv6_decode_pair");
415
416
  /*
417
   *  The API changes, so we just bounce directly to the
418
   *  decode_option() function.
419
   *
420
   *  All options including VSAs in DHCPv6 MUST follow the
421
   *  standard format.
422
   */
423
16.6k
  return decode_option(ctx, out, fr_dict_root(dict_dhcpv6), data, data_len, decode_ctx);
424
16.6k
}
425
426
ssize_t fr_dhcpv6_decode_foreign(TALLOC_CTX *ctx, fr_pair_list_t *out,
427
         uint8_t const *data, size_t data_len)
428
3.06k
{
429
3.06k
  ssize_t slen;
430
3.06k
  uint8_t const *attr, *end;
431
432
3.06k
  fr_dhcpv6_decode_ctx_t decode_ctx = {};
433
434
3.06k
  fr_assert(dict_dhcpv6 != NULL);
435
436
3.06k
  decode_ctx.tmp_ctx = talloc(ctx, uint8_t);
437
438
3.06k
  attr = data;
439
3.06k
  end = data + data_len;
440
441
15.2k
  while (attr < end) {
442
14.7k
    slen = fr_dhcpv6_decode_option(ctx, out, attr, (end - attr), &decode_ctx);
443
14.7k
    if (slen < 0) {
444
2.53k
      talloc_free(decode_ctx.tmp_ctx);
445
2.53k
      return slen;
446
2.53k
    }
447
448
    /*
449
     *  If slen is larger than the room in the packet,
450
     *  all kinds of bad things happen.
451
     */
452
12.1k
     if (!fr_cond_assert(slen <= (end - attr))) {
453
0
      talloc_free(decode_ctx.tmp_ctx);
454
0
       return -slen - (attr - data);
455
0
     }
456
457
12.1k
    attr += slen;
458
12.1k
    talloc_free_children(decode_ctx.tmp_ctx);
459
12.1k
  }
460
461
3.06k
  talloc_free(decode_ctx.tmp_ctx);
462
531
  return data_len;
463
3.06k
}
464
465
static int decode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict,
466
         UNUSED fr_dict_attr_t const *root_da)
467
2
{
468
2
  fr_dhcpv6_decode_ctx_t  *test_ctx;
469
470
2
  test_ctx = talloc_zero(ctx, fr_dhcpv6_decode_ctx_t);
471
2
  if (!test_ctx) return -1;
472
473
2
  test_ctx->tmp_ctx = talloc(test_ctx, uint8_t);
474
475
2
  *out = test_ctx;
476
477
2
  return 0;
478
2
}
479
480
static ssize_t fr_dhcpv6_decode_proto(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *data, size_t data_len, UNUSED void *proto_ctx)
481
0
{
482
0
  size_t packet_len = data_len;
483
//  fr_dhcpv6_decode_ctx_t  *test_ctx = talloc_get_type_abort(proto_ctx, fr_dhcpv6_decode_ctx_t);
484
485
0
  if (!fr_dhcpv6_ok(data, packet_len, 200)) return -1;
486
487
0
  return fr_dhcpv6_decode(ctx, out, data, packet_len);
488
0
}
489
490
491
static ssize_t decode_pair(TALLOC_CTX *ctx, fr_pair_list_t *out, NDEBUG_UNUSED fr_dict_attr_t const *parent,
492
         uint8_t const *data, size_t data_len, void *decode_ctx)
493
0
{
494
0
  fr_assert(parent == fr_dict_root(dict_dhcpv6));
495
496
0
  return decode_option(ctx, out, fr_dict_root(dict_dhcpv6), data, data_len, decode_ctx);
497
0
}
498
499
/*
500
 *  Test points
501
 */
502
extern fr_test_point_pair_decode_t dhcpv6_tp_decode_pair;
503
fr_test_point_pair_decode_t dhcpv6_tp_decode_pair = {
504
  .test_ctx = decode_test_ctx,
505
  .func   = decode_pair,
506
};
507
508
extern fr_test_point_proto_decode_t dhcpv6_tp_decode_proto;
509
fr_test_point_proto_decode_t dhcpv6_tp_decode_proto = {
510
  .test_ctx = decode_test_ctx,
511
  .func   = fr_dhcpv6_decode_proto
512
};