Coverage Report

Created: 2026-11-12 06:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/util/dl.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
/**
18
 * $Id: 071aee3d39edff21590316e840419ad528015773 $
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: 071aee3d39edff21590316e840419ad528015773 $")
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_VALGRIND_H
38
#  include <valgrind/valgrind.h>
39
#else
40
112
#  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 fr_cmp_ret_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 CMP_EQ;
133
134
#ifdef STATIC_ANALYZER
135
  if (!fr_cond_assert(a->symbol && b->symbol)) return CMP_EQ; /* 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 fr_cmp_ret_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 CMP_EQ;
156
157
#ifdef STATIC_ANALYZER
158
  if (!fr_cond_assert(a->symbol && b->symbol)) return CMP_EQ; /* 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 fr_cmp_ret_t dl_handle_cmp(void const *one, void const *two)
169
204
{
170
204
  int ret;
171
172
204
  ret = strcmp(((dl_t const *)one)->name, ((dl_t const *)two)->name);
173
204
  return CMP(ret, 0);
174
204
}
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
66
{
235
66
  dl_symbol_init_t  *init = NULL;
236
66
  void      *sym = NULL;
237
66
  char      buffer[256] = "";
238
239
66
  while ((init = fr_dlist_next(&dl_loader->sym_init, init))) {
240
0
    if (init->symbol) {
241
0
      char *p;
242
243
0
      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
0
      for (p = buffer; *p != '\0'; p++) {
251
0
        if (*p == '-') *p = '_';
252
0
      }
253
254
0
      sym = dlsym(dl->handle, buffer);
255
0
      if (!sym) {
256
0
        continue;
257
0
      }
258
0
    }
259
260
0
    if (init->func(dl, sym, init->uctx) < 0) {
261
0
      fr_strerror_printf("Initialiser failed for %s", dl->name);
262
0
      return -1;
263
0
    }
264
0
  }
265
266
66
  return 0;
267
66
}
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
66
{
281
66
  dl_symbol_free_t  *free = NULL;
282
66
  void      *sym = NULL;
283
284
66
  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
66
  return 0;
301
66
}
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
0
{
324
0
  dl_symbol_init_t  *n;
325
326
0
  dl_symbol_init_cb_unregister(dl_loader, symbol, func);
327
328
0
  n = talloc(dl_loader, dl_symbol_init_t);
329
0
  if (unlikely(!n)) return -1;
330
0
  *n = (dl_symbol_init_t){
331
0
    .priority = priority,
332
0
    .symbol = symbol,
333
0
    .func = func,
334
0
    .uctx = uctx
335
0
  };
336
337
0
  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
0
  if (n) fr_dlist_insert_tail(&dl_loader->sym_init, n);
345
346
0
  return 0;
347
0
}
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
0
{
357
0
  dl_symbol_init_t  *found = NULL, find = { .symbol = symbol, .func = func };
358
359
0
  while ((found = fr_dlist_next(&dl_loader->sym_init, found)) && (dl_symbol_init_cmp(&find, found) != 0));
360
0
  if (found) {
361
0
    fr_dlist_remove(&dl_loader->sym_init, found);
362
0
    talloc_free(found);
363
0
  }
364
0
}
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
66
{
439
66
  dl = talloc_get_type_abort(dl, dl_t);
440
441
66
  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
66
  if (dl->loader->do_dlclose) dlclose(dl->handle);        /* ignore any errors */
448
449
66
  dl->handle = NULL;
450
451
66
  if (dl->in_tree) fr_rb_delete(dl->loader->tree, dl);
452
453
66
  return 0;
