Coverage Report

Created: 2026-01-10 06:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/util/ext.c
Line
Count
Source
1
/*
2
 *   This program is free software; you can redistribute it and/or modify
3
 *   it under the terms of the GNU General Public License as published by
4
 *   the Free Software Foundation; either version 2 of the License, or
5
 *   (at your option) any later version.
6
 *
7
 *   This program 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
10
 *   GNU General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU General Public License
13
 *   along with this program; if not, write to the Free Software
14
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15
 */
16
17
/** 'compositing' using talloced structures
18
 *
19
 * @file src/lib/util/ext.c
20
 *
21
 * @copyright 2020 The FreeRADIUS server project
22
 * @copyright 2020 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
23
 */
24
RCSID("$Id: 4cc429de6c78ee53b86cb96e349e6f4be0031d04 $")
25
26
#include <freeradius-devel/util/debug.h>
27
#include <freeradius-devel/util/ext.h>
28
#include <freeradius-devel/util/misc.h>
29
#include <freeradius-devel/util/syserror.h>
30
31
/** Add a variable length extension to a talloc chunk
32
 *
33
 * This is used to build a structure from a primary struct type and one or more
34
 * extension structures.  The memory for the composed structure is contiguous which
35
 * has performance benefits, and means we don't have the overhead of talloc headers
36
 * for each of the extensions.
37
 *
38
 * @note When a new extension is allocated its memory will be zeroed.
39
 *
40
 * @note It is highly recommended to allocate composed structures within a talloc_pool
41
 * to avoid the overhead of malloc+memcpy.
42
 *
43
 * @param[in] def   Extension definitions.
44
 * @param[in,out] chunk_p The chunk to add an extension for.
45
 *        Under certain circumstances the value of *chunk_p will
46
 *        be changed to point to a new memory block.
47
 *        All cached copies of the previous pointer should be
48
 *        updated.
49
 * @param[in] ext   to alloc.
50
 * @param[in] ext_len   The length of the extension.
51
 * @return
52
 *  - NULL if we failed allocating an extension.
53
 *  - A pointer to the extension we allocated.
54
 */
55
void *fr_ext_alloc_size(fr_ext_t const *def, void **chunk_p, int ext, size_t ext_len)
56
9.17M
{
57
9.17M
  size_t      aligned_len = ROUND_UP_POW2(ext_len, FR_EXT_ALIGNMENT);
58
9.17M
  size_t      chunk_len;
59
9.17M
  size_t      hdr_len = 0;
60
61
9.17M
  size_t      offset;
62
63
9.17M
  fr_ext_info_t const *info = &def->info[ext];
64
9.17M
  void      *n_chunk, *chunk = *chunk_p;
65
9.17M
  uint8_t     *ext_offsets;
66
9.17M
  uint8_t     *ext_ptr;
67
9.17M
  char const    *type;
68
69
9.17M
  fr_assert(chunk != NULL);
70
9.17M
  fr_assert(talloc_parent(chunk) != NULL);
71
72
9.17M
  ext_offsets = fr_ext_offsets(def, *chunk_p);
73
9.17M
  if (ext_offsets[ext]) return fr_ext_ptr(*chunk_p, ext_offsets[ext], info->has_hdr);
74
75
9.17M
  if (info->has_hdr) hdr_len = sizeof(fr_ext_hdr_t); /* Add space for a length prefix */
76
77
  /*
78
   *  Packing the offsets into a uint8_t array
79
   *  means the offset address of the final
80
   *  extension must be less than or equal to
81
   *  UINT8_MAX * FR_EXT_ALIGNMENT.
82
   */
83
9.17M
  chunk_len = talloc_get_size(chunk);
84
9.17M
  offset = ROUND_UP_DIV(chunk_len, FR_EXT_ALIGNMENT);
85
9.17M
  if (unlikely(offset > UINT8_MAX)) {
86
0
    fr_strerror_const("Insufficient space remaining for extensions");
87
0
    return NULL;
88
0
  }
89
90
  /*
91
   *  talloc_realloc_size unhelpfully forgets
92
   *  the name of the chunk, so we need to
93
   *  record it and set it back again.
94
   */
95
9.17M
  type = talloc_get_name(chunk);
96
9.17M
  n_chunk = talloc_realloc_size(NULL, chunk, (offset * FR_EXT_ALIGNMENT) + hdr_len + aligned_len);
97
9.17M
  if (!n_chunk) {
98
0
    fr_strerror_printf("Failed reallocing %s (%s).  Tried to realloc %zu bytes -> %zu bytes",
99
0
           type, fr_syserror(errno), chunk_len, chunk_len + aligned_len);
100
0
    return NULL;
101
0
  }
102
9.17M
  talloc_set_name_const(n_chunk, type);
103
104
9.17M
  ext_offsets = fr_ext_offsets(def, n_chunk);
105
9.17M
  ext_offsets[ext] = (uint8_t)offset;
106
107
9.17M
  ext_ptr = ((uint8_t *)n_chunk) + chunk_len;
108
9.17M
  memset(ext_ptr, 0, hdr_len + aligned_len);
109
110
9.17M
  *chunk_p = n_chunk;
111
112
9.17M
  if (info->has_hdr) {
113
4.69M
    fr_ext_hdr_t *ext_hdr = (fr_ext_hdr_t *)ext_ptr;
114
115
4.69M
    ext_hdr->len = ext_len;   /* Record the real size */
116
4.69M
    return &ext_hdr->data;    /* Pointer to the data portion */
117
4.69M
  }
118
119
4.47M
  return ext_ptr;
120
9.17M
}
121
122
/** Return the length of an extension
123
 *
124
 * @param[in] def   Extension definitions.
125
 * @param[in] chunk   to return extension length for.
126
 * @param[in] ext   to return length for.
127
 * @return
128
 *  - 0 if no extension exists or is of zero length.
129
 *  - >0 the length of the extension.
130
 */
