Coverage Report

Created: 2026-05-11 06:44

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: d028e2f58c12fac19bd80a02e4651327485d834e $
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: d028e2f58c12fac19bd80a02e4651327485d834e $")
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
84
#  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
116
{
170
116
  int ret;
171
172
116
  ret = strcmp(((dl_t const *)one)->name, ((dl_t const *)two)->name);
173
116
  return CMP(ret, 0);
174
116
}
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
38
{
235
38
  dl_symbol_init_t  *init = NULL;
236
38
  void      *sym = NULL;
237
38
  char      buffer[256];
238
239
38
  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
38
  return 0;
267
38
}
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
38
{
281
38
  dl_symbol_free_t  *free = NULL;
282
38
  void      *sym = NULL;
283
284
38
  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
38
  return 0;
301
38
}
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
38
{
439
38
  dl = talloc_get_type_abort(dl, dl_t);
440
441
38
  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
38
  if (dl->loader->do_dlclose) dlclose(dl->handle);        /* ignore any errors */
448
449
38
  dl->handle = NULL;
450
451
38
  if (dl->in_tree) fr_rb_delete(dl->loader->tree, dl);
452
453
38
  return 0;
454
38
}
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
38
{
472
38
  int   flags = RTLD_NOW;
473
38
  void    *handle = NULL;
474
38
  char const  *search_path;
475
38
  dl_t    *dl;
476
477
  /*
478
   *  There's already something in the tree,
479
   *  just return that instead.
480
   */
481
38
  dl = fr_rb_find(dl_loader->tree, &(dl_t){ .name = name });
482
38
  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
38
  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
38
  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
38
  fr_strerror_clear();  /* clear error buffer */
520
521
  /*
522
   *  Bind all the symbols *NOW* so we don't hit errors later
523
   */
524
38
  flags |= RTLD_NOW;
525
526
38
  search_path = dl_search_path(dl_loader);
527
528
  /*
529
   *  Prefer loading our libraries by absolute path.
530
   */
531
38
  if (search_path) {
532
38
    char *ctx, *paths, *path;
533
38
    char *p;
534
38
    char *dlerror_txt = NULL;
535
536
38
    fr_strerror_clear();
537
538
38
    ctx = paths = talloc_strdup(NULL, search_path);
539
38
    while ((path = strsep(&paths, ":")) != NULL) {
540
38
      char *fullpath;
541
542
      /*
543
       *  Trim the trailing slash
544
       */
545
38
      p = strrchr(path, '/');
546
38
      if (p && ((p[1] == '\0') || (p[1] == ':'))) *p = '\0';
547
548
38
      fullpath = talloc_typed_asprintf(ctx, "%s/%s%s", path, name, DL_EXTENSION);
549
38
      handle = dlopen(fullpath, flags);
550
38
      talloc_free(fullpath);
551
38
      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
0
#ifndef __APPLE__
617
0
      fr_strerror_const_push(dlerror_txt);
618
#else
619
      fr_strerror_const(dlerror_txt);
620
#endif
621
0
    }
622
623
    /*
624
     *  No element of "path" had the library.  Return
625
     *  the error from the last dlopen().
626
     */
627
38
    if (!handle) {
628
0
      talloc_free(ctx);
629
0
      return NULL;
630
0
    }
631
632
38
    fr_strerror_clear();  /* Don't leave spurious errors in the buffer */
633
634
38
    talloc_free(ctx);
635
38
  } else {
636
0
    char  buffer[2048];
637
638
0
    strlcpy(buffer, name, sizeof(buffer));
639
    /*
640
     *  FIXME: Make this configurable...
641
     */
642
0
    strlcat(buffer, DL_EXTENSION, sizeof(buffer));
643
644
0
    handle = dlopen(buffer, flags);
645
0
    if (!handle) {
646
0
      char *error = dlerror();
647
648
      /*
649
       *  Append the error
650
       */
651
0
      fr_strerror_printf_push("%s", error);
652
0
      return NULL;
653
0
    }
654
0
  }
655
656
38
do_symbol_check:
657
38
  dl = talloc(dl_loader, dl_t);
658
38
  if (unlikely(!dl)) {
659
0
    dlclose(handle);
660
0
    return NULL;
661
0
  }
662
38
  *dl = (dl_t){
663
38
    .name = talloc_strdup(dl, name),
664
38
    .handle = handle,
665
38
    .loader = dl_loader,
666
38
    .uctx = uctx,
667
38
    .uctx_free = uctx_free
668
38
  };
669
38
  talloc_set_destructor(dl, _dl_free);
670
671
38
  dl->in_tree = fr_rb_insert(dl_loader->tree, dl);
672
38
  if (!dl->in_tree) {
673
0
    talloc_free(dl);
674
0
    return NULL;
675
0
  }
676
677
38
  if (!dl_loader->defer_symbol_init) dl_symbol_init(dl_loader, dl);
678
679
38
  return dl;
680
38
}
681
682
/** "free" a dl handle, possibly actually freeing it, and unloading the library
683
 *
684
 * This function should be used to explicitly free a dl.
685
 *
686
 * Because dls are reference counted, it may not actually free the memory
687
 * or unload the library, but it will reduce the reference count.
688
 *
689
 * @return
690
 *  - 0 if the dl was actually freed.
691
 *  - >0  the number of remaining references.
692
 */
