Coverage Report

Created: 2026-08-08 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/protocols/internal/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: b7b7d42e7c834a156c837ce7f86c5b293eae6f37 $
19
 *
20
 * Because what we need is yet *ANOTHER* serialisation scheme.
21
 *
22
 * @file protocols/internal/decode.c
23
 * @brief Functions to decode data in our internal structure.
24
 *
25
 * @copyright 2020 The FreeRADIUS server project
26
 * @copyright 2020 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
27
 */
28
29
#include <freeradius-devel/internal/internal.h>
30
#include <freeradius-devel/io/pair.h>
31
#include <freeradius-devel/io/test_point.h>
32
#include <freeradius-devel/util/proto.h>
33
34
static ssize_t internal_decode_pair(TALLOC_CTX *ctx, fr_pair_list_t *head, fr_dict_attr_t const *parent_da,
35
            fr_dbuff_t *dbuff, void *decode_ctx);
36
37
/** Decodes the value of an attribute, potentially producing a pair (added to the cursor)
38
 *
39
 */
40
static ssize_t internal_decode_pair_value(TALLOC_CTX *ctx, fr_pair_list_t *head, fr_dict_attr_t const *parent_da,
41
            fr_dbuff_t *dbuff,
42
            bool tainted, UNUSED void *decode_ctx)
43
1.53M
{
44
1.53M
  fr_pair_t *vp;
45
1.53M
  ssize_t   slen;
46
1.53M
  fr_dbuff_t  work_dbuff = FR_DBUFF(dbuff);
47
48
1.53M
  vp = fr_pair_afrom_da(ctx, parent_da);
49
1.53M
  if (!vp) return PAIR_DECODE_OOM;
50
1.53M
  PAIR_ALLOCED(vp);
51
52
  /*
53
   *  Zero length is fine here
54
   */
55
1.53M
  slen = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da,
56
1.53M
           &work_dbuff, fr_dbuff_len(&work_dbuff), tainted);
57
1.53M
  if (slen < 0) {
58
142
    talloc_free(vp);
59
142
    return slen;
60
142
  }
61
1.53M
  fr_pair_append(head, vp);
62
63
1.53M
  return fr_dbuff_set(dbuff, &work_dbuff);
64
1.53M
}
65
66
/** Decode a group
67
 *
68
 */
69
static ssize_t internal_decode_structural(TALLOC_CTX *ctx, fr_pair_list_t *head, fr_dict_attr_t const *parent_da,
70
            fr_dbuff_t *dbuff, void *decode_ctx)
71
3.64k
{
72
3.64k
  fr_pair_t *vp;
73
3.64k
  ssize_t   slen;
74
3.64k
  fr_dbuff_t  work_dbuff = FR_DBUFF(dbuff);
75
76
3.64k
  FR_PROTO_TRACE("Decoding group - %s", parent_da->name);
77
78
3.64k
  vp = fr_pair_afrom_da(ctx, parent_da);
79
3.64k
  if (!vp) return PAIR_DECODE_OOM;
80
3.64k
  PAIR_ALLOCED(vp);
81
82
  /*
83
   *  Decode all the children of this group
84
   */
85
7.75k
  while (fr_dbuff_extend(&work_dbuff)) {
86
7.75k
    FR_PROTO_HEX_MARKER(fr_dbuff_current(dbuff), fr_dbuff_remaining(dbuff), fr_dbuff_used(&work_dbuff),
87
7.75k
            "Decoding child");
88
89
7.75k
    slen = internal_decode_pair(vp, &vp->vp_group, parent_da, &work_dbuff, decode_ctx);
90
7.75k
    if (slen <= 0) {
91
317
      talloc_free(vp);
92
317
      return slen;
93
317
    }
94
7.75k
  }
95
3.32k
  fr_pair_append(head, vp);
96
97
3.32k
  return fr_dbuff_set(dbuff, &work_dbuff);
98
3.64k
}
99
100
static ssize_t internal_decode_pair(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent_da,
101
            fr_dbuff_t *dbuff, void *decode_ctx)
