The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
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  */
24 RCSID("$Id: bfa07ef9869f78205a6787ffcccfde5008e593ce $")
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 {
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  ext_offsets = fr_ext_offsets(def, *chunk_p);
70  if (ext_offsets[ext]) return fr_ext_ptr(*chunk_p, ext_offsets[ext], info->has_hdr);
71 
72  if (info->has_hdr) hdr_len = sizeof(fr_ext_hdr_t); /* Add space for a length prefix */
73 
74  /*
75  * Packing the offsets into a uint8_t array
76  * means the offset address of the final
77  * extension must be less than or equal to
78  * UINT8_MAX * FR_EXT_ALIGNMENT.
79  */
80  chunk_len = talloc_get_size(chunk);
81  offset = ROUND_UP_DIV(chunk_len, FR_EXT_ALIGNMENT);
82  if (unlikely(offset > UINT8_MAX)) {
83  fr_strerror_const("Insufficient space remaining for extensions");
84  return NULL;
85  }
86 
87  /*
88  * talloc_realloc_size unhelpfully forgets
89  * the name of the chunk, so we need to
90  * record it and set it back again.
91  */
92  type = talloc_get_name(chunk);
93  n_chunk = talloc_realloc_size(NULL, chunk, (offset * FR_EXT_ALIGNMENT) + hdr_len + aligned_len);
94  if (!n_chunk) {
95  fr_strerror_printf("Failed reallocing %s (%s). Tried to realloc %zu bytes -> %zu bytes",
96  type, fr_syserror(errno), chunk_len, chunk_len + aligned_len);
97  return NULL;
98  }
99  talloc_set_name_const(n_chunk, type);
100 
101  ext_offsets = fr_ext_offsets(def, n_chunk);
102  ext_offsets[ext] = (uint8_t)offset;
103 
104  ext_ptr = ((uint8_t *)n_chunk) + chunk_len;
105  memset(ext_ptr, 0, hdr_len + aligned_len);
106 
107  *chunk_p = n_chunk;
108 
109  if (info->has_hdr) {
110  fr_ext_hdr_t *ext_hdr = (fr_ext_hdr_t *)ext_ptr;
111 
112  ext_hdr->len = ext_len; /* Record the real size */
113  return &ext_hdr->data; /* Pointer to the data portion */
114  }
115 
116  return ext_ptr;
117 }
118 
119 /** Return the length of an extension
120  *
121  * @param[in] def Extension definitions.
122  * @param[in] chunk to return extension length for.
123  * @param[in] ext to return length for.
124  * @return
125  * - 0 if no extension exists or is of zero length.
126  * - >0 the length of the extension.
127  */
128 size_t fr_ext_len(fr_ext_t const *def, TALLOC_CTX const *chunk, int ext)
129 {
130  uint8_t offset;
131  fr_ext_info_t const *info;
132  fr_ext_hdr_t *ext_hdr;
133  uint8_t *ext_offsets;
134 
135  ext_offsets = fr_ext_offsets(def, chunk);
136  offset = ext_offsets[ext];
137  if (!offset) return 0;
138 
139  info = &def->info[ext];
140  if (!info->has_hdr) return info->min; /* Fixed size */
141 
142  ext_hdr = fr_ext_ptr(chunk, offset, false); /* false as we're getting the header */
143  return ext_hdr->len;
144 }
145 
146 /** Copy extension data from one attribute to another
147  *
148  * @param[in] def Extension definitions.
149  * @param[in,out] chunk_dst to copy extension to.
150  * Under certain circumstances the value of *chunk_dst will
151  * be changed to point to a new memory block.
152  * All cached copies of the previous pointer should be
153  * updated.
154  * @param[in] chunk_src to copy extension from.
155  * @param[in] ext to copy.
156  * @return
157  * - NULL if we failed to allocate an extension structure.
158  * - A pointer to the offset of the extension in da_out.
159  */
160 void *fr_ext_copy(fr_ext_t const *def, TALLOC_CTX **chunk_dst, TALLOC_CTX const *chunk_src, int ext)
161 {
162  int i;
163  uint8_t *ext_src_offsets = fr_ext_offsets(def, chunk_src);
164  uint8_t *ext_dst_offsets = fr_ext_offsets(def, *chunk_dst);
165  void *ext_src_ptr, *ext_dst_ptr;
166  fr_ext_info_t const *info = &def->info[ext];
167 
168  if (!info->can_copy) {
169  fr_strerror_const("Extension cannot be copied");
170  return NULL;
171  }
172 
173  if (!ext_src_offsets[ext]) return NULL;
174 
175  ext_src_ptr = fr_ext_ptr(chunk_src, ext_src_offsets[ext], info->has_hdr);
176 
177  /*
178  * Only alloc if the extension doesn't
179  * already exist.
180  */
181  if (!ext_dst_offsets[ext]) {
182  if (info->alloc) {
183  ext_dst_ptr = info->alloc(def, chunk_dst, ext,
184  ext_src_ptr,
185  fr_ext_len(def, chunk_src, ext));
186  /*
187  * If there's no special alloc function
188  * we just allocate a chunk of the same
189  * size.
190  */
191  } else {
192  ext_dst_ptr = fr_ext_alloc_size(def, chunk_dst, ext,
193  fr_ext_len(def, chunk_src, ext));
194  }
195  } else {
196  ext_dst_ptr = fr_ext_ptr(*chunk_dst, ext_dst_offsets[ext], info->has_hdr);
197  }
198 
199  if (info->copy) {
200  info->copy(ext,
201  *chunk_dst,
202  ext_dst_ptr, fr_ext_len(def, *chunk_dst, ext),
203  chunk_src,
204  ext_src_ptr, fr_ext_len(def, chunk_src, ext));
205  /*
206  * If there's no special copy function
207  * we just copy the data from the old
208  * extension to the new one.
209  */
210  } else {
211  memcpy(ext_dst_ptr, ext_src_ptr, fr_ext_len(def, *chunk_dst, ext));
212  }
213 
214  /*
215  * Call any fixup functions
216  */
217  ext_dst_offsets = fr_ext_offsets(def, *chunk_dst);
218  for (i = 0; i < def->max; i++) {
219  if (i == ext) continue;
220 
221  if (!ext_dst_offsets[i]) continue;
222 
223  if (info->fixup &&
224  info->fixup(i, *chunk_dst,
225  fr_ext_ptr(*chunk_dst, ext_dst_offsets[i], info->has_hdr),
226  fr_ext_len(def, *chunk_dst, i)) < 0) return NULL;
227  }
228 
229  return ext_dst_ptr;
230 }
231 
232 /** Copy all the extensions from one attribute to another
233  *
234  * @param[in] def Extension definitions.
235  * @param[in,out] chunk_dst to copy extensions to.
236  * Under certain circumstances the value of *chunk_dst will
237  * be changed to point to a new memory block.
238  * All cached copies of the previous pointer should be
239  * updated.
240  * @param[in] chunk_src to copy extensions from.
241  * @return
242  * - 0 on success.
243  * - -1 if a copy operation failed.
244  */
245 int fr_ext_copy_all(fr_ext_t const *def, TALLOC_CTX **chunk_dst, TALLOC_CTX const *chunk_src)
246 {
247  int i;
248  uint8_t *ext_src_offsets = fr_ext_offsets(def, chunk_src); /* old chunk array */
249  uint8_t *ext_dst_offsets = fr_ext_offsets(def, *chunk_dst); /* new chunk array */
250  bool ext_new_alloc[def->max];
251 
252  /*
253  * Do the operation in two phases.
254  *
255  * Phase 1 allocates space for all the extensions.
256  */
257  for (i = 0; i < def->max; i++) {
258  fr_ext_info_t const *info = &def->info[i];
259 
260  if (!ext_src_offsets[i] || !info->can_copy) {
261  no_copy:
262  ext_new_alloc[i] = false;
263  continue;
264  }
265 
266  if (info->alloc) {
267  if (!info->alloc(def, chunk_dst, i,
268  fr_ext_ptr(chunk_src, ext_src_offsets[i], info->has_hdr),
269  fr_ext_len(def, chunk_src, i))) goto no_copy;
270  /*
271  * If there's no special alloc function
272  * we just allocate a chunk of the same
273  * size.
274  */
275  } else {
276  fr_ext_alloc_size(def, chunk_dst, i, fr_ext_len(def, chunk_src, i));
277  }
278  ext_new_alloc[i] = true;
279  ext_dst_offsets = fr_ext_offsets(def, *chunk_dst); /* Grab new offsets, chunk might have changed */
280  }
281 
282  /*
283  * Phase 2 populates the extension memory.
284  *
285  * We do this in two phases to avoid invalidating
286  * any pointers from extensions back to the extended
287  * talloc chunk.
288  */
289  for (i = 0; i < def->max; i++) {
290  fr_ext_info_t const *info = &def->info[i];
291 
292  if (!ext_src_offsets[i] || !ext_dst_offsets[i]) continue;
293 
294  if (!ext_new_alloc[i]) {
295  if (info->fixup &&
296  info->fixup(i, *chunk_dst,
297  fr_ext_ptr(*chunk_dst, ext_dst_offsets[i], info->has_hdr),
298  fr_ext_len(def, *chunk_dst, i)) < 0) return -1;
299  continue;
300  }
301  if (!info->can_copy) continue;
302 
303  if (info->copy) {
304  if (info->copy(i,
305  *chunk_dst,
306  fr_ext_ptr(*chunk_dst, ext_dst_offsets[i], info->has_hdr),
307  fr_ext_len(def, *chunk_dst, i),
308  chunk_src,
309  fr_ext_ptr(chunk_src, ext_src_offsets[i], info->has_hdr),
310  fr_ext_len(def, chunk_src, i)) < 0) return -1;
311  /*
312  * If there's no special copy function
313  * we just copy the data from the old
314  * extension to the new one.
315  */
316  } else {
317  memcpy(fr_ext_ptr(*chunk_dst, ext_dst_offsets[i], info->has_hdr),
318  fr_ext_ptr(chunk_src, ext_src_offsets[i], info->has_hdr),
319  fr_ext_len(def, *chunk_dst, i));
320  }
321  }
322 
323  return 0;
324 }
325 
326 /** Print out all extensions and hexdump their contents
327  *
328  * This function is intended to be called from interactive debugging
329  * sessions only. It does not use the normal logging infrastructure.
330  *
331  * @param[in] def Extension definitions.
332  * @param[in] name the identifier of the structure
333  * being debugged i.e da->name.
334  * @param[in] chunk to debug.
335  */
336 void fr_ext_debug(fr_ext_t const *def, char const *name, void const *chunk)
337 {
338  int i;
339 
340  FR_FAULT_LOG("%s ext total_len=%zu", name, talloc_get_size(chunk));
341  for (i = 0; i < (int)def->max; i++) {
342  uint8_t *ext_offsets = fr_ext_offsets(def, chunk);
343 
344  if (ext_offsets[i]) {
345  void *ext = fr_ext_ptr(chunk, ext_offsets[i], def->info[i].has_hdr);
346  size_t ext_len = fr_ext_len(def, chunk, i);
347  char const *ext_name = fr_table_ordered_str_by_num(def->name_table,
348  *def->name_table_len,
349  i, "<INVALID>");
350 
351  if (ext_len > 1024) {
352  FR_FAULT_LOG("%s ext id=%s - possibly bad length %zu - limiting dump to 1024",
353  name, ext_name, ext_len);
354  ext_len = 1024;
355  }
356 
357  FR_FAULT_LOG("%s ext id=%s start=%p end=%p len=%zu",
358  name, ext_name, ext, ((uint8_t *)ext) + ext_len, ext_len);
359  FR_FAULT_LOG_HEX(ext, ext_len);
360  }
361  }
362 }
#define RCSID(id)
Definition: build.h:444
#define unlikely(_x)
Definition: build.h:378
#define FR_FAULT_LOG_HEX(_data, _data_len)
Definition: debug.h:50
#define FR_FAULT_LOG(_fmt,...)
Definition: debug.h:49
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
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:128
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:336
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:160
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:245
bool can_copy
Copying this extension between structs is allowed.
Definition: ext.h:116
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
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
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
Definition: merged_model.c:30
#define UINT8_MAX
Definition: merged_model.c:32
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