131
size_t fr_ext_len(fr_ext_t const *def, TALLOC_CTX const *chunk, int ext)
132
629k
{
133
629k
  uint8_t     offset;
134
629k
  fr_ext_info_t const *info;
135
629k
  fr_ext_hdr_t    *ext_hdr;
136
629k
  uint8_t     *ext_offsets;
137
138
629k
  ext_offsets = fr_ext_offsets(def, chunk);
139
629k
  offset = ext_offsets[ext];
140
629k
  if (!offset) return 0;
141
142
629k
  info = &def->info[ext];
143
629k
  if (!info->has_hdr) return info->min;    /* Fixed size */
144
145
539k
  ext_hdr = fr_ext_ptr(chunk, offset, false); /* false as we're getting the header */
146
539k
  return ext_hdr->len;
147
629k
}
148
149
/** Copy extension data from one attribute to another
150
 *
151
 * @param[in] def   Extension definitions.
152
 * @param[in,out] chunk_dst to copy extension to.
153
 *        Under certain circumstances the value of *chunk_dst will
154
 *        be changed to point to a new memory block.
155
 *        All cached copies of the previous pointer should be
156
 *        updated.
157
 * @param[in] chunk_src   to copy extension from.
158
 * @param[in] ext   to copy.
159
 * @return
160
 *  - NULL if we failed to allocate an extension structure.
161
 *  - A pointer to the offset of the extension in da_out.
162
 */