693
int dl_free(dl_t const *dl)
694
38
{
695
38
  if (!dl) return 0;
696
697
38
  return talloc_decrease_ref_count(talloc_get_type_abort_const(dl, dl_t));
698
38
}
699
700
static int _dl_loader_free(dl_loader_t *dl_loader)
701
20
{
702
20
  int ret = 0;
703
704
20
  if (dl_loader->uctx_free) {
705
0
    ret = talloc_free(dl_loader->uctx);
706
0
    if (ret != 0) goto finish;
707
0
  }
708
709
  /*
710
   *  Prevent freeing if we still have dls loaded
711
   *  We do reference counting, we know exactly what
712
   *  should still be active.
713
   */
714
20
  if (fr_rb_num_elements(dl_loader->tree) > 0) {
715
0
#ifndef NDEBUG
716
0
    fr_rb_iter_inorder_t  iter;
717
0
    void        *data;
718
719
    /*
720
     *  Yes, this is the correct call order
721
     */
722
0
    for (data = fr_rb_iter_init_inorder(dl_loader->tree, &iter);
723
0
         data;
724
0
         data = fr_rb_iter_next_inorder(dl_loader->tree, &iter)) {
725
0
      dl_t *dl = talloc_get_type_abort(data, dl_t);
726
727
0
      fr_strerror_printf_push("  %s (%zu)", dl->name, talloc_reference_count(dl));
728
0
    }
729
730
0
    fr_strerror_printf_push("Refusing to cleanup dl loader, the following dynamically loaded "
731
0
          "libraries are still in use:");
732
0
#endif
733
0
    ret = -1;
734
0
    goto finish;
735
0
  }
736
737
20
finish:
738
20
  return ret;
739
20
}
740
741
/** Return current library path
742
 *
743
 */
744
char const *dl_search_path(dl_loader_t *dl_loader)
745
38
{
746
38
  char    *env;
747
38
  fr_sbuff_t  *search_path = NULL;
748
749
  /*
750
   * The search path in this order [env:][global:]dl_loader->lib_dir
751
   */
752
753
  /*
754
   *  Create a thread-local extensible buffer to
755
   *  store library search_path data.
756
   *
757
   *  This is created once per-thread (the first time
758
   *  this function is called), and freed when the
759
   *  thread exits.
760
   */
761
38
  FR_SBUFF_TALLOC_THREAD_LOCAL(&search_path, 16, PATH_MAX);
762
763
  /*
764
   * Apple removed support for DYLD_LIBRARY_PATH in rootless mode.
765
   */
766
38
  env = getenv("FR_LIBRARY_PATH");
767
38
  if (env && fr_sbuff_in_sprintf(search_path, "%s:", env) < 0) return NULL;
768
769
38
  if (dl_global_libdir && fr_sbuff_in_sprintf(search_path, "%s:", dl_global_libdir) < 0) return NULL;
770
771
38
  if (fr_sbuff_in_strcpy(search_path, dl_loader->lib_dir) < 0) return NULL;
772
773
38
  return fr_sbuff_start(search_path);
774
38
}
775
776
/** Set the global library path
777
 *
778
 * @param[in] lib_dir   ":" separated list of paths to search for libraries in.
779
 * @return
780
 *  - 0 on success.
781
 *  - -1 on failure.
782
 */