454
66
}
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
66
{
472
66
  int   flags = RTLD_NOW;
473
66
  void    *handle = NULL;
474
66
  char const  *search_path;
475
66
  dl_t    *dl;
476
477
  /*
478
   *  There's already something in the tree,
479
   *  just return that instead.
480
   */
481
66
  fr_rb_find((void **)&dl, dl_loader->tree, &(dl_t){ .name = name });
482
66
  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
66
  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
66
  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
66
  fr_strerror_clear();  /* clear error buffer */
520
521
  /*
522
   *  Bind all the symbols *NOW* so we don't hit errors later
523
   */
524
66
  flags |= RTLD_NOW;
525
526
66
  search_path = dl_search_path(dl_loader);
527
528
  /*
529
   *  Prefer loading our libraries by absolute path.
530
   */
531
66
  if (search_path) {
532
66
    char *ctx, *paths, *path;
533
66
    char *p;
534
66
    char *dlerror_txt = NULL;
535
536
66
    fr_strerror_clear();
537
538
66
    ctx = paths = talloc_strdup(NULL, search_path);
539
66
    while ((path = strsep(&paths, ":")) != NULL) {
540
66
      char *fullpath;
541
542
      /*
543
       *  Trim the trailing slash
544
       */
545
66
      p = strrchr(path, '/');
546
66
      if (p && ((p[1] == '\0') || (p[1] == ':'))) *p = '\0';
547
548
66
      fullpath = talloc_typed_asprintf(ctx, "%s/%s%s", path, name, DL_EXTENSION);
549
66
      handle = dlopen(fullpath, flags);
550
66
      talloc_free(fullpath);
551
66
      if (handle) break;
552
553
      /*
554
       *  There's no dlopenat(), so one can't use it and later
555
       *  check acessatat() to avoid toctou.
556
       *
557
       *  The only indication of why dlopen() failed is thus the
558
       *  contents of the string dlerror() returns, but all the
559
       *  man page says about that is that it's "human readable",
560
       *  NUL-terminated, and doesn't end with a newline.
561
       *
562
       *  The following attempts to use the dlerror() output to
563
       *  determine whether dlopen() failed because path doesn't
564
       *  exist. This goes beyond what the API specifies (Hyrum's
565
       *  Law strikes again!), but the alternatives are
566
       *
567
       *  1. looking into the data structure dlerror() uses
568
       *  2. let the toctou remain
569
       *
570
       *  both of which seem worse.
571
       */
572
573
      /*
574
       *  If the file doesn't exist, continue with the next element
575
       *  of  "path". Otherwise, stop looking for more libraries
576
       *  and instead complain about access permissions.
577
       */
578
0
      dlerror_txt = dlerror();
579
0
      if (!dlerror_txt) {
580
0
        fr_strerror_printf_push("Unknown error when trying directory %s", path);
581
0
        continue;
582
0
      }
583
584
      /*
585
       *  Yes, this really is the only way of getting the errno
586
       *  from the dlopen API.
587
       */
588
0
      if (strstr(dlerror_txt, fr_syserror_simple(ENOENT)) != NULL) {
589
#ifndef __linux__
590
        int access_mode = R_OK | X_OK;
591
592
#  ifdef AT_ACCESS
593
        access_mode |= AT_ACCESS;
594
#  endif
595
        if ((access(path, access_mode) < 0) && (errno == ENOENT)) continue;
596
#endif
597
0
        fr_strerror_printf_push("Access check failed: %s", dlerror_txt);
598
0
        break;
599
0
      }
600
601
      /*
602
       *  We're reliant on dlerror_txt from the loop,
603
       *  because once dlerror() is called the error
604
       *  is cleared.
605
       *
606
       *  We need to push every error because we
607
       *  don't know which one would be useful
608
       *  in diagnosing the underlying cause of the
609
       *  load failure.
610
       *
611
       *  However, OSX doesn't clear the error,
612
       *  it simply appends to it.  We don't
613
       *  want endless amounts of duplication,
614
       *  so we tidy it up here.
615
       *
616
       *  The message must be copied (printf, not
617
       *  const): dlerror() hands back a buffer
618
       *  which is reused by the next dl call,
619
       *  including this loop's next dlopen(),
620
       *  and the const variants store only the
621
       *  pointer.
622
       */
623
0
#ifndef __APPLE__
624
0
      fr_strerror_printf_push("%s", dlerror_txt);
625
#else
626
      fr_strerror_printf("%s", dlerror_txt);
627
#endif
628
0
    }
629
630
    /*
631
     *  No element of "path" had the library.  Return
632
     *  the error from the last dlopen().
633
     */
634
66
    if (!handle) {
635
0
      talloc_free(ctx);
636
0
      return NULL;
637
0
    }
638
639
66
    fr_strerror_clear();  /* Don't leave spurious errors in the buffer */
640
641
66
    talloc_free(ctx);
642
66
  } else {
643
0
    char  buffer[2048];
644
645
0
    strlcpy(buffer, name, sizeof(buffer));
646
    /*
647
     *  FIXME: Make this configurable...
648
     */
649
0
    strlcat(buffer, DL_EXTENSION, sizeof(buffer));
650
651
0
    handle = dlopen(buffer, flags);
652
0
    if (!handle) {
653
0
      char *error = dlerror();
654
655
      /*
656
       *  Append the error
657
       */
658
0
      fr_strerror_printf_push("%s", error);
659
0
      return NULL;
660
0
    }
661
0
  }
662
663
66
do_symbol_check:
664
66
  dl = talloc(dl_loader, dl_t);
665
66
  if (unlikely(!dl)) {
666
0
    dlclose(handle);
667
0
    return NULL;
668
0
  }
669
66
  *dl = (dl_t){
670
66
    .name = talloc_strdup(dl, name),
671
66
    .handle = handle,
672
66
    .loader = dl_loader,
673
66
    .uctx = uctx,
674
66
    .uctx_free = uctx_free
675
66
  };
676
66
  talloc_set_destructor(dl, _dl_free);
677
678
66
  dl->in_tree = (fr_rb_insert(dl_loader->tree, dl) == 0);
679
66
  if (!dl->in_tree) {
680
0
    talloc_free(dl);
681
0
    return NULL;
682
0
  }
683
684
66
  if (!dl_loader->defer_symbol_init) dl_symbol_init(dl_loader, dl);
685
686
66
  return dl;
687
66
}
688
689
/** "free" a dl handle, possibly actually freeing it, and unloading the library
690
 *
691
 * This function should be used to explicitly free a dl.
692
 *
693
 * Because dls are reference counted, it may not actually free the memory
694
 * or unload the library, but it will reduce the reference count.
695
 *
696
 * @return
697
 *  - 0 if the dl was actually freed.
698
 *  - >0  the number of remaining references.
699
 */