102
1.53M
{
103
1.53M
  ssize_t     slen = 0;
104
1.53M
  fr_dict_attr_t const  *da;
105
1.53M
  uint8_t     enc_byte = 0, ext_byte = 0, type_field_size, len_field_size;
106
1.53M
  fr_dbuff_marker_t len_field, enc_field, ext_field;
107
1.53M
  uint64_t    len = 0, type = 0;
108
1.53M
  size_t      remaining, needed;
109
1.53M
  bool      tainted, extended, unknown = false, internal = false;
110
1.53M
  fr_dbuff_t    work_dbuff = FR_DBUFF(dbuff);
111
112
  /*
113
   * The first byte of each attribute describes the encoding format.
114
   *
115
   * tlen (type field len)   - Describes how many byte(s) were used to encode the type.
116
   * llen (length field len) - Describes how many byte(s) were used to encode the length.
117
   * t (tainted)             - This attribute was tainted when it was encoded,
118
   *           so should be marked tainted now.
119
   * e (extended)            - Process the next byte as an extension of the encoding
120
   *                           field (allows for future extensions).
121
   *
122
   * 0                   1                   2                   3
123
   * 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
124
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
125
   * |tlen |llen |t|e|   Type (min)  |  Length (min) | value...
126
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
127
   *
128
   */
129
1.53M
  remaining = fr_dbuff_extend_lowat(NULL, &work_dbuff, 3);
130
1.53M
  if (remaining < 3) {
131
82
    fr_strerror_printf("%s: Insufficient data.  Need %zu additional byte(s)",
132
82
           __FUNCTION__, 3 - remaining);
133
82
    return -fr_dbuff_len(&work_dbuff);
134
82
  }
135
136
1.53M
  fr_dbuff_marker(&enc_field, &work_dbuff);
137
1.53M
  fr_dbuff_marker(&ext_field, &work_dbuff); /* Placed here to make static analysis happy */
138
1.53M
  FR_DBUFF_OUT_RETURN(&enc_byte, &work_dbuff);
139
1.53M
  type_field_size = ((enc_byte & FR_INTERNAL_MASK_TYPE) >> 5) + 1;  /* bits 0-2 */
140
1.53M
  len_field_size = ((enc_byte & FR_INTERNAL_MASK_LEN) >> 2) + 1;    /* bits 3-5 */
141
142
1.53M
  tainted = (enc_byte & FR_INTERNAL_FLAG_TAINTED) != 0;     /* bit 6 */
143
1.53M
  extended = (enc_byte & FR_INTERNAL_FLAG_EXTENDED) != 0;     /* bit 7 */
144
145
  /* Processed first encoding byte */
146
147
1.53M
  needed = type_field_size + len_field_size + extended;
148
1.53M
  remaining = fr_dbuff_extend_lowat(NULL, &work_dbuff, needed);
149
1.53M
  if (remaining < needed) {
150
30
    fr_strerror_printf("%s: Encoding byte invalid, fields overrun input data. "
151
30
           "%zu byte(s) remaining, need %zu byte(s)",
152
30
           __FUNCTION__, remaining, needed);
153
30
    return -needed;
154
30
  }
155
156
  /*
157
   * The second (optional) extension byte carries more flag information from the attribute.
158
   *
159
   * u (unknown attribute)   - When this pair was converted from network to internal
160
   *           format, it was found to be badly formatted, or not
161
   *           match an existing dictionary definition.
162
   *           A new unknown DA should be allocated for this attribute
163
   *           and it should be treated as raw octets.
164
   * i (internal attribute)  - Resolve this attribute in the internal dictionary.
165
   * - (currently unused)    - Unused flag.
166
   * e (extended)      - Encoding definitions continue to a third byte.
167
   *
168
   * 0                   1
169
   * 0 1 2 3 4 5 6 7 8 9 0
170
   * +-+-+-+-+-+-+-+-+-+-+
171
   * |u|i|-|-|-|-|-|e|
172
   * +-+-+-+-+-+-+-+-+-+-+
173
   *
174
   */
175
1.53M
  if (extended) {
176
15.3k
    fr_dbuff_set(&ext_field, &work_dbuff);
177
15.3k
    FR_DBUFF_OUT_RETURN(&ext_byte, &work_dbuff);
178
15.3k
    unknown = (ext_byte & FR_INTERNAL_FLAG_UNKNOWN) != 0;
179
15.3k
    internal = (ext_byte & FR_INTERNAL_FLAG_INTERNAL) != 0;
180
15.3k
    if (ext_byte & FR_INTERNAL_FLAG_EXTENDED) {
181
31
      fr_strerror_printf("%s: Third extension byte not in use", __FUNCTION__);
182
31
      return PAIR_DECODE_FATAL_ERROR;
183
31
    }
184
15.3k
  }
185
186
1.53M
  FR_DBUFF_OUT_UINT64V_RETURN(&type, &work_dbuff, type_field_size);
187
188
  /*
189
   *  This is the length of the start *after* the flags and
190
   *  type/length fields.
191
   */
192
1.53M
  fr_dbuff_marker(&len_field, &work_dbuff);
193
1.53M
  FR_DBUFF_OUT_UINT64V_RETURN(&len, &work_dbuff, len_field_size);
194
195
1.53M
  remaining = fr_dbuff_extend_lowat(NULL, &work_dbuff, len);
196
1.53M
  if (remaining < len) {
197
215
    fr_strerror_printf("%s: Length field value overruns input data. "
198
215
           "%zu byte(s) remaining, need %zu byte(s)",
199
215
           __FUNCTION__, remaining, (size_t) len);
200
215
    return -(fr_dbuff_current(&len_field) - fr_dbuff_start(&work_dbuff));
201
215
  }
202
203
  /*
204
   *  Internal flag is only set on the outer attribute
205
   *  so it's fine to swap the parent_da.
206
   */
207
1.53M
  if (internal) {
208
14.4k
    if (!parent_da->flags.is_root && !(parent_da->type == FR_TYPE_GROUP)) {
209
3
      fr_strerror_printf("%s: Internal flag can only be set on top level attribute", __FUNCTION__);
210
3
      return PAIR_DECODE_FATAL_ERROR;
211
3
    }
212
14.4k
    parent_da = fr_dict_root(fr_dict_internal());
213
14.4k
  }
214
215
1.53M
  if (unknown || parent_da->flags.is_unknown) {
216
1.48M
  unknown:
217
1.48M
    FR_PROTO_TRACE("Unknown attribute %" PRIu64, type);
218
1.48M
    da = fr_dict_attr_unknown_raw_afrom_num(ctx, parent_da, type);
219
1.48M
    if (!da) return PAIR_DECODE_FATAL_ERROR;
220
1.48M
    unknown = true;
221
1.53M
  } else {
222
1.53M
    da = fr_dict_attr_child_by_num(parent_da, type);
223
1.53M
    if (!da) goto unknown;
224
1.53M
  }
225
226
1.53M
  FR_PROTO_TRACE("decode context changed %s -> %s", da->parent->name, da->name);
227
228
  /*
229
   *  Set the end of our dbuff to match the length
230
   *  of the attribute.
231
   */
232
1.53M
  fr_dbuff_set_end(&work_dbuff, fr_dbuff_current(&work_dbuff) + len);
233
234
1.53M
  switch (da->type) {
235
  /*
236
   *  Structural types
237
   *
238
   *  STRUCTs are encoded as TLVs, because the struct
239
   *  packing only applies to the original protocol, and not
240
   *  to our internal encoding.
241
   */
242
13.3k
   case FR_TYPE_STRUCTURAL:
243
13.3k
    if (fr_type_is_vsa(da->type)) {
244
197
      if (unlikely(unknown)) {
245
0
        fr_strerror_printf("%s: %s can't be marked as unknown", __FUNCTION__,
246
0
            fr_type_to_str(da->type));
247
0
        fr_dbuff_set(&work_dbuff, &ext_field);
248
459
      error:
249
459
        if (unknown) fr_dict_attr_unknown_free(&da);
250
459
        return fr_pair_decode_slen(slen, fr_dbuff_start(&work_dbuff), fr_dbuff_current(&work_dbuff));
251
0
      }
252
197
    }
253
    /*
254
     *  It's ok for this function to return 0
255
     *  we can have empty groups (i.e. groups
256
     *  with no children)
257
     */
258
3.64k
    slen = internal_decode_structural(ctx, out, da, &work_dbuff, decode_ctx);
259
3.64k
    if (slen < 0) goto error;
260
3.32k
    break;
261
262
1.53M
  default:
263
    /*
264
     *  It's ok for this function to return 0
265
     *  we can have zero length strings.
266
     */
267
1.53M
    slen = internal_decode_pair_value(ctx, out, da, &work_dbuff, tainted, decode_ctx);
268
1.53M
    if (slen < 0) goto error;
269
1.53M
  }
270
271
1.53M
  return fr_dbuff_set(dbuff, &work_dbuff);
272
1.53M
}
273
274
/** Create a single fr_pair_t and all its nesting
275
 *
276
 */