783
int dl_search_global_path_set(char const *lib_dir)
784
42
{
785
42
  if (dl_global_libdir) TALLOC_FREE(dl_global_libdir);
786
787
42
  dl_global_libdir = talloc_strdup(NULL, lib_dir);
788
42
  if (!dl_global_libdir) {
789
0
    fr_strerror_const("Failed allocating memory for global dl search path");
790
0
    return -1;
791
0
  }
792
793
42
  fr_atexit_global_once(NULL, fr_atexit_talloc_free, dl_global_libdir);
794
795
42
  return 0;
796
42
}
797
798
/** Set the current library path
799
 *
800
 * @param[in] dl_loader   to add search path component for.
801
 * @param[in] lib_dir   A ":" separated list of paths to search for libraries in.
802
 */
803
int dl_search_path_set(dl_loader_t *dl_loader, char const *lib_dir)
804
0
{
805
0
  char const *old;
806
807
0
  old = dl_loader->lib_dir;
808
809
0
  dl_loader->lib_dir = talloc_strdup(dl_loader, lib_dir);
810
0
  if (!dl_loader->lib_dir) {
811
0
    fr_strerror_const("Failed allocating memory for dl search path");
812
0
    return -1;
813
0
  }
814
815
0
  talloc_const_free(old);
816
817
0
  return 0;
818
0
}
819
820
/** Append a new search path component to the library search path
821
 *
822
 * @param[in] dl_loader   to add search path component for.
823
 * @param[in] lib_dir   to add.  Does not require a ":" prefix.
824
 * @return
825
 *  - 0 on success.
826
 *  - -1 on failure.
827
 */
828
int dl_search_path_prepend(dl_loader_t *dl_loader, char const *lib_dir)
829
0
{
830
0
  char *new;
831
832
0
  if (!dl_loader->lib_dir) {
833
0
    dl_loader->lib_dir = talloc_strdup(dl_loader, lib_dir);
834
0
    if (!dl_loader->lib_dir) {
835
0
    oom:
836
0
      fr_strerror_const("Failed allocating memory for dl search path");
837
0
      return -1;
838
0
    }
839
0
    return 0;
840
0
  }
841
842
0
  new = talloc_asprintf(dl_loader->lib_dir, "%s:%s", lib_dir, dl_loader->lib_dir);
843
0
  if (!new) goto oom;
844
845
0
  dl_loader->lib_dir = new;
846
847
0
  return 0;
848
0
}
849
850
/** Append a new search path component to the library search path
851
 *
852
 * @param[in] dl_loader   to add search path component for.
853
 * @param[in] lib_dir   to add.  Does not require a ":" prefix.
854
 * @return
855
 *  - 0 on success.
856
 *  - -1 on failure.
857
 */
858
int dl_search_path_append(dl_loader_t *dl_loader, char const *lib_dir)
859
0
{
860
0
  char *new;
861
862
0
  if (!dl_loader->lib_dir) {
863
0
    dl_loader->lib_dir = talloc_strdup(dl_loader, lib_dir);
864
0
    if (!dl_loader->lib_dir) {
865
0
    oom:
866
0
      fr_strerror_const("Failed allocating memory for dl search path");
867
0
      return -1;
868
0
    }
869
0
    return 0;
870
0
  }
871
872
0
  new = talloc_asprintf_append_buffer(dl_loader->lib_dir, ":%s", lib_dir);
873
0
  if (!new) goto oom;
874
875
0
  dl_loader->lib_dir = new;
876
877
0
  return 0;
878
0
}
879
880
/** Retrieve the uctx from a dl_loader
881
 *
882
 */
883
void *dl_loader_uctx(dl_loader_t *dl_loader)
884
0
{
885
0
  return dl_loader->uctx;
886
0
}
887
888
/** Initialise structures needed by the dynamic linker
889
 *
890
 * @param[in] ctx   To bind lifetime of dl_loader_t too.
891
 * @param[in] uctx    API client opaque data to store in dl_loader_t.
892
 * @param[in] uctx_free   Call talloc_free() on uctx when the dl_loader_t
893
 *        is freed.
894
 * @param[in] defer_symbol_init If true, it is up to the caller to call
895
 *        #dl_symbol_init after calling #dl_by_name.
896
 *        This prevents any of the registered callbacks
897
 *        from executing until #dl_symbol_init is
898
 *        called explicitly.
899
 */