163
void *fr_ext_copy(fr_ext_t const *def, TALLOC_CTX **chunk_dst, TALLOC_CTX const *chunk_src, int ext)
164
4.57M
{
165
4.57M
  int     i;
166
4.57M
  uint8_t     *ext_src_offsets = fr_ext_offsets(def, chunk_src);
167
4.57M
  uint8_t     *ext_dst_offsets = fr_ext_offsets(def, *chunk_dst);
168
4.57M
  void      *ext_src_ptr, *ext_dst_ptr;
169
4.57M
  fr_ext_info_t const *info = &def->info[ext];
170
171
4.57M
  if (!info->can_copy) {
172
0
    fr_strerror_const("Extension cannot be copied");
173
0
    return NULL;
174
0
  }
175
176
4.57M
  if (!ext_src_offsets[ext]) return NULL;
177
178
297k
  ext_src_ptr = fr_ext_ptr(chunk_src, ext_src_offsets[ext], info->has_hdr);
179
180
  /*
181
   *  Only alloc if the extension doesn't
182
   *  already exist.
183
   */
184
297k
  if (!ext_dst_offsets[ext]) {
185
29.5k
    if (info->alloc) {
186
0
      ext_dst_ptr = info->alloc(def, chunk_dst, ext,
187
0
              ext_src_ptr,
188
0
              fr_ext_len(def, chunk_src, ext));
189
    /*
190
     *  If there's no special alloc function
191
     *  we just allocate a chunk of the same
192
     *  size.
193
     */
194
29.5k
    } else {
195
29.5k
      ext_dst_ptr = fr_ext_alloc_size(def, chunk_dst, ext,
196
29.5k
              fr_ext_len(def, chunk_src, ext));
197
29.5k
    }
198
268k
  } else {
199
268k
    ext_dst_ptr = fr_ext_ptr(*chunk_dst, ext_dst_offsets[ext], info->has_hdr);
200
268k
  }
201
202
297k
  if (info->copy) {
203
297k
    info->copy(ext,
204
297k
         *chunk_dst,
205
297k
         ext_dst_ptr, fr_ext_len(def, *chunk_dst, ext),
206
297k
         chunk_src,
207
297k
         ext_src_ptr, fr_ext_len(def, chunk_src, ext));
208
  /*
209
   *  If there's no special copy function
210
   *  we just copy the data from the old
211
   *  extension to the new one.
212
   */
213
297k
  } else {
214
0
    memcpy(ext_dst_ptr, ext_src_ptr, fr_ext_len(def, *chunk_dst, ext));
215
0
  }
216
217
  /*
218
   *  Call any fixup functions
219
   */
220
297k
  ext_dst_offsets = fr_ext_offsets(def, *chunk_dst);
221
2.68M
  for (i = 0; i < def->max; i++) {
222
2.38M
    if (i == ext) continue;
223
224
2.08M
    if (!ext_dst_offsets[i]) continue;
225
226
693k
    if (info->fixup &&
227
0
        info->fixup(i, *chunk_dst,
228
0
        fr_ext_ptr(*chunk_dst, ext_dst_offsets[i], info->has_hdr),
229
0
        fr_ext_len(def, *chunk_dst, i)) < 0) return NULL;
230
693k
  }
231
232
297k
  return ext_dst_ptr;
233
297k
}
234
235
/** Copy all the extensions from one attribute to another
236
 *
237
 * @param[in] def   Extension definitions.
238
 * @param[in,out] chunk_dst to copy extensions to.
239
 *        Under certain circumstances the value of *chunk_dst will
240
 *        be changed to point to a new memory block.
241
 *        All cached copies of the previous pointer should be
242
 *        updated.
243
 * @param[in] chunk_src   to copy extensions from.
244
 * @return
245
 *  - 0 on success.
246
 *  - -1 if a copy operation failed.
247
 */
