The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
ext.c
Go to the documentation of this file.
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 */
24RCSID("$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 */
55void *fr_ext_alloc_size(fr_ext_t const *def, void **chunk_p, int ext, size_t ext_len)
56{
57 size_t aligned_len = ROUND_UP_POW2(ext_len, FR_EXT_ALIGNMENT);
58 size_t chunk_len;
59 size_t hdr_len = 0;
60
61 size_t offset;
62
63 fr_ext_info_t const *info = &def->info[ext];
64 void *n_chunk, *chunk = *chunk_p;
65 uint8_t *ext_offsets;
66 uint8_t *ext_ptr;
67 char const *type;
68
69 fr_assert(chunk != NULL);
70 fr_assert(talloc_parent(chunk) != NULL);
71
72 ext_offsets = fr_ext_offsets(def, *chunk_p);
73 if (ext_offsets[ext]) return fr_ext_ptr(*chunk_p, ext_offsets[ext], info->has_hdr);
74
75 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 chunk_len = talloc_get_size(chunk);
84 offset = ROUND_UP_DIV(chunk_len, FR_EXT_ALIGNMENT);
85 if (unlikely(offset > UINT8_MAX)) {
86 fr_strerror_const("Insufficient space remaining for extensions");
87 return NULL;
88 }
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 type = talloc_get_name(chunk);
96 n_chunk = talloc_realloc_size(NULL, chunk, (offset * FR_EXT_ALIGNMENT) + hdr_len + aligned_len);
97 if (!n_chunk) {
98 fr_strerror_printf("Failed reallocing %s (%s). Tried to realloc %zu bytes -> %zu bytes",
99 type, fr_syserror(errno), chunk_len, chunk_len + aligned_len);
100 return NULL;
101 }
102 talloc_set_name_const(n_chunk, type);
103
104 ext_offsets = fr_ext_offsets(def, n_chunk);
105 ext_offsets[ext] = (uint8_t)offset;
106
107 ext_ptr = ((uint8_t *)n_chunk) + chunk_len;
108 memset(ext_ptr, 0, hdr_len + aligned_len);
109
110 *chunk_p = n_chunk;
111
112 if (info->has_hdr) {
113 fr_ext_hdr_t *ext_hdr = (fr_ext_hdr_t *)ext_ptr;
114
115 ext_hdr->len = ext_len; /* Record the real size */
116 return &ext_hdr->data; /* Pointer to the data portion */
117 }
118
119 return ext_ptr;
120}
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 */
131size_t fr_ext_len(fr_ext_t const *def, TALLOC_CTX const *chunk, int ext)
132{
133 uint8_t offset;
134 fr_ext_info_t const *info;
135 fr_ext_hdr_t *ext_hdr;
136 uint8_t *ext_offsets;
137
138 ext_offsets = fr_ext_offsets(def, chunk);
139 offset = ext_offsets[ext];
140 if (!offset) return 0;
141
142 info = &def->info[ext];
143 if (!info->has_hdr) return info->min; /* Fixed size */
144
145 ext_hdr = fr_ext_ptr(chunk, offset, false); /* false as we're getting the header */
146 return ext_hdr->len;
147}
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 */
163void *fr_ext_copy(fr_ext_t const *def, TALLOC_CTX **chunk_dst, TALLOC_CTX const *chunk_src, int ext)
164{
165 int i;
166 uint8_t *ext_src_offsets = fr_ext_offsets(def, chunk_src);
167 uint8_t *ext_dst_offsets = fr_ext_offsets(def, *chunk_dst);
168 void *ext_src_ptr, *ext_dst_ptr;
169 fr_ext_info_t const *info = &def->info[ext];
170
171 if (!info->can_copy) {
172 fr_strerror_const("Extension cannot be copied");
173 return NULL;
174 }
175
176 if (!ext_src_offsets[ext]) return NULL;
177
178 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 if (!ext_dst_offsets[ext]) {
185 if (info->alloc) {
186 ext_dst_ptr = info->alloc(def, chunk_dst, ext,
187 ext_src_ptr,
188 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 } else {
195 ext_dst_ptr = fr_ext_alloc_size(def, chunk_dst, ext,
196 fr_ext_len(def, chunk_src, ext));
197 }
198 } else {
199 ext_dst_ptr = fr_ext_ptr(*chunk_dst, ext_dst_offsets[ext], info->has_hdr);
200 }
201
202 if (info->copy) {
203 info->copy(ext,
204 *chunk_dst,
205 ext_dst_ptr, fr_ext_len(def, *chunk_dst, ext),
206 chunk_src,
207 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 } else {
214 memcpy(ext_dst_ptr, ext_src_ptr, fr_ext_len(def, *chunk_dst, ext));
215 }
216
217 /*
218 * Call any fixup functions
219 */
220 ext_dst_offsets = fr_ext_offsets(def, *chunk_dst);
221 for (i = 0; i < def->max; i++) {
222 if (i == ext) continue;
223
224 if (!ext_dst_offsets[i]) continue;
225
226 if (info->fixup &&
227 info->fixup(i, *chunk_dst,
228 fr_ext_ptr(*chunk_dst, ext_dst_offsets[i], info->has_hdr),
229 fr_ext_len(def, *chunk_dst, i)) < 0) return NULL;
230 }
231
232 return ext_dst_ptr;
233}
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 */
248int fr_ext_copy_all(fr_ext_t const *def, TALLOC_CTX **chunk_dst, TALLOC_CTX const *chunk_src)
249{
250 int i;
251 uint8_t *ext_src_offsets = fr_ext_offsets(def, chunk_src); /* old chunk array */
252 uint8_t *ext_dst_offsets = fr_ext_offsets(def, *chunk_dst); /* new chunk array */
253 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 for (i = 0; i < def->max; i++) {
261 fr_ext_info_t const *info = &def->info[i];
262
263 if (!ext_src_offsets[i] || !info->can_copy) {
264 no_copy:
265 ext_new_alloc[i] = false;
266 continue;
267 }
268
269 if (info->alloc) {
270 if (!info->alloc(def, chunk_dst, i,
271 fr_ext_ptr(chunk_src, ext_src_offsets[i], info->has_hdr),
272 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 } else {
279 fr_ext_alloc_size(def, chunk_dst, i, fr_ext_len(def, chunk_src, i));
280 }
281 ext_new_alloc[i] = true;
282 ext_dst_offsets = fr_ext_offsets(def, *chunk_dst); /* Grab new offsets, chunk might have changed */
283 }
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 for (i = 0; i < def->max; i++) {
293 fr_ext_info_t const *info = &def->info[i];
294
295 if (!ext_src_offsets[i] || !ext_dst_offsets[i]) continue;
296
297 if (!ext_new_alloc[i]) {
298 if (info->fixup &&
299 info->fixup(i, *chunk_dst,
300 fr_ext_ptr(*chunk_dst, ext_dst_offsets[i], info->has_hdr),
301 fr_ext_len(def, *chunk_dst, i)) < 0) return -1;
302 continue;
303 }
304 if (!info->can_copy) continue;
305
306 if (info->copy) {
307 if (info->copy(i,
308 *chunk_dst,
309 fr_ext_ptr(*chunk_dst, ext_dst_offsets[i], info->has_hdr),
310 fr_ext_len(def, *chunk_dst, i),
311 chunk_src,
312 fr_ext_ptr(chunk_src, ext_src_offsets[i], info->has_hdr),
313 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 } else {
320 memcpy(fr_ext_ptr(*chunk_dst, ext_dst_offsets[i], info->has_hdr),
321 fr_ext_ptr(chunk_src, ext_src_offsets[i], info->has_hdr),
322 fr_ext_len(def, *chunk_dst, i));
323 }
324 }
325
326 return 0;
327}
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 */
339void fr_ext_debug(fr_ext_t const *def, char const *name, void const *chunk)
340{
341 int i;
342
343 FR_FAULT_LOG("%s ext total_len=%zu", name, talloc_get_size(chunk));
344 for (i = 0; i < (int)def->max; i++) {
345 uint8_t *ext_offsets = fr_ext_offsets(def, chunk);
346
347 if (ext_offsets[i]) {
348 void *ext = fr_ext_ptr(chunk, ext_offsets[i], def->info[i].has_hdr);
349 size_t ext_len = fr_ext_len(def, chunk, i);
350 char const *ext_name = fr_table_ordered_str_by_num(def->name_table,
351 *def->name_table_len,
352 i, "<INVALID>");
353
354 if (ext_len > 1024) {
355 FR_FAULT_LOG("%s ext id=%s - possibly bad length %zu - limiting dump to 1024",
356 name, ext_name, ext_len);
357 ext_len = 1024;
358 }
359
360 FR_FAULT_LOG("%s ext id=%s start=%p end=%p len=%zu",
361 name, ext_name, ext, ((uint8_t *)ext) + ext_len, ext_len);
362 FR_FAULT_LOG_HEX(ext, ext_len);
363 }
364 }
365}
#define RCSID(id)
Definition build.h:483
#define unlikely(_x)
Definition build.h:381
#define FR_FAULT_LOG_HEX(_data, _data_len)
Definition debug.h:50
#define FR_FAULT_LOG(_fmt,...)
Definition debug.h:49
void * fr_ext_copy(fr_ext_t const *def, TALLOC_CTX **chunk_dst, TALLOC_CTX const *chunk_src, int ext)
Copy extension data from one attribute to another.
Definition ext.c:163
size_t fr_ext_len(fr_ext_t const *def, TALLOC_CTX const *chunk, int ext)
Return the length of an extension.
Definition ext.c:131
void fr_ext_debug(fr_ext_t const *def, char const *name, void const *chunk)
Print out all extensions and hexdump their contents.
Definition ext.c:339
int fr_ext_copy_all(fr_ext_t const *def, TALLOC_CTX **chunk_dst, TALLOC_CTX const *chunk_src)
Copy all the extensions from one attribute to another.
Definition ext.c:248
void * fr_ext_alloc_size(fr_ext_t const *def, void **chunk_p, int ext, size_t ext_len)
Add a variable length extension to a talloc chunk.
Definition ext.c:55
bool can_copy
Copying this extension between structs is allowed.
Definition ext.h:116
int max
The highest extension value.
Definition ext.h:130
uint8_t data[]
Extension data.
Definition ext.h:139
bool has_hdr
Additional metadata should be allocated before the extension data to record the exact length of the e...
Definition ext.h:113
size_t * name_table_len
How many extensions there are in the table.
Definition ext.h:129
static void * fr_ext_ptr(TALLOC_CTX const *chunk, size_t offset, bool has_hdr)
Return a pointer to an extension in a chunk.
Definition ext.h:150
fr_table_num_ordered_t const * name_table
String identifiers for the extensions.
Definition ext.h:128
size_t len
Length of extension data.
Definition ext.h:138
fr_ext_copy_t copy
Override the normal copy operation with a callback.
Definition ext.h:119
fr_ext_alloc_t alloc
Override the normal alloc operation with a callback.
Definition ext.h:118
fr_ext_fixup_t fixup
Callback for fixing up internal consistency issues.
Definition ext.h:120
#define FR_EXT_ALIGNMENT
The alignment of object extension structures.
Definition ext.h:54
size_t min
Minimum size of extension.
Definition ext.h:112
static uint8_t * fr_ext_offsets(fr_ext_t const *def, TALLOC_CTX const *chunk)
Definition ext.h:142
fr_ext_info_t const * info
Additional information about each extension.
Definition ext.h:131
Optional extension header struct.
Definition ext.h:137
Additional information for a given extension.
Definition ext.h:111
Structure to define a set of extensions.
Definition ext.h:126
#define ROUND_UP_POW2(_num, _mul)
Round up - Only works if _mul is a power of 2 but avoids division.
Definition math.h:144
#define ROUND_UP_DIV(_x, _y)
Get the ceiling value of integer division.
Definition math.h:153
unsigned char uint8_t
#define UINT8_MAX
#define fr_assert(_expr)
Definition rad_assert.h:38
static char const * name
fr_aka_sim_id_type_t type
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
char const * fr_table_ordered_str_by_num(fr_table_num_ordered_t const *table, size_t table_len, int number, char const *def)
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
#define fr_strerror_const(_msg)
Definition strerror.h:223