700
int dl_free(dl_t const *dl)
701
66
{
702
66
  if (!dl) return 0;
703
704
66
  return talloc_decrease_ref_count(talloc_get_type_abort_const(dl, dl_t));
705
66
}
706
707
static int _dl_loader_free(dl_loader_t *dl_loader)
708
28
{
709
28
  int ret = 0;
710
711
28
  if (dl_loader->uctx_free) {
712
0
    ret = talloc_free(dl_loader->uctx);
713
0
    if (ret != 0) goto finish;
714
0
  }
715
716
  /*
717
   *  Prevent freeing if we still have dls loaded
718
   *  We do reference counting, we know exactly what
719
   *  should still be active.
720
   */
721
28
  if (fr_rb_num_elements(dl_loader->tree) > 0) {
722
0
#ifndef NDEBUG
723
0
    fr_rb_iter_inorder_t  iter;
724
0
    void        *data;
725
726
    /*
727
     *  Yes, this is the correct call order
728
     */
729
0
    for (data = fr_rb_iter_init_inorder(dl_loader->tree, &iter);
730
0
         data;
731
0
         data = fr_rb_iter_next_inorder(dl_loader->tree, &iter)) {
732
0
      dl_t *dl = talloc_get_type_abort(data, dl_t);
733
734
0
      fr_strerror_printf_push("  %s (%zu)", dl->name, talloc_reference_count(dl));
735
0
    }
736
737
0
    fr_strerror_printf_push("Refusing to cleanup dl loader, the following dynamically loaded "
738
0
          "libraries are still in use:");
739
0
#endif
740
0
    ret = -1;
741
0
    goto finish;
742
0
  }
743
744
28
finish:
745
28
  return ret;
746
28
}
747
748
/** Return current library path
749
 *
750
 */
751
char const *dl_search_path(dl_loader_t *dl_loader)
752
66
{
753
66
  char    *env;
754
66
  fr_sbuff_t  *search_path = NULL;
755
756
  /*
757
   * The search path in this order [env:][global:]dl_loader->lib_dir
758
   */
759
760
  /*
761
   *  Create a thread-local extensible buffer to
762
   *  store library search_path data.
763
   *
764
   *  This is created once per-thread (the first time
765
   *  this function is called), and freed when the
766
   *  thread exits.
767
   */
768
66
  FR_SBUFF_TALLOC_THREAD_LOCAL(&search_path, 16, PATH_MAX);
769
770
  /*
771
   * Apple removed support for DYLD_LIBRARY_PATH in rootless mode.
772
   */
773
66
  env = getenv("FR_LIBRARY_PATH");
774
66
  if (env && fr_sbuff_in_sprintf(search_path, "%s:", env) < 0) return NULL;
775
776
66
  if (dl_global_libdir && fr_sbuff_in_sprintf(search_path, "%s:", dl_global_libdir) < 0) return NULL;
777
778
66
  if (fr_sbuff_in_strcpy(search_path, dl_loader->lib_dir) < 0) return NULL;
779
780
66
  return fr_sbuff_start(search_path);
781
66
}
782
783
/** Set the global library path
784
 *
785
 * @param[in] lib_dir   ":" separated list of paths to search for libraries in.
786
 * @return
787
 *  - 0 on success.
788
 *  - -1 on failure.
789
 */
