Coverage Report

Created: 2024-08-28 06:17

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