248
int fr_ext_copy_all(fr_ext_t const *def, TALLOC_CTX **chunk_dst, TALLOC_CTX const *chunk_src)
249
576
{
250
576
  int i;
251
576
  uint8_t *ext_src_offsets = fr_ext_offsets(def, chunk_src);  /* old chunk array */
252
576
  uint8_t *ext_dst_offsets = fr_ext_offsets(def, *chunk_dst); /* new chunk array */
253
576
  bool  ext_new_alloc[def->max];
254
255
  /*
256
   *  Do the operation in two phases.
257
   *
258
   *  Phase 1 allocates space for all the extensions.
259
   */
260
5.18k
  for (i = 0; i < def->max; i++) {
261
4.60k
    fr_ext_info_t const *info = &def->info[i];
262
263
4.60k
    if (!ext_src_offsets[i] || !info->can_copy) {
264
3.55k
    no_copy:
265
3.55k
      ext_new_alloc[i] = false;
266
3.55k
      continue;
267
3.55k
    }
268
269
1.05k
    if (info->alloc) {
270
0
      if (!info->alloc(def, chunk_dst, i,
271
0
               fr_ext_ptr(chunk_src, ext_src_offsets[i], info->has_hdr),
272
0
               fr_ext_len(def, chunk_src, i))) goto no_copy;
273
    /*
274
     *  If there's no special alloc function
275
     *  we just allocate a chunk of the same
276
     *  size.
277
     */
278
1.05k
    } else {
279
1.05k
      fr_ext_alloc_size(def, chunk_dst, i, fr_ext_len(def, chunk_src, i));
280
1.05k
    }
281
1.05k
    ext_new_alloc[i] = true;
282
1.05k
    ext_dst_offsets = fr_ext_offsets(def, *chunk_dst);  /* Grab new offsets, chunk might have changed */
283
1.05k
  }
284
285
  /*
286
   *  Phase 2 populates the extension memory.
287
   *
288
   *  We do this in two phases to avoid invalidating
289
   *  any pointers from extensions back to the extended
290
   *  talloc chunk.
291
   */
292
5.18k
  for (i = 0; i < def->max; i++) {
293
4.60k
    fr_ext_info_t const *info = &def->info[i];
294
295
4.60k
    if (!ext_src_offsets[i] || !ext_dst_offsets[i]) continue;
296
297
1.93k
    if (!ext_new_alloc[i]) {
298
876
      if (info->fixup &&
299
576
          info->fixup(i, *chunk_dst,
300
576
          fr_ext_ptr(*chunk_dst, ext_dst_offsets[i], info->has_hdr),
301
576
          fr_ext_len(def, *chunk_dst, i)) < 0) return -1;
302
876
      continue;
303
876
    }
304
1.05k
    if (!info->can_copy) continue;
305
306
1.05k
    if (info->copy) {
307
1.01k
      if (info->copy(i,
308
1.01k
               *chunk_dst,
309
1.01k
               fr_ext_ptr(*chunk_dst, ext_dst_offsets[i], info->has_hdr),
310
1.01k
               fr_ext_len(def, *chunk_dst, i),
311
1.01k
               chunk_src,
312
1.01k
               fr_ext_ptr(chunk_src, ext_src_offsets[i], info->has_hdr),
313
1.01k
               fr_ext_len(def, chunk_src, i)) < 0) return -1;
314
    /*
315
     *  If there's no special copy function
316
     *  we just copy the data from the old
317
     *  extension to the new one.
318
     */
319
1.01k
    } else {
320
40
      memcpy(fr_ext_ptr(*chunk_dst, ext_dst_offsets[i], info->has_hdr),
321
40
             fr_ext_ptr(chunk_src, ext_src_offsets[i], info->has_hdr),
322
40
             fr_ext_len(def, *chunk_dst, i));
323
40
    }
324
1.05k
  }
325
326
576
  return 0;
327
576
}
328
329
/** Print out all extensions and hexdump their contents
330
 *
331
 * This function is intended to be called from interactive debugging
332
 * sessions only.  It does not use the normal logging infrastructure.
333
 *
334
 * @param[in] def   Extension definitions.
335
 * @param[in] name    the identifier of the structure
336
 *        being debugged i.e da->name.
337
 * @param[in] chunk   to debug.
338
 */
339
void fr_ext_debug(fr_ext_t const *def, char const *name, void const *chunk)
340
0
{
341
0
  int i;
342
343
0
  FR_FAULT_LOG("%s ext total_len=%zu", name, talloc_get_size(chunk));
344
0
  for (i = 0; i < (int)def->max; i++) {
345
0
    uint8_t *ext_offsets = fr_ext_offsets(def, chunk);
346
347
0
    if (ext_offsets[i]) {
348
0
      void    *ext = fr_ext_ptr(chunk, ext_offsets[i], def->info[i].has_hdr);
349
0
      size_t    ext_len = fr_ext_len(def, chunk, i);
350
0
      char const  *ext_name = fr_table_ordered_str_by_num(def->name_table,
351
0
                    *def->name_table_len,
352
0
                    i, "<INVALID>");
353
354
0
      if (ext_len > 1024) {
355
0
        FR_FAULT_LOG("%s ext id=%s - possibly bad length %zu - limiting dump to 1024",
356
0
               name, ext_name, ext_len);
357
0
        ext_len = 1024;
358
0
      }
359
360
0
      FR_FAULT_LOG("%s ext id=%s start=%p end=%p len=%zu",
361
0
             name, ext_name, ext, ((uint8_t *)ext) + ext_len, ext_len);
362
0
      FR_FAULT_LOG_HEX(ext, ext_len);
363
0
    }
364
0
  }
365
0
}