790
int dl_search_global_path_set(char const *lib_dir)
791
56
{
792
56
  if (dl_global_libdir) TALLOC_FREE(dl_global_libdir);
793
794
56
  dl_global_libdir = talloc_strdup(NULL, lib_dir);
795
56
  if (!dl_global_libdir) {
796
0
    fr_strerror_const("Failed allocating memory for global dl search path");
797
0
    return -1;
798
0
  }
799
800
56
  fr_atexit_global_once(NULL, fr_atexit_talloc_free, dl_global_libdir);
801
802
56
  return 0;
803
56
}
804
805
/** Set the current library path
806
 *
807
 * @param[in] dl_loader   to add search path component for.
808
 * @param[in] lib_dir   A ":" separated list of paths to search for libraries in.
809
 */
810
int dl_search_path_set(dl_loader_t *dl_loader, char const *lib_dir)
811
0
{
812
0
  char const *old;
813
814
0
  old = dl_loader->lib_dir;
815
816
0
  dl_loader->lib_dir = talloc_strdup(dl_loader, lib_dir);
817
0
  if (!dl_loader->lib_dir) {
818
0
    fr_strerror_const("Failed allocating memory for dl search path");
819
0
    return -1;
820
0
  }
821
822
0
  talloc_const_free(old);
823
824
0
  return 0;
825
0
}
826
827
/** Append a new search path component to the library search path
828
 *
829
 * @param[in] dl_loader   to add search path component for.
830
 * @param[in] lib_dir   to add.  Does not require a ":" prefix.
831
 * @return
832
 *  - 0 on success.
833
 *  - -1 on failure.
834
 */
835
int dl_search_path_prepend(dl_loader_t *dl_loader, char const *lib_dir)
836
0
{
837
0
  char *new;
838
839
0
  if (!dl_loader->lib_dir) {
840
0
    dl_loader->lib_dir = talloc_strdup(dl_loader, lib_dir);
841
0
    if (!dl_loader->lib_dir) {
842
0
    oom:
843
0
      fr_strerror_const("Failed allocating memory for dl search path");
844
0
      return -1;
845
0
    }
846
0
    return 0;
847
0
  }
848
849
0
  new = talloc_asprintf(dl_loader->lib_dir, "%s:%s", lib_dir, dl_loader->lib_dir);
850
0
  if (!new) goto oom;
851
852
0
  dl_loader->lib_dir = new;
853
854
0
  return 0;
855
0
}
856
857
/** Append a new search path component to the library search path
858
 *
859
 * @param[in] dl_loader   to add search path component for.
860
 * @param[in] lib_dir   to add.  Does not require a ":" prefix.
861
 * @return
862
 *  - 0 on success.
863
 *  - -1 on failure.
864
 */
865
int dl_search_path_append(dl_loader_t *dl_loader, char const *lib_dir)
866
0
{
867
0
  char *new;
868
869
0
  if (!dl_loader->lib_dir) {
870
0
    dl_loader->lib_dir = talloc_strdup(dl_loader, lib_dir);
871
0
    if (!dl_loader->lib_dir) {
872
0
    oom:
873
0
      fr_strerror_const("Failed allocating memory for dl search path");
874
0
      return -1;
875
0
    }
876
0
    return 0;
877
0
  }
878
879
0
  new = talloc_asprintf_append_buffer(dl_loader->lib_dir, ":%s", lib_dir);
880
0
  if (!new) goto oom;
881
882
0
  dl_loader->lib_dir = new;
883
884
0
  return 0;
885
0
}
886
887
/** Retrieve the uctx from a dl_loader
888
 *
889
 */
890
void *dl_loader_uctx(dl_loader_t *dl_loader)
891
0
{
892
0
  return dl_loader->uctx;
893
0
}
894
895
/** Initialise structures needed by the dynamic linker
896
 *
897
 * @param[in] ctx   To bind lifetime of dl_loader_t too.
898
 * @param[in] uctx    API client opaque data to store in dl_loader_t.
899
 * @param[in] uctx_free   Call talloc_free() on uctx when the dl_loader_t
900
 *        is freed.
901
 * @param[in] defer_symbol_init If true, it is up to the caller to call
902
 *        #dl_symbol_init after calling #dl_by_name.
903
 *        This prevents any of the registered callbacks
904
 *        from executing until #dl_symbol_init is
905
 *        called explicitly.
906
 */
