/src/freeradius-server/src/lib/util/dl.c
Line | Count | Source (jump to first uncovered line) |
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 | | /** |
18 | | * $Id: 37717f9f82c82f4dbd6ede12de7e9f66a357e939 $ |
19 | | * |
20 | | * @file src/lib/util/dl.c |
21 | | * @brief Wrappers around dlopen to manage loading shared objects at runtime. |
22 | | * |
23 | | * @copyright 2016-2019 The FreeRADIUS server project |
24 | | * @copyright 2016-2019 Arran Cudbard-Bell (a.cudbardb@freeradius.org) |
25 | | */ |
26 | | RCSID("$Id: 37717f9f82c82f4dbd6ede12de7e9f66a357e939 $") |
27 | | |
28 | | #include <freeradius-devel/server/dl_module.h> |
29 | | #include <freeradius-devel/server/log.h> |
30 | | #include <freeradius-devel/util/atexit.h> |
31 | | #include <freeradius-devel/util/debug.h> |
32 | | |
33 | | #include <freeradius-devel/util/paths.h> |
34 | | #include <freeradius-devel/util/syserror.h> |
35 | | |
36 | | |
37 | | #ifdef HAVE_VALGRIND_H |
38 | | # include <valgrind.h> |
39 | | #else |
40 | 76 | # define RUNNING_ON_VALGRIND 0 |
41 | | #endif |
42 | | |
43 | | #ifndef RTLD_NOW |
44 | | # define RTLD_NOW (0) |
45 | | #endif |
46 | | #ifndef RTLD_LOCAL |
47 | | # define RTLD_LOCAL (0) |
48 | | #endif |
49 | | |
50 | | /** Symbol dependent initialisation callback |
51 | | * |
52 | | * Call this function when the dl is loaded for the first time. |
53 | | */ |
54 | | typedef struct dl_symbol_init_s dl_symbol_init_t; |
55 | | struct dl_symbol_init_s { |
56 | | fr_dlist_t entry; //!< Entry into the list of 'init' symbol callbacks. |
57 | | |
58 | | unsigned int priority; //!< Call priority |
59 | | char const *symbol; //!< to search for. May be NULL in which case func is always called. |
60 | | dl_onload_t func; //!< to call when symbol is found in a dl's symbol table. |
61 | | void *uctx; //!< User data to pass to func. |
62 | | }; |
63 | | |
64 | | /** Symbol dependent free callback |
65 | | * |
66 | | * Call this function before the dl is unloaded. |
67 | | */ |
68 | | typedef struct dl_symbol_free_s dl_symbol_free_t; |
69 | | struct dl_symbol_free_s { |
70 | | fr_dlist_t entry; //!< Entry into the list of 'free' symbol callbacks. |
71 | | |
72 | | unsigned int priority; //!< Call priority |
73 | | char const *symbol; //!< to search for. May be NULL in which case func is always called. |
74 | | dl_unload_t func; //!< to call when symbol is found in a dl's symbol table. |
75 | | void *uctx; //!< User data to pass to func. |
76 | | }; |
77 | | |
78 | | /** A dynamic loader |
79 | | * |
80 | | */ |
81 | | struct dl_loader_s { |
82 | | char *lib_dir; //!< Where the libraries live. |
83 | | |
84 | | /** Linked list of symbol init callbacks |
85 | | * |
86 | | * @note Is linked list to retain insertion order. We don't expect huge numbers |
87 | | * of callbacks so there shouldn't be efficiency issues. |
88 | | */ |
89 | | fr_dlist_head_t sym_init; |
90 | | |
91 | | /** Linked list of symbol free callbacks |
92 | | * |
93 | | * @note Is linked list to retain insertion order. We don't expect huge numbers |
94 | | * of callbacks so there shouldn't be efficiency issues. |
95 | | */ |
96 | | fr_dlist_head_t sym_free; |
97 | | |
98 | | bool do_dlclose; //!< dlclose modules when we're done with them. |
99 | | |
100 | | bool do_static; //!< Do all symbol resolution using the special |
101 | | ///< RTLD_DEFAULT handle, instead of attempting |
102 | | ///< to load modules using dlopen(). This is |
103 | | ///< useful when FreeRADIUS has been built as |
104 | | ///< a monolithic binary. |
105 | | |
106 | | fr_rb_tree_t *tree; //!< Tree of shared objects loaded. |
107 | | |
108 | | void *uctx; //!< dl private extension data. |
109 | | |
110 | | bool uctx_free; //!< Free uctx when dl_loader_t is freed. |
111 | | |
112 | | bool defer_symbol_init; //!< Do not call dl_symbol_init in dl_loader_init. |
113 | | }; |
114 | | |
115 | | /** Global search path, prepended all dlopen paths |
116 | | */ |
117 | | static char *dl_global_libdir = NULL; |
118 | | |
119 | | static int8_t dl_symbol_init_cmp(void const *one, void const *two) |
120 | 0 | { |
121 | 0 | dl_symbol_init_t const *a = one, *b = two; |
122 | 0 | int ret; |
123 | |
|
124 | 0 | fr_assert(a && b); |
125 | |
|
126 | 0 | ret = ((void *)a->func > (void *)b->func) - ((void *)a->func < (void *)b->func); |
127 | 0 | if (ret != 0) return ret; |
128 | | |
129 | 0 | ret = (a->symbol && !b->symbol) - (!a->symbol && b->symbol); |
130 | 0 | if (ret != 0) return ret; |
131 | | |
132 | 0 | if (!a->symbol && !b->symbol) return 0; |
133 | | |
134 | | #ifdef STATIC_ANALYZER |
135 | | if (!fr_cond_assert(a->symbol && b->symbol)) return 0; /* Bug in clang scan ? */ |
136 | | #endif |
137 | | |
138 | 0 | ret = strcmp(a->symbol, b->symbol); |
139 | 0 | return CMP(ret, 0); |
140 | 0 | } |
141 | | |
142 | | static int8_t dl_symbol_free_cmp(void const *one, void const *two) |
143 | 0 | { |
144 | 0 | dl_symbol_free_t const *a = one, *b = two; |
145 | 0 | int ret; |
146 | |
|
147 | 0 | fr_assert(a && b); |
148 | |
|
149 | 0 | ret = ((void *)a->func > (void *)b->func) - ((void *)a->func < (void *)b->func); |
150 | 0 | if (ret != 0) return ret; |
151 | | |
152 | 0 | ret = (a->symbol && !b->symbol) - (!a->symbol && b->symbol); |
153 | 0 | if (ret != 0) return ret; |
154 | | |
155 | 0 | if (!a->symbol && !b->symbol) return 0; |
156 | | |
157 | | #ifdef STATIC_ANALYZER |
158 | | if (!fr_cond_assert(a->symbol && b->symbol)) return 0; /* Bug in clang scan ? */ |
159 | | #endif |
160 | | |
161 | 0 | ret = strcmp(a->symbol, b->symbol); |
162 | 0 | return CMP(ret, 0); |
163 | 0 | } |
164 | | |
165 | | /** Compare the name of two dl_t |
166 | | * |
167 | | */ |
168 | | static int8_t dl_handle_cmp(void const *one, void const *two) |
169 | 135 | { |
170 | 135 | int ret; |
171 | | |
172 | 135 | ret = strcmp(((dl_t const *)one)->name, ((dl_t const *)two)->name); |
173 | 135 | return CMP(ret, 0); |
174 | 135 | } |
175 | | |
176 | | /** Utility function to dlopen the library containing a particular symbol |
177 | | * |
178 | | * @note Not really part of our 'dl' API, just a convenience function. |
179 | | * |
180 | | * @param[in] sym_name to resolve. |
181 | | * @param[in] flags to pass to dlopen. |
182 | | * @return |
183 | | * - NULL on error. |
184 | | * - A new handle on success. |
185 | | */ |
186 | | void *dl_open_by_sym(char const *sym_name, int flags) |
187 | 0 | { |
188 | 0 | Dl_info info; |
189 | 0 | void *sym; |
190 | 0 | void *handle; |
191 | | |
192 | | /* |
193 | | * Resolve the test symbol in our own symbol space by |
194 | | * iterating through all the libraries. |
195 | | * This might be slow. Don't do this at runtime! |
196 | | */ |
197 | 0 | sym = dlsym(RTLD_DEFAULT, sym_name); |
198 | 0 | if (!sym) { |
199 | 0 | fr_strerror_printf("Can't resolve symbol %s", sym_name); |
200 | 0 | return NULL; |
201 | 0 | } |
202 | | |
203 | | /* |
204 | | * Lookup the library the symbol belongs to |
205 | | */ |
206 | 0 | if (dladdr(sym, &info) == 0) { |
207 | 0 | fr_strerror_printf("Failed retrieving info for \"%s\" (%p)", sym_name, sym); |
208 | 0 | return NULL; |
209 | 0 | } |
210 | | |
211 | 0 | handle = dlopen(info.dli_fname, flags); |
212 | 0 | if (!handle) { |
213 | 0 | fr_strerror_printf("Failed loading \"%s\": %s", info.dli_fname, dlerror()); |
214 | 0 | return NULL; |
215 | 0 | } |
216 | | |
217 | 0 | return handle; |
218 | 0 | } |
219 | | |
220 | | /** Walk over the registered init callbacks, searching for the symbols they depend on |
221 | | * |
222 | | * Allows code outside of the dl API to register initialisation functions that get |
223 | | * executed depending on whether the dl exports a particular symbol. |
224 | | * |
225 | | * This cuts down the amount of boilerplate code in 'mod_load' functions. |
226 | | * |
227 | | * @param[in] dl_loader Tree of dynamically loaded libraries, and callbacks. |
228 | | * @param[in] dl to search for symbols in. |
229 | | * @return |
230 | | * - 0 continue walking. |
231 | | * - -1 error. |
232 | | */ |
233 | | int dl_symbol_init(dl_loader_t *dl_loader, dl_t const *dl) |
234 | 39 | { |
235 | 39 | dl_symbol_init_t *init = NULL; |
236 | 39 | void *sym = NULL; |
237 | 39 | char buffer[256]; |
238 | | |
239 | 78 | while ((init = fr_dlist_next(&dl_loader->sym_init, init))) { |
240 | 39 | if (init->symbol) { |
241 | 39 | char *p; |
242 | | |
243 | 39 | snprintf(buffer, sizeof(buffer), "%s_%s", dl->name, init->symbol); |
244 | | |
245 | | /* |
246 | | * '-' is not a valid symbol character in |
247 | | * C. But "libfreeradius-radius" is a |
248 | | * valid library name. So we hash things together. |
249 | | */ |
250 | 1.38k | for (p = buffer; *p != '\0'; p++) { |
251 | 1.34k | if (*p == '-') *p = '_'; |
252 | 1.34k | } |
253 | | |
254 | 39 | sym = dlsym(dl->handle, buffer); |
255 | 39 | if (!sym) { |
256 | 21 | continue; |
257 | 21 | } |
258 | 39 | } |
259 | | |
260 | 18 | if (init->func(dl, sym, init->uctx) < 0) { |
261 | 0 | fr_strerror_printf("Initialiser \"%s\" failed", buffer); |
262 | 0 | return -1; |
263 | 0 | } |
264 | 18 | } |
265 | | |
266 | 39 | return 0; |
267 | 39 | } |
268 | | |
269 | | /** Walk over the registered init callbacks, searching for the symbols they depend on |
270 | | * |
271 | | * Allows code outside of the dl API to register free functions that get |
272 | | * executed depending on whether the dl exports a particular symbol. |
273 | | * |
274 | | * This cuts down the amount of boilerplate code in 'mod_unload' functions. |
275 | | * |
276 | | * @param[in] dl_loader Tree of dynamically loaded libraries, and callbacks. |
277 | | * @param[in] dl to search for symbols in. |
278 | | */ |
279 | | static int dl_symbol_free(dl_loader_t *dl_loader, dl_t const *dl) |
280 | 39 | { |
281 | 39 | dl_symbol_free_t *free = NULL; |
282 | 39 | void *sym = NULL; |
283 | | |
284 | 39 | while ((free = fr_dlist_next(&dl_loader->sym_free, free))) { |
285 | 0 | if (free->symbol) { |
286 | 0 | char *sym_name = NULL; |
287 | |
|
288 | 0 | sym_name = talloc_typed_asprintf(NULL, "%s_%s", dl->name, free->symbol); |
289 | 0 | if (!sym_name) return -1; |
290 | | |
291 | 0 | sym = dlsym(dl->handle, sym_name); |
292 | 0 | talloc_free(sym_name); |
293 | |
|
294 | 0 | if (!sym) continue; |
295 | 0 | } |
296 | | |
297 | 0 | free->func(dl, sym, free->uctx); |
298 | 0 | } |
299 | | |
300 | 39 | return 0; |
301 | 39 | } |
302 | | |
303 | | /** Register a callback to execute when a dl with a particular symbol is first loaded |
304 | | * |
305 | | * @note Will replace ctx data for callbacks with the same symbol/func. |
306 | | * |
307 | | * @param[in] dl_loader Tree of dynamically loaded libraries, and callbacks. |
308 | | * @param[in] priority Execution priority. Callbacks with a higher priority get |
309 | | * called first. |
310 | | * @param[in] symbol that determines whether func should be called. "<modname>_" is |
311 | | * added as a prefix to the symbol. The prefix is added because |
312 | | * some dls are loaded with RTLD_GLOBAL into the global symbol |
313 | | * space, so the symbols they export must be unique. |
314 | | * May be NULL to always call the function. |
315 | | * @param[in] func to register. Called when dl is loaded. |
316 | | * @param[in] uctx to pass to func. |
317 | | * @return |
318 | | * - 0 on success (or already registered). |
319 | | * - -1 on failure. |
320 | | */ |
321 | | int dl_symbol_init_cb_register(dl_loader_t *dl_loader, unsigned int priority, |
322 | | char const *symbol, dl_onload_t func, void *uctx) |
323 | 18 | { |
324 | 18 | dl_symbol_init_t *n; |
325 | | |
326 | 18 | dl_symbol_init_cb_unregister(dl_loader, symbol, func); |
327 | | |
328 | 18 | n = talloc(dl_loader, dl_symbol_init_t); |
329 | 18 | if (unlikely(!n)) return -1; |
330 | 18 | *n = (dl_symbol_init_t){ |
331 | 18 | .priority = priority, |
332 | 18 | .symbol = symbol, |
333 | 18 | .func = func, |
334 | 18 | .uctx = uctx |
335 | 18 | }; |
336 | | |
337 | 18 | fr_dlist_foreach(&dl_loader->sym_init, dl_symbol_init_t, p) { |
338 | 0 | if (p->priority < priority) { |
339 | 0 | fr_dlist_insert_before(&dl_loader->sym_init, p, n); |
340 | 0 | n = NULL; |
341 | 0 | break; |
342 | 0 | } |
343 | 0 | } |
344 | 18 | if (n) fr_dlist_insert_tail(&dl_loader->sym_init, n); |
345 | | |
346 | 18 | return 0; |
347 | 18 | } |
348 | | |
349 | | /** Unregister an callback that was to be executed when a dl was first loaded |
350 | | * |
351 | | * @param[in] dl_loader Tree of dynamically loaded libraries, and callbacks. |
352 | | * @param[in] symbol the callback is attached to. |
353 | | * @param[in] func the callback. |
354 | | */ |
355 | | void dl_symbol_init_cb_unregister(dl_loader_t *dl_loader, char const *symbol, dl_onload_t func) |
356 | 18 | { |
357 | 18 | dl_symbol_init_t *found = NULL, find = { .symbol = symbol, .func = func }; |
358 | | |
359 | 18 | while ((found = fr_dlist_next(&dl_loader->sym_init, found)) && (dl_symbol_init_cmp(&find, found) != 0)); |
360 | 18 | if (found) { |
361 | 0 | fr_dlist_remove(&dl_loader->sym_init, found); |
362 | 0 | talloc_free(found); |
363 | 0 | } |
364 | 18 | } |
365 | | |
366 | | /** Register a callback to execute when a dl with a particular symbol is unloaded |
367 | | * |
368 | | * @note Will replace ctx data for callbacks with the same symbol/func. |
369 | | * |
370 | | * @param[in] dl_loader Tree of dynamically loaded libraries, and callbacks. |
371 | | * @param[in] priority Execution priority. Callbacks with a higher priority get |
372 | | * called first. |
373 | | * @param[in] symbol that determines whether func should be called. "<modname>_" is |
374 | | * added as a prefix to the symbol. The prefix is added because |
375 | | * some dls are loaded with RTLD_GLOBAL into the global symbol |
376 | | * space, so the symbols they export must be unique. |
377 | | * May be NULL to always call the function. |
378 | | * @param[in] func to register. Called then dl is unloaded. |
379 | | * @param[in] uctx to pass to func. |
380 | | * @return |
381 | | * - 0 on success (or already registered). |
382 | | * - -1 on failure. |
383 | | */ |
384 | | int dl_symbol_free_cb_register(dl_loader_t *dl_loader, unsigned int priority, |
385 | | char const *symbol, dl_unload_t func, void *uctx) |
386 | 0 | { |
387 | 0 | dl_symbol_free_t *n; |
388 | |
|
389 | 0 | dl_symbol_free_cb_unregister(dl_loader, symbol, func); |
390 | |
|
391 | 0 | n = talloc(dl_loader, dl_symbol_free_t); |
392 | 0 | if (!n) return -1; |
393 | | |
394 | 0 | *n = (dl_symbol_free_t){ |
395 | 0 | .priority = priority, |
396 | 0 | .symbol = symbol, |
397 | 0 | .func = func, |
398 | 0 | .uctx = uctx |
399 | 0 | }; |
400 | |
|
401 | 0 | fr_dlist_foreach(&dl_loader->sym_free, dl_symbol_free_t, p) { |
402 | 0 | if (p->priority < priority) { |
403 | 0 | fr_dlist_insert_before(&dl_loader->sym_free, p, n); |
404 | 0 | n = NULL; |
405 | 0 | break; |
406 | 0 | } |
407 | 0 | } |
408 | 0 | if (n) fr_dlist_insert_tail(&dl_loader->sym_free, n); |
409 | |
|
410 | 0 | return 0; |
411 | 0 | } |
412 | | |
413 | | /** Unregister an callback that was to be executed when a dl was unloaded |
414 | | * |
415 | | * @param[in] dl_loader Tree of dynamically loaded libraries, and callbacks. |
416 | | * @param[in] symbol the callback is attached to. |
417 | | * @param[in] func the callback. |
418 | | */ |
419 | | void dl_symbol_free_cb_unregister(dl_loader_t *dl_loader, char const *symbol, dl_unload_t func) |
420 | 0 | { |
421 | 0 | dl_symbol_free_t *found = NULL, find = { .symbol = symbol, .func = func }; |
422 | |
|
423 | 0 | while ((found = fr_dlist_next(&dl_loader->sym_free, found)) && (dl_symbol_free_cmp(&find, found) != 0)); |
424 | 0 | if (found) { |
425 | 0 | fr_dlist_remove(&dl_loader->sym_free, found); |
426 | 0 | talloc_free(found); |
427 | 0 | } |
428 | 0 | } |
429 | | |
430 | | /** Free a dl |
431 | | * |
432 | | * Close dl's dlhandle, unloading it. |
433 | | * |
434 | | * @param[in] dl to close. |
435 | | * @return 0. |
436 | | */ |
437 | | static int _dl_free(dl_t *dl) |
438 | 39 | { |
439 | 39 | dl = talloc_get_type_abort(dl, dl_t); |
440 | | |
441 | 39 | dl_symbol_free(dl->loader, dl); |
442 | | |
443 | | /* |
444 | | * Only dlclose() handle if we're *NOT* running under valgrind |
445 | | * as it unloads the symbols valgrind needs. |
446 | | */ |
447 | 39 | if (dl->loader->do_dlclose) dlclose(dl->handle); /* ignore any errors */ |
448 | | |
449 | 39 | dl->handle = NULL; |
450 | | |
451 | 39 | if (dl->in_tree) fr_rb_delete(dl->loader->tree, dl); |
452 | | |
453 | 39 | return 0; |
454 | 39 | } |
455 | | |
456 | | /** Search for a dl's shared object in various locations |
457 | | * |
458 | | * @note You must call dl_symbol_init when ready to call autoloader callbacks. |
459 | | * |
460 | | * @param[in] dl_loader Tree of dynamically loaded libraries, and callbacks. |
461 | | * @param[in] name of library to load. May be a relative path. |
462 | | * @param[in] uctx Data to store within the dl_t. |
463 | | * @param[in] uctx_free talloc_free the passed in uctx data if this |
464 | | * dl_t is freed. |
465 | | * @return |
466 | | * - A new dl_t on success, or a pointer to an existing |
467 | | * one with the reference count increased. |
468 | | * - NULL on error. |
469 | | */ |
470 | | dl_t *dl_by_name(dl_loader_t *dl_loader, char const *name, void *uctx, bool uctx_free) |
471 | 39 | { |
472 | 39 | int flags = RTLD_NOW; |
473 | 39 | void *handle = NULL; |
474 | 39 | char const *search_path; |
475 | 39 | dl_t *dl; |
476 | | |
477 | | /* |
478 | | * There's already something in the tree, |
479 | | * just return that instead. |
480 | | */ |
481 | 39 | dl = fr_rb_find(dl_loader->tree, &(dl_t){ .name = name }); |
482 | 39 | if (dl) { |
483 | 0 | talloc_increase_ref_count(dl); |
484 | 0 | return dl; |
485 | 0 | } |
486 | | |
487 | | /* |
488 | | * Get a reference to the internal RTLD_HANDLE, |
489 | | * which is used to search through all current loaded |
490 | | * modules. |
491 | | * |
492 | | * This is useful for static builds which don't actually |
493 | | * want to dlopen anything, and just want to look at the |
494 | | * current global symbol table to load everything |
495 | | * from there. |
496 | | */ |
497 | 39 | if (dl_loader->do_static) { |
498 | 0 | handle = dlopen(NULL, 0); |
499 | 0 | if (!handle) { |
500 | 0 | fr_strerror_printf("Failed opening RTLD_DEFAULT handle - %s", dlerror()); |
501 | 0 | return NULL; |
502 | 0 | } |
503 | 0 | goto do_symbol_check; |
504 | 0 | } |
505 | | |
506 | 39 | flags |= RTLD_LOCAL; |
507 | | |
508 | | /* |
509 | | * Forces dlopened libraries to resolve symbols within |
510 | | * their local symbol tables instead of the global symbol |
511 | | * table. |
512 | | * |
513 | | * May help resolve issues with symbol conflicts. |
514 | | */ |
515 | | #if defined(RTLD_DEEPBIND) && !defined(__SANITIZE_ADDRESS__) |
516 | | flags |= RTLD_DEEPBIND; |
517 | | #endif |
518 | | |
519 | 39 | fr_strerror_clear(); /* clear error buffer */ |
520 | | |
521 | | /* |
522 | | * Bind all the symbols *NOW* so we don't hit errors later |
523 | | */ |
524 | 39 | flags |= RTLD_NOW; |
525 | | |
526 | 39 | search_path = dl_search_path(dl_loader); |
527 | | |
528 | | /* |
529 | | * Prefer loading our libraries by absolute path. |
530 | | */ |
531 | 39 | if (search_path) { |
532 | 39 | char *ctx, *paths, *path; |
533 | 39 | char *p; |
534 | 39 | char *dlerror_txt = NULL; |
535 | | |
536 | 39 | fr_strerror_clear(); |
537 | | |
538 | 39 | ctx = paths = talloc_typed_strdup(NULL, search_path); |
539 | 39 | while ((path = strsep(&paths, ":")) != NULL) { |
540 | | /* |
541 | | * Trim the trailing slash |
542 | | */ |
543 | 39 | p = strrchr(path, '/'); |
544 | 39 | if (p && ((p[1] == '\0') || (p[1] == ':'))) *p = '\0'; |
545 | | |
546 | 39 | path = talloc_typed_asprintf(ctx, "%s/%s%s", path, name, DL_EXTENSION); |
547 | 39 | handle = dlopen(path, flags); |
548 | 39 | talloc_free(path); |
549 | 39 | if (handle) break; |
550 | | |
551 | | /* |
552 | | * There's no dlopenat(), so one can't use it and later |
553 | | * check acessatat() to avoid toctou. |
554 | | * |
555 | | * The only indication of why dlopen() failed is thus the |
556 | | * contents of the string dlerror() returns, but all the |
557 | | * man page says about that is that it's "human readable", |
558 | | * NUL-terminated, and doesn't end with a newline. |
559 | | * |
560 | | * The following attempts to use the dlerror() output to |
561 | | * determine whether dlopen() failed because path doesn't |
562 | | * exist. This goes beyond what the API specifies (Hyrum's |
563 | | * Law strikes again!), but the alternatives are |
564 | | * |
565 | | * 1. looking into the data structure dlerror() uses |
566 | | * 2. let the toctou remain |
567 | | * |
568 | | * both of which seem worse. |
569 | | */ |
570 | | |
571 | | /* |
572 | | * If the file doesn't exist, continue with the next element |
573 | | * of "path". Otherwise, stop looking for more libraries |
574 | | * and instead complain about access permissions. |
575 | | */ |
576 | 0 | dlerror_txt = dlerror(); |
577 | | |
578 | | /* |
579 | | * Yes, this really is the only way of getting the errno |
580 | | * from the dlopen API. |
581 | | */ |
582 | 0 | if (strstr(dlerror_txt, fr_syserror_simple(ENOENT)) != NULL) { |
583 | | #ifndef __linux__ |
584 | | int access_mode = R_OK | X_OK; |
585 | | |
586 | | # ifdef AT_ACCESS |
587 | | access_mode |= AT_ACCESS; |
588 | | # endif |
589 | | if (access(path, access_mode) < 0 && errno == ENOENT) continue; |
590 | | #endif |
591 | 0 | fr_strerror_printf_push("Access check failed: %s", dlerror_txt); |
592 | 0 | break; |
593 | 0 | } |
594 | | |
595 | | /* |
596 | | * We're reliant on dlerror_txt from the loop, |
597 | | * because once dlerror() is called the error |
598 | | * is cleared. |
599 | | * |
600 | | * We need to push every error because we |
601 | | * don't know which one would be useful |
602 | | * in diagnosing the underlying cause of the |
603 | | * load failure. |
604 | | */ |
605 | 0 | fr_strerror_printf_push("%s", dlerror_txt ? dlerror_txt : "unknown dlopen error"); |
606 | 0 | } |
607 | | |
608 | | /* |
609 | | * No element of "path" had the library. Return |
610 | | * the error from the last dlopen(). |
611 | | */ |
612 | 39 | if (!handle) { |
613 | 0 | talloc_free(ctx); |
614 | 0 | return NULL; |
615 | 0 | } |
616 | | |
617 | 39 | fr_strerror_clear(); /* Don't leave spurious errors in the buffer */ |
618 | | |
619 | 39 | talloc_free(ctx); |
620 | 39 | } else { |
621 | 0 | char buffer[2048]; |
622 | |
|
623 | 0 | strlcpy(buffer, name, sizeof(buffer)); |
624 | | /* |
625 | | * FIXME: Make this configurable... |
626 | | */ |
627 | 0 | strlcat(buffer, DL_EXTENSION, sizeof(buffer)); |
628 | |
|
629 | 0 | handle = dlopen(buffer, flags); |
630 | 0 | if (!handle) { |
631 | 0 | char *error = dlerror(); |
632 | | |
633 | | /* |
634 | | * Append the error |
635 | | */ |
636 | 0 | fr_strerror_printf_push("%s", error); |
637 | 0 | return NULL; |
638 | 0 | } |
639 | 0 | } |
640 | | |
641 | 39 | do_symbol_check: |
642 | 39 | dl = talloc(dl_loader, dl_t); |
643 | 39 | if (unlikely(!dl)) { |
644 | 0 | dlclose(handle); |
645 | 0 | return NULL; |
646 | 0 | } |
647 | 39 | *dl = (dl_t){ |
648 | 39 | .name = talloc_typed_strdup(dl, name), |
649 | 39 | .handle = handle, |
650 | 39 | .loader = dl_loader, |
651 | 39 | .uctx = uctx, |
652 | 39 | .uctx_free = uctx_free |
653 | 39 | }; |
654 | 39 | talloc_set_destructor(dl, _dl_free); |
655 | | |
656 | 39 | dl->in_tree = fr_rb_insert(dl_loader->tree, dl); |
657 | 39 | if (!dl->in_tree) { |
658 | 0 | talloc_free(dl); |
659 | 0 | return NULL; |
660 | 0 | } |
661 | | |
662 | 39 | if (!dl_loader->defer_symbol_init) dl_symbol_init(dl_loader, dl); |
663 | | |
664 | 39 | return dl; |
665 | 39 | } |
666 | | |
667 | | /** "free" a dl handle, possibly actually freeing it, and unloading the library |
668 | | * |
669 | | * This function should be used to explicitly free a dl. |
670 | | * |
671 | | * Because dls are reference counted, it may not actually free the memory |
672 | | * or unload the library, but it will reduce the reference count. |
673 | | * |
674 | | * @return |
675 | | * - 0 if the dl was actually freed. |
676 | | * - >0 the number of remaining references. |
677 | | */ |
678 | | int dl_free(dl_t const *dl) |
679 | 39 | { |
680 | 39 | if (!dl) return 0; |
681 | | |
682 | 39 | return talloc_decrease_ref_count(talloc_get_type_abort_const(dl, dl_t)); |
683 | 39 | } |
684 | | |
685 | | static int _dl_loader_free(dl_loader_t *dl_loader) |
686 | 18 | { |
687 | 18 | int ret = 0; |
688 | | |
689 | 18 | if (dl_loader->uctx_free) { |
690 | 0 | ret = talloc_free(dl_loader->uctx); |
691 | 0 | if (ret != 0) goto finish; |
692 | 0 | } |
693 | | |
694 | | /* |
695 | | * Prevent freeing if we still have dls loaded |
696 | | * We do reference counting, we know exactly what |
697 | | * should still be active. |
698 | | */ |
699 | 18 | if (fr_rb_num_elements(dl_loader->tree) > 0) { |
700 | 0 | #ifndef NDEBUG |
701 | 0 | fr_rb_iter_inorder_t iter; |
702 | 0 | void *data; |
703 | | |
704 | | /* |
705 | | * Yes, this is the correct call order |
706 | | */ |
707 | 0 | for (data = fr_rb_iter_init_inorder(&iter, dl_loader->tree); |
708 | 0 | data; |
709 | 0 | data = fr_rb_iter_next_inorder(&iter)) { |
710 | 0 | dl_t *dl = talloc_get_type_abort(data, dl_t); |
711 | |
|
712 | 0 | fr_strerror_printf_push(" %s (%zu)", dl->name, talloc_reference_count(dl)); |
713 | 0 | } |
714 | |
|
715 | 0 | fr_strerror_printf_push("Refusing to cleanup dl loader, the following dynamically loaded " |
716 | 0 | "libraries are still in use:"); |
717 | 0 | #endif |
718 | 0 | ret = -1; |
719 | 0 | goto finish; |
720 | 0 | } |
721 | | |
722 | 18 | finish: |
723 | 18 | return ret; |
724 | 18 | } |
725 | | |
726 | | /** Return current library path |
727 | | * |
728 | | */ |
729 | | char const *dl_search_path(dl_loader_t *dl_loader) |
730 | 39 | { |
731 | 39 | char *env; |
732 | 39 | fr_sbuff_t *search_path = NULL; |
733 | | |
734 | | /* |
735 | | * The search path in this order [env:][global:]dl_loader->lib_dir |
736 | | */ |
737 | | |
738 | | /* |
739 | | * Create a thread-local extensible buffer to |
740 | | * store library search_path data. |
741 | | * |
742 | | * This is created once per-thread (the first time |
743 | | * this function is called), and freed when the |
744 | | * thread exits. |
745 | | */ |
746 | 39 | FR_SBUFF_TALLOC_THREAD_LOCAL(&search_path, 16, PATH_MAX); |
747 | | |
748 | | /* |
749 | | * Apple removed support for DYLD_LIBRARY_PATH in rootless mode. |
750 | | */ |
751 | 39 | env = getenv("FR_LIBRARY_PATH"); |
752 | 39 | if (env && fr_sbuff_in_sprintf(search_path, "%s:", env) < 0) return NULL; |
753 | | |
754 | 39 | if (dl_global_libdir && fr_sbuff_in_sprintf(search_path, "%s:", dl_global_libdir) < 0) return NULL; |
755 | | |
756 | 39 | if (fr_sbuff_in_strcpy(search_path, dl_loader->lib_dir) < 0) return NULL; |
757 | | |
758 | 39 | return fr_sbuff_start(search_path); |
759 | 39 | } |
760 | | |
761 | | /** Set the global library path |
762 | | * |
763 | | * @param[in] lib_dir ":" separated list of paths to search for libraries in. |
764 | | * @return |
765 | | * - 0 on success. |
766 | | * - -1 on failure. |
767 | | */ |
768 | | int dl_search_global_path_set(char const *lib_dir) |
769 | 38 | { |
770 | 38 | if (dl_global_libdir) TALLOC_FREE(dl_global_libdir); |
771 | | |
772 | 38 | dl_global_libdir = talloc_typed_strdup(NULL, lib_dir); |
773 | 38 | if (!dl_global_libdir) { |
774 | 0 | fr_strerror_const("Failed allocating memory for global dl search path"); |
775 | 0 | return -1; |
776 | 0 | } |
777 | | |
778 | 38 | fr_atexit_global_once(NULL, fr_atexit_talloc_free, dl_global_libdir); |
779 | | |
780 | 38 | return 0; |
781 | 38 | } |
782 | | |
783 | | /** Set the current library path |
784 | | * |
785 | | * @param[in] dl_loader to add search path component for. |
786 | | * @param[in] lib_dir A ":" separated list of paths to search for libraries in. |
787 | | */ |
788 | | int dl_search_path_set(dl_loader_t *dl_loader, char const *lib_dir) |
789 | 0 | { |
790 | 0 | char const *old; |
791 | |
|
792 | 0 | old = dl_loader->lib_dir; |
793 | |
|
794 | 0 | dl_loader->lib_dir = talloc_strdup(dl_loader, lib_dir); |
795 | 0 | if (!dl_loader->lib_dir) { |
796 | 0 | fr_strerror_const("Failed allocating memory for dl search path"); |
797 | 0 | return -1; |
798 | 0 | } |
799 | | |
800 | 0 | talloc_const_free(old); |
801 | |
|
802 | 0 | return 0; |
803 | 0 | } |
804 | | |
805 | | /** Append a new search path component to the library search path |
806 | | * |
807 | | * @param[in] dl_loader to add search path component for. |
808 | | * @param[in] lib_dir to add. Does not require a ":" prefix. |
809 | | * @return |
810 | | * - 0 on success. |
811 | | * - -1 on failure. |
812 | | */ |
813 | | int dl_search_path_prepend(dl_loader_t *dl_loader, char const *lib_dir) |
814 | 0 | { |
815 | 0 | char *new; |
816 | |
|
817 | 0 | if (!dl_loader->lib_dir) { |
818 | 0 | dl_loader->lib_dir = talloc_strdup(dl_loader->lib_dir, lib_dir); |
819 | 0 | if (!dl_loader->lib_dir) { |
820 | 0 | oom: |
821 | 0 | fr_strerror_const("Failed allocating memory for dl search path"); |
822 | 0 | return -1; |
823 | 0 | } |
824 | 0 | return 0; |
825 | 0 | } |
826 | | |
827 | 0 | new = talloc_asprintf(dl_loader->lib_dir, "%s:%s", lib_dir, dl_loader->lib_dir); |
828 | 0 | if (!new) goto oom; |
829 | | |
830 | 0 | dl_loader->lib_dir = new; |
831 | |
|
832 | 0 | return 0; |
833 | 0 | } |
834 | | |
835 | | /** Append a new search path component to the library search path |
836 | | * |
837 | | * @param[in] dl_loader to add search path component for. |
838 | | * @param[in] lib_dir to add. Does not require a ":" prefix. |
839 | | * @return |
840 | | * - 0 on success. |
841 | | * - -1 on failure. |
842 | | */ |
843 | | int dl_search_path_append(dl_loader_t *dl_loader, char const *lib_dir) |
844 | 0 | { |
845 | 0 | char *new; |
846 | |
|
847 | 0 | if (!dl_loader->lib_dir) { |
848 | 0 | dl_loader->lib_dir = talloc_strdup(dl_loader->lib_dir, lib_dir); |
849 | 0 | if (!dl_loader->lib_dir) { |
850 | 0 | oom: |
851 | 0 | fr_strerror_const("Failed allocating memory for dl search path"); |
852 | 0 | return -1; |
853 | 0 | } |
854 | 0 | return 0; |
855 | 0 | } |
856 | | |
857 | 0 | new = talloc_asprintf_append_buffer(dl_loader->lib_dir, ":%s", lib_dir); |
858 | 0 | if (!new) goto oom; |
859 | | |
860 | 0 | dl_loader->lib_dir = new; |
861 | |
|
862 | 0 | return 0; |
863 | 0 | } |
864 | | |
865 | | /** Retrieve the uctx from a dl_loader |
866 | | * |
867 | | */ |
868 | | void *dl_loader_uctx(dl_loader_t *dl_loader) |
869 | 0 | { |
870 | 0 | return dl_loader->uctx; |
871 | 0 | } |
872 | | |
873 | | /** Initialise structures needed by the dynamic linker |
874 | | * |
875 | | * @param[in] ctx To bind lifetime of dl_loader_t too. |
876 | | * @param[in] uctx API client opaque data to store in dl_loader_t. |
877 | | * @param[in] uctx_free Call talloc_free() on uctx when the dl_loader_t |
878 | | * is freed. |
879 | | * @param[in] defer_symbol_init If true, it is up to the caller to call |
880 | | * #dl_symbol_init after calling #dl_by_name. |
881 | | * This prevents any of the registered callbacks |
882 | | * from executing until #dl_symbol_init is |
883 | | * called explicitly. |
884 | | */ |
885 | | dl_loader_t *dl_loader_init(TALLOC_CTX *ctx, void *uctx, bool uctx_free, bool defer_symbol_init) |
886 | 38 | { |
887 | 38 | dl_loader_t *dl_loader; |
888 | | |
889 | 38 | dl_loader = talloc_zero(NULL, dl_loader_t); |
890 | 38 | if (!dl_loader) { |
891 | 0 | fr_strerror_const("Failed allocating dl_loader"); |
892 | 0 | return NULL; |
893 | 0 | } |
894 | | |
895 | 38 | dl_loader->tree = fr_rb_inline_talloc_alloc(dl_loader, dl_t, node, dl_handle_cmp, NULL); |
896 | 38 | if (!dl_loader->tree) { |
897 | 0 | fr_strerror_const("Failed initialising dl->tree"); |
898 | 0 | error: |
899 | 0 | TALLOC_FREE(dl_loader); |
900 | 0 | return NULL; |
901 | 0 | } |
902 | | |
903 | 38 | talloc_link_ctx(ctx, dl_loader); |
904 | | |
905 | 38 | dl_loader->lib_dir = talloc_strdup(dl_loader, fr_path_default_lib_dir()); |
906 | 38 | if (!dl_loader->lib_dir) { |
907 | 0 | fr_strerror_const("Failed allocating memory for dl search path"); |
908 | 0 | goto error; |
909 | 0 | } |
910 | | |
911 | 38 | talloc_set_destructor(dl_loader, _dl_loader_free); |
912 | | |
913 | | /* |
914 | | * Run this now to avoid bizarre issues |
915 | | * with the talloc atexit handlers firing |
916 | | * in the child, and that causing issues. |
917 | | */ |
918 | 38 | dl_loader->do_dlclose = (!RUNNING_ON_VALGRIND && (fr_get_lsan_state() != 1)); |
919 | 38 | dl_loader->uctx = uctx; |
920 | 38 | dl_loader->uctx_free = uctx_free; |
921 | 38 | dl_loader->defer_symbol_init = defer_symbol_init; |
922 | | |
923 | | /* |
924 | | * It's not clear yet whether we still want |
925 | | * some dynamic loading capability in |
926 | | * emscripten, so keep this as a potentially |
927 | | * runtime toggle for now. |
928 | | */ |
929 | | #ifdef __EMSCRIPTEN__ |
930 | | dl_loader->do_static = true; |
931 | | #else |
932 | 38 | dl_loader->do_static = false; |
933 | 38 | #endif |
934 | 38 | fr_dlist_init(&dl_loader->sym_init, dl_symbol_init_t, entry); |
935 | 38 | fr_dlist_init(&dl_loader->sym_free, dl_symbol_free_t, entry); |
936 | | |
937 | 38 | return dl_loader; |
938 | 38 | } |
939 | | |
940 | | /** Runtime override for doing static or dynamic module loading |
941 | | * |
942 | | * @param[in] dl_loader to configure. |
943 | | * @param[in] do_static If true, all dlopen calls result in a |
944 | | * reference to RTLD_DEFAULT being returned |
945 | | * which allows all the dynamic loading |
946 | | * infrastructure to worth correctly with |
947 | | * a monolithic binary. |
948 | | * @return The previous value of do_static |
949 | | */ |
950 | | bool dl_loader_set_static(dl_loader_t *dl_loader, bool do_static) |
951 | 0 | { |
952 | 0 | bool old = dl_loader->do_static; |
953 | |
|
954 | 0 | dl_loader->do_static = do_static; |
955 | |
|
956 | 0 | return old; |
957 | 0 | } |
958 | | |
959 | | /** Called from a debugger to print information about a dl_loader |
960 | | * |
961 | | */ |
962 | | void dl_loader_debug(dl_loader_t *dl) |
963 | 0 | { |
964 | 0 | FR_FAULT_LOG("dl_loader %p", dl); |
965 | 0 | FR_FAULT_LOG("lib_dir : %s", dl->lib_dir); |
966 | 0 | FR_FAULT_LOG("do_dlclose : %s", dl->do_dlclose ? "yes" : "no"); |
967 | 0 | FR_FAULT_LOG("uctx : %p", dl->uctx); |
968 | 0 | FR_FAULT_LOG("uctx_free : %s", dl->do_dlclose ? "yes" : "no"); |
969 | 0 | FR_FAULT_LOG("defer_symbol_init : %s", dl->defer_symbol_init ? "yes" : "no"); |
970 | |
|
971 | 0 | fr_dlist_foreach(&dl->sym_init, dl_symbol_init_t, sym) { |
972 | 0 | FR_FAULT_LOG("symbol_init %s", sym->symbol ? sym->symbol : "<base>"); |
973 | 0 | FR_FAULT_LOG("\tpriority : %u", sym->priority); |
974 | 0 | FR_FAULT_LOG("\tfunc : %p", sym->func); |
975 | 0 | FR_FAULT_LOG("\tuctx : %p", sym->uctx); |
976 | 0 | } |
977 | |
|
978 | 0 | fr_dlist_foreach(&dl->sym_free, dl_symbol_free_t, sym) { |
979 | 0 | FR_FAULT_LOG("symbol_free %s", sym->symbol ? sym->symbol : "<base>"); |
980 | 0 | FR_FAULT_LOG("\tpriority : %u", sym->priority); |
981 | 0 | FR_FAULT_LOG("\tfunc : %p", sym->func); |
982 | 0 | FR_FAULT_LOG("\tuctx : %p", sym->uctx); |
983 | 0 | } |
984 | 0 | } |