/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 | } |