907
dl_loader_t *dl_loader_init(TALLOC_CTX *ctx, void *uctx, bool uctx_free, bool defer_symbol_init)
908
56
{
909
56
  dl_loader_t *dl_loader;
910
911
56
  dl_loader = talloc_zero(NULL, dl_loader_t);
912
56
  if (!dl_loader) {
913
0
    fr_strerror_const("Failed allocating dl_loader");
914
0
    return NULL;
915
0
  }
916
917
56
  dl_loader->tree = fr_rb_inline_talloc_alloc(dl_loader, dl_t, node, dl_handle_cmp, NULL);
918
56
  if (!dl_loader->tree) {
919
0
    fr_strerror_const("Failed initialising dl->tree");
920
0
  error:
921
0
    TALLOC_FREE(dl_loader);
922
0
    return NULL;
923
0
  }
924
925
56
  talloc_link_ctx(ctx, dl_loader);
926
927
56
  dl_loader->lib_dir = talloc_strdup(dl_loader, fr_path_default_lib_dir());
928
56
  if (!dl_loader->lib_dir) {
929
0
    fr_strerror_const("Failed allocating memory for dl search path");
930
0
    goto error;
931
0
  }
932
933
56
  talloc_set_destructor(dl_loader, _dl_loader_free);
934
935
  /*
936
   *  Run this now to avoid bizarre issues
937
   *  with the talloc atexit handlers firing
938
   *  in the child, and that causing issues.
939
   */
940
56
  dl_loader->do_dlclose = (!RUNNING_ON_VALGRIND && (fr_get_lsan_state() != 1));
941
56
  dl_loader->uctx = uctx;
942
56
  dl_loader->uctx_free = uctx_free;
943
56
  dl_loader->defer_symbol_init = defer_symbol_init;
944
945
  /*
946
   *  It's not clear yet whether we still want
947
   *  some dynamic loading capability in
948
   *  emscripten, so keep this as a potentially
949
   *  runtime toggle for now.
950
   */
951
#ifdef __EMSCRIPTEN__
952
  dl_loader->do_static = true;
953
#else
954
56
  dl_loader->do_static = false;
955
56
#endif
956
56
  fr_dlist_init(&dl_loader->sym_init, dl_symbol_init_t, entry);
957
56
  fr_dlist_init(&dl_loader->sym_free, dl_symbol_free_t, entry);
958
959
56
  return dl_loader;
960
56
}
961
962
/** Runtime override for doing static or dynamic module loading
963
 *
964
 * @param[in] dl_loader   to configure.
965
 * @param[in] do_static   If true, all dlopen calls result in a
966
 *        reference to RTLD_DEFAULT being returned
967
 *        which allows all the dynamic loading
968
 *        infrastructure to worth correctly with
969
 *        a monolithic binary.
970
 * @return The previous value of do_static
971
 */
972
bool dl_loader_set_static(dl_loader_t *dl_loader, bool do_static)
973
0
{
974
0
  bool old = dl_loader->do_static;
975
976
0
  dl_loader->do_static = do_static;
977
978
0
  return old;
979
0
}
980
981
/** Called from a debugger to print information about a dl_loader
982
 *
983
 */
984
void dl_loader_debug(FILE *fp, dl_loader_t *dl)
985
0
{
986
0
  fprintf(fp, "dl_loader %p\n", dl);
987
0
  fprintf(fp, "lib_dir           : %s\n", dl->lib_dir);
988
0
  fprintf(fp, "do_dlclose        : %s\n", dl->do_dlclose ? "yes" : "no");
989
0
  fprintf(fp, "uctx              : %p\n", dl->uctx);
990
0
  fprintf(fp, "uctx_free         : %s\n", dl->uctx_free ? "yes" : "no");
991
0
  fprintf(fp, "defer_symbol_init : %s\n", dl->defer_symbol_init ? "yes" : "no");
992
993
0
  fr_dlist_foreach(&dl->sym_init, dl_symbol_init_t, sym) {
994
0
    fprintf(fp, "symbol_init %s\n", sym->symbol ? sym->symbol : "<base>");
995
0
    fprintf(fp, "\tpriority : %u\n", sym->priority);
996
0
    fprintf(fp, "\tfunc     : %p\n", sym->func);
997
0
    fprintf(fp, "\tuctx     : %p\n", sym->uctx);
998
0
  }
999
1000
0
  fr_dlist_foreach(&dl->sym_free, dl_symbol_free_t, sym) {
1001
0
    fprintf(fp, "symbol_free %s\n", sym->symbol ? sym->symbol : "<base>");
1002
0
    fprintf(fp, "\tpriority : %u\n", sym->priority);
1003
0
    fprintf(fp, "\tfunc     : %p\n", sym->func);
1004
0
    fprintf(fp, "\tuctx     : %p\n", sym->uctx);
1005
0
  }
1006
0
}