900
dl_loader_t *dl_loader_init(TALLOC_CTX *ctx, void *uctx, bool uctx_free, bool defer_symbol_init)
901
42
{
902
42
  dl_loader_t *dl_loader;
903
904
42
  dl_loader = talloc_zero(NULL, dl_loader_t);
905
42
  if (!dl_loader) {
906
0
    fr_strerror_const("Failed allocating dl_loader");
907
0
    return NULL;
908
0
  }
909
910
42
  dl_loader->tree = fr_rb_inline_talloc_alloc(dl_loader, dl_t, node, dl_handle_cmp, NULL);
911
42
  if (!dl_loader->tree) {
912
0
    fr_strerror_const("Failed initialising dl->tree");
913
0
  error:
914
0
    TALLOC_FREE(dl_loader);
915
0
    return NULL;
916
0
  }
917
918
42
  talloc_link_ctx(ctx, dl_loader);
919
920
42
  dl_loader->lib_dir = talloc_strdup(dl_loader, fr_path_default_lib_dir());
921
42
  if (!dl_loader->lib_dir) {
922
0
    fr_strerror_const("Failed allocating memory for dl search path");
923
0
    goto error;
924
0
  }
925
926
42
  talloc_set_destructor(dl_loader, _dl_loader_free);
927
928
  /*
929
   *  Run this now to avoid bizarre issues
930
   *  with the talloc atexit handlers firing
931
   *  in the child, and that causing issues.
932
   */
933
42
  dl_loader->do_dlclose = (!RUNNING_ON_VALGRIND && (fr_get_lsan_state() != 1));
934
42
  dl_loader->uctx = uctx;
935
42
  dl_loader->uctx_free = uctx_free;
936
42
  dl_loader->defer_symbol_init = defer_symbol_init;
937
938
  /*
939
   *  It's not clear yet whether we still want
940
   *  some dynamic loading capability in
941
   *  emscripten, so keep this as a potentially
942
   *  runtime toggle for now.
943
   */
944
#ifdef __EMSCRIPTEN__
945
  dl_loader->do_static = true;
946
#else
947
42
  dl_loader->do_static = false;
948
42
#endif
949
42
  fr_dlist_init(&dl_loader->sym_init, dl_symbol_init_t, entry);
950
42
  fr_dlist_init(&dl_loader->sym_free, dl_symbol_free_t, entry);
951
952
42
  return dl_loader;
953
42
}
954
955
/** Runtime override for doing static or dynamic module loading
956
 *
957
 * @param[in] dl_loader   to configure.
958
 * @param[in] do_static   If true, all dlopen calls result in a
959
 *        reference to RTLD_DEFAULT being returned
960
 *        which allows all the dynamic loading
961
 *        infrastructure to worth correctly with
962
 *        a monolithic binary.
963
 * @return The previous value of do_static
964
 */
965
bool dl_loader_set_static(dl_loader_t *dl_loader, bool do_static)
966
0
{
967
0
  bool old = dl_loader->do_static;
968
969
0
  dl_loader->do_static = do_static;
970
971
0
  return old;
972
0
}
973
974
/** Called from a debugger to print information about a dl_loader
975
 *
976
 */
977
void dl_loader_debug(FILE *fp, dl_loader_t *dl)
978
0
{
979
0
  fprintf(fp, "dl_loader %p\n", dl);
980
0
  fprintf(fp, "lib_dir           : %s\n", dl->lib_dir);
981
0
  fprintf(fp, "do_dlclose        : %s\n", dl->do_dlclose ? "yes" : "no");
982
0
  fprintf(fp, "uctx              : %p\n", dl->uctx);
983
0
  fprintf(fp, "uctx_free         : %s\n", dl->uctx_free ? "yes" : "no");
984
0
  fprintf(fp, "defer_symbol_init : %s\n", dl->defer_symbol_init ? "yes" : "no");
985
986
0
  fr_dlist_foreach(&dl->sym_init, dl_symbol_init_t, sym) {
987
0
    fprintf(fp, "symbol_init %s\n", sym->symbol ? sym->symbol : "<base>");
988
0
    fprintf(fp, "\tpriority : %u\n", sym->priority);
989
0
    fprintf(fp, "\tfunc     : %p\n", sym->func);
990
0
    fprintf(fp, "\tuctx     : %p\n", sym->uctx);
991
0
  }
992
993
0
  fr_dlist_foreach(&dl->sym_free, dl_symbol_free_t, sym) {
994
0
    fprintf(fp, "symbol_free %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
0
}