277
ssize_t fr_internal_decode_pair(TALLOC_CTX *ctx, fr_pair_list_t *list, fr_dict_attr_t const *parent,
278
        uint8_t const *data, size_t data_len, void *decode_ctx)
279
0
{
280
0
  return fr_internal_decode_pair_dbuff(ctx, list, parent, &FR_DBUFF_TMP(data, data_len), decode_ctx);
281
0
}
282
283
ssize_t fr_internal_decode_pair_dbuff(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
284
        fr_dbuff_t *dbuff, void *decode_ctx)
285
1.52M
{
286
1.52M
  fr_pair_list_t  tmp;
287
1.52M
  ssize_t   slen;
288
1.52M
  fr_dbuff_t  work_dbuff = FR_DBUFF(dbuff);
289
290
1.52M
  fr_pair_list_init(&tmp);
291
292
1.52M
  slen = internal_decode_pair(ctx, &tmp, parent, &work_dbuff, decode_ctx);
293
1.52M
  if (slen <= 0) {
294
590
    fr_pair_list_free(&tmp);
295
590
    return slen;
296
590
  }
297
298
1.52M
  fr_pair_list_append(out, &tmp);
299
300
1.52M
  return fr_dbuff_set(dbuff, &work_dbuff);
301
1.52M
}
302
303
/** Retrieve all pairs from the dbuff
304
 *
305
 * @param ctx   to create new pairs in
306
 * @param out   list to append pairs to
307
 * @param parent  attribute within which which to decode
308
 * @param dbuff   to parse
309
 * @param decode_ctx  to pass to decoder function
310
 * @return
311
 *  - bytes of dbuff consumed
312
 *  - < 0 on error
313
 */
314
ssize_t fr_internal_decode_list_dbuff(TALLOC_CTX *ctx, fr_pair_list_t *out, fr_dict_attr_t const *parent,
315
            fr_dbuff_t *dbuff, void *decode_ctx)
316
1.29k
{
317
1.29k
  ssize_t   ret, len = 0;
318
319
1.53M
  while (fr_dbuff_remaining(dbuff)) {
320
1.52M
    ret = fr_internal_decode_pair_dbuff(ctx, out, parent, dbuff, decode_ctx);
321
1.52M
    if (ret < 0) return ret;
322
1.52M
    if (ret == 0) break;
323
1.52M
    len += ret;
324
1.52M
  }
325
326
701
  return len;
327
1.29k
}
328
329
/*
330
 *  Test points
331
 */
332
extern fr_test_point_pair_decode_t internal_tp_decode_pair;
333
fr_test_point_pair_decode_t internal_tp_decode_pair = {
334
  .test_ctx = NULL,
335
  .func   = fr_internal_decode_pair
336
};