Coverage Report

Created: 2025-08-28 06:51

/src/krb5/src/util/support/plugins.c
Line
Count
Source (jump to first uncovered line)
1
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
/* util/support/plugins.c - Plugin module support functions */
3
/*
4
 * Copyright 2006, 2008 by the Massachusetts Institute of Technology.
5
 * All Rights Reserved.
6
 *
7
 * Export of this software from the United States of America may
8
 *   require a specific license from the United States Government.
9
 *   It is the responsibility of any person or organization contemplating
10
 *   export to obtain such a license before exporting.
11
 *
12
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13
 * distribute this software and its documentation for any purpose and
14
 * without fee is hereby granted, provided that the above copyright
15
 * notice appear in all copies and that both that copyright notice and
16
 * this permission notice appear in supporting documentation, and that
17
 * the name of M.I.T. not be used in advertising or publicity pertaining
18
 * to distribution of the software without specific, written prior
19
 * permission.  Furthermore if you modify this software you must label
20
 * your software as modified software and not distribute it in such a
21
 * fashion that it might be confused with the original M.I.T. software.
22
 * M.I.T. makes no representations about the suitability of
23
 * this software for any purpose.  It is provided "as is" without express
24
 * or implied warranty.
25
 */
26
27
#include "k5-platform.h"
28
#include "k5-plugin.h"
29
#if USE_DLOPEN
30
#include <dlfcn.h>
31
#endif
32
33
#if USE_DLOPEN
34
#ifdef RTLD_GROUP
35
#define GROUP RTLD_GROUP
36
#else
37
0
#define GROUP 0
38
#endif
39
#ifdef RTLD_NODELETE
40
0
#define NODELETE RTLD_NODELETE
41
#else
42
#define NODELETE 0
43
#endif
44
0
#define PLUGIN_DLOPEN_FLAGS (RTLD_NOW | RTLD_LOCAL | GROUP | NODELETE)
45
#endif
46
47
/*
48
 * glibc bug 11941, fixed in release 2.25, can cause an assertion failure in
49
 * dlclose() on process exit.  Our workaround is to leak dlopen() handles
50
 * (which doesn't typically manifest in leak detection tools because the
51
 * handles are still reachable via a global table in libdl).  Because we
52
 * dlopen() with RTLD_NODELETE, we weren't going to unload the plugin objects
53
 * anyway.
54
 */
55
#ifdef __GLIBC_PREREQ
56
#if ! __GLIBC_PREREQ(2, 25)
57
#define dlclose(x)
58
#endif
59
#endif
60
61
#include <stdarg.h>
62
static void Tprintf (const char *fmt, ...)
63
0
{
64
#ifdef DEBUG
65
    va_list va;
66
    va_start (va, fmt);
67
    vfprintf (stderr, fmt, va);
68
    va_end (va);
69
#endif
70
0
}
71
72
struct plugin_file_handle {
73
#if defined(USE_DLOPEN)
74
    void *dlhandle;
75
#elif defined(_WIN32)
76
    HMODULE module;
77
#else
78
    char dummy;
79
#endif
80
};
81
82
#if defined(USE_DLOPEN)
83
84
static long
85
open_plugin_dlfcn(struct plugin_file_handle *h, const char *filename,
86
                  struct errinfo *ep)
87
0
{
88
0
    const char *e;
89
90
0
    h->dlhandle = dlopen(filename, PLUGIN_DLOPEN_FLAGS);
91
0
    if (h->dlhandle == NULL) {
92
0
        e = dlerror();
93
0
        if (e == NULL)
94
0
            e = _("unknown failure");
95
0
        Tprintf("dlopen(%s): %s\n", filename, e);
96
0
        k5_set_error(ep, ENOENT, _("unable to load plugin [%s]: %s"),
97
0
                     filename, e);
98
0
        return ENOENT;
99
0
    }
100
0
    return 0;
101
0
}
102
0
#define open_plugin open_plugin_dlfcn
103
104
static long
105
get_sym_dlfcn(struct plugin_file_handle *h, const char *csymname,
106
              void **sym_out, struct errinfo *ep)
107
0
{
108
0
    const char *e;
109
110
0
    if (h->dlhandle == NULL)
111
0
        return ENOENT;
112
0
    *sym_out = dlsym(h->dlhandle, csymname);
113
0
    if (*sym_out == NULL) {
114
0
        e = dlerror();
115
0
        if (e == NULL)
116
0
            e = _("unknown failure");
117
0
        Tprintf("dlsym(%s): %s\n", csymname, e);
118
0
        k5_set_error(ep, ENOENT, "%s", e);
119
0
        return ENOENT;
120
0
    }
121
0
    return 0;
122
0
}
123
0
#define get_sym get_sym_dlfcn
124
125
static void
126
close_plugin_dlfcn(struct plugin_file_handle *h)
127
0
{
128
0
    if (h->dlhandle != NULL)
129
0
        dlclose(h->dlhandle);
130
0
}
131
0
#define close_plugin close_plugin_dlfcn
132
133
#elif defined(_WIN32)
134
135
static long
136
open_plugin_win32(struct plugin_file_handle *h, const char *filename,
137
                  struct errinfo *ep)
138
{
139
    h->module = LoadLibrary(filename);
140
    if (h == NULL) {
141
        Tprintf("Unable to load dll: %s\n", filename);
142
        k5_set_error(ep, ENOENT, _("unable to load DLL [%s]"), filename);
143
        return ENOENT;
144
    }
145
    return 0;
146
}
147
#define open_plugin open_plugin_win32
148
149
static long
150
get_sym_win32(struct plugin_file_handle *h, const char *csymname,
151
              void **sym_out, struct errinfo *ep)
152
{
153
    LPVOID lpMsgBuf;
154
    DWORD dw;
155
156
    if (h->module == NULL)
157
        return ENOENT;
158
    *sym_out = GetProcAddress(h->module, csymname);
159
    if (*sym_out == NULL) {
160
        Tprintf("GetProcAddress(%s): %i\n", csymname, GetLastError());
161
        dw = GetLastError();
162
        if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
163
                          FORMAT_MESSAGE_FROM_SYSTEM,
164
                          NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
165
                          (LPTSTR)&lpMsgBuf, 0, NULL)) {
166
            k5_set_error(ep, ENOENT, _("unable to get DLL Symbol: %s"),
167
                         (char *)lpMsgBuf);
168
            LocalFree(lpMsgBuf);
169
        }
170
        return ENOENT;
171
    }
172
    return 0;
173
}
174
#define get_sym get_sym_win32
175
176
static void
177
close_plugin_win32(struct plugin_file_handle *h)
178
{
179
    if (h->module != NULL)
180
        FreeLibrary(h->module);
181
}
182
#define close_plugin close_plugin_win32
183
184
#else
185
186
static long
187
open_plugin_dummy(struct plugin_file_handle *h, const char *filename,
188
                  struct errinfo *ep)
189
{
190
    k5_set_error(ep, ENOENT, _("plugin loading unavailable"));
191
    return ENOENT;
192
}
193
#define open_plugin open_plugin_dummy
194
195
static long
196
get_sym_dummy(struct plugin_file_handle *h, const char *csymname,
197
              void **sym_out, struct errinfo *ep)
198
{
199
    return ENOENT;
200
}
201
#define get_sym get_sym_dummy
202
203
static void
204
close_plugin_dummy(struct plugin_file_handle *h)
205
{
206
}
207
#define close_plugin close_plugin_dummy
208
209
#endif
210
211
long KRB5_CALLCONV
212
krb5int_open_plugin(const char *filename,
213
                    struct plugin_file_handle **handle_out, struct errinfo *ep)
214
0
{
215
0
    long ret;
216
0
    struct plugin_file_handle *h;
217
218
0
    *handle_out = NULL;
219
220
0
    h = calloc(1, sizeof(*h));
221
0
    if (h == NULL)
222
0
        return ENOMEM;
223
224
0
    ret = open_plugin(h, filename, ep);
225
0
    if (ret) {
226
0
        free(h);
227
0
        return ret;
228
0
    }
229
230
0
    *handle_out = h;
231
0
    return 0;
232
0
}
233
234
long KRB5_CALLCONV
235
krb5int_get_plugin_data(struct plugin_file_handle *h, const char *csymname,
236
                        void **sym_out, struct errinfo *ep)
237
0
{
238
0
    return get_sym(h, csymname, sym_out, ep);
239
0
}
240
241
long KRB5_CALLCONV
242
krb5int_get_plugin_func(struct plugin_file_handle *h, const char *csymname,
243
                        void (**sym_out)(void), struct errinfo *ep)
244
0
{
245
0
    void *dptr = NULL;
246
0
    long ret = get_sym(h, csymname, &dptr, ep);
247
248
0
    if (!ret)
249
0
        *sym_out = (void (*)(void))dptr;
250
0
    return ret;
251
0
}
252
253
void KRB5_CALLCONV
254
krb5int_close_plugin (struct plugin_file_handle *h)
255
0
{
256
0
    close_plugin(h);
257
0
    free(h);
258
0
}
259
260
static long
261
krb5int_plugin_file_handle_array_init (struct plugin_file_handle ***harray)
262
0
{
263
0
    long err = 0;
264
265
0
    *harray = calloc (1, sizeof (**harray)); /* calloc initializes to NULL */
266
0
    if (*harray == NULL) { err = ENOMEM; }
267
268
0
    return err;
269
0
}
270
271
static long
272
krb5int_plugin_file_handle_array_add (struct plugin_file_handle ***harray, size_t *count,
273
                                      struct plugin_file_handle *p)
274
0
{
275
0
    long err = 0;
276
0
    struct plugin_file_handle **newharray = NULL;
277
0
    size_t newcount = *count + 1;
278
279
0
    newharray = realloc (*harray, ((newcount + 1) * sizeof (**harray))); /* +1 for NULL */
280
0
    if (newharray == NULL) {
281
0
        err = ENOMEM;
282
0
    } else {
283
0
        newharray[newcount - 1] = p;
284
0
        newharray[newcount] = NULL;
285
0
        *count = newcount;
286
0
        *harray = newharray;
287
0
    }
288
289
0
    return err;
290
0
}
291
292
static void
293
krb5int_plugin_file_handle_array_free (struct plugin_file_handle **harray)
294
0
{
295
0
    size_t i;
296
297
0
    if (harray != NULL) {
298
0
        for (i = 0; harray[i] != NULL; i++) {
299
0
            krb5int_close_plugin (harray[i]);
300
0
        }
301
0
        free (harray);
302
0
    }
303
0
}
304
305
#if TARGET_OS_MAC
306
#define FILEEXTS { "", ".bundle", ".dylib", ".so", NULL }
307
#elif defined(_WIN32)
308
#define FILEEXTS  { "", ".dll", NULL }
309
#else
310
0
#define FILEEXTS  { "", ".so", NULL }
311
#endif
312
313
314
static void
315
krb5int_free_plugin_filenames (char **filenames)
316
0
{
317
0
    size_t i;
318
319
0
    if (filenames != NULL) {
320
0
        for (i = 0; filenames[i] != NULL; i++) {
321
0
            free (filenames[i]);
322
0
        }
323
0
        free (filenames);
324
0
    }
325
0
}
326
327
328
static long
329
krb5int_get_plugin_filenames (const char * const *filebases, char ***filenames)
330
0
{
331
0
    long err = 0;
332
0
    static const char *const fileexts[] = FILEEXTS;
333
0
    char **tempnames = NULL;
334
0
    size_t bases_count = 0;
335
0
    size_t exts_count = 0;
336
0
    size_t i;
337
338
0
    if (!filebases) { err = EINVAL; }
339
0
    if (!filenames) { err = EINVAL; }
340
341
0
    if (!err) {
342
0
        for (i = 0; filebases[i]; i++) { bases_count++; }
343
0
        for (i = 0; fileexts[i]; i++) { exts_count++; }
344
0
        tempnames = calloc ((bases_count * exts_count)+1, sizeof (char *));
345
0
        if (!tempnames) { err = ENOMEM; }
346
0
    }
347
348
0
    if (!err) {
349
0
        size_t j;
350
0
        for (i = 0; !err && filebases[i]; i++) {
351
0
            for (j = 0; !err && fileexts[j]; j++) {
352
0
                if (asprintf(&tempnames[(i*exts_count)+j], "%s%s",
353
0
                             filebases[i], fileexts[j]) < 0) {
354
0
                    tempnames[(i*exts_count)+j] = NULL;
355
0
                    err = ENOMEM;
356
0
                }
357
0
            }
358
0
        }
359
0
        tempnames[bases_count * exts_count] = NULL; /* NUL-terminate */
360
0
    }
361
362
0
    if (!err) {
363
0
        *filenames = tempnames;
364
0
        tempnames = NULL;
365
0
    }
366
367
0
    krb5int_free_plugin_filenames(tempnames);
368
369
0
    return err;
370
0
}
371
372
373
/* Takes a NULL-terminated list of directories.  If filebases is NULL, filebases is ignored
374
 * all plugins in the directories are loaded.  If filebases is a NULL-terminated array of names,
375
 * only plugins in the directories with those name (plus any platform extension) are loaded. */
376
377
long KRB5_CALLCONV
378
krb5int_open_plugin_dirs (const char * const *dirnames,
379
                          const char * const *filebases,
380
                          struct plugin_dir_handle *dirhandle,
381
                          struct errinfo *ep)
382
0
{
383
0
    long err = 0;
384
0
    struct plugin_file_handle **h = NULL;
385
0
    size_t count = 0;
386
0
    char **filenames = NULL;
387
0
    size_t i;
388
389
0
    if (!err) {
390
0
        err = krb5int_plugin_file_handle_array_init (&h);
391
0
    }
392
393
0
    if (!err && (filebases != NULL)) {
394
0
        err = krb5int_get_plugin_filenames (filebases, &filenames);
395
0
    }
396
397
0
    for (i = 0; !err && dirnames[i] != NULL; i++) {
398
0
        if (filenames != NULL) {
399
            /* load plugins with names from filenames from each directory */
400
0
            size_t j;
401
402
0
            for (j = 0; !err && filenames[j] != NULL; j++) {
403
0
                struct plugin_file_handle *handle = NULL;
404
0
                char *filepath = NULL;
405
406
0
                if (!err)
407
0
                    err = k5_path_join(dirnames[i], filenames[j], &filepath);
408
409
0
                if (!err && krb5int_open_plugin(filepath, &handle, ep) == 0) {
410
0
                    err = krb5int_plugin_file_handle_array_add (&h, &count, handle);
411
0
                    if (!err)
412
0
                        handle = NULL; /* h takes ownership */
413
0
                }
414
415
0
                free(filepath);
416
0
                if (handle   != NULL) { krb5int_close_plugin (handle); }
417
0
            }
418
0
        } else {
419
0
            char **fnames = NULL;
420
0
            size_t j;
421
422
0
            err = k5_dir_filenames(dirnames[i], &fnames);
423
0
            for (j = 0; !err && fnames[j] != NULL; j++) {
424
0
                char *filepath = NULL;
425
0
                struct plugin_file_handle *handle = NULL;
426
427
0
                if (strcmp(fnames[j], ".") == 0 ||
428
0
                    strcmp(fnames[j], "..") == 0)
429
0
                    continue;
430
431
0
                err = k5_path_join(dirnames[i], fnames[j], &filepath);
432
433
0
                if (!err && krb5int_open_plugin(filepath, &handle, ep) == 0) {
434
0
                    err = krb5int_plugin_file_handle_array_add(&h, &count,
435
0
                                                               handle);
436
0
                    if (!err)
437
0
                        handle = NULL;  /* h takes ownership */
438
0
                }
439
440
0
                free(filepath);
441
0
                if (handle != NULL)
442
0
                    krb5int_close_plugin(handle);
443
0
            }
444
445
0
            k5_free_filenames(fnames);
446
0
        }
447
0
    }
448
449
0
    if (err == ENOENT) {
450
0
        err = 0;  /* ran out of plugins -- do nothing */
451
0
    }
452
453
0
    if (!err) {
454
0
        dirhandle->files = h;
455
0
        h = NULL;  /* dirhandle->files takes ownership */
456
0
    }
457
458
0
    if (filenames != NULL) { krb5int_free_plugin_filenames (filenames); }
459
0
    if (h         != NULL) { krb5int_plugin_file_handle_array_free (h); }
460
461
0
    return err;
462
0
}
463
464
void KRB5_CALLCONV
465
krb5int_close_plugin_dirs (struct plugin_dir_handle *dirhandle)
466
14.3k
{
467
14.3k
    size_t i;
468
469
14.3k
    if (dirhandle->files != NULL) {
470
0
        for (i = 0; dirhandle->files[i] != NULL; i++) {
471
0
            krb5int_close_plugin (dirhandle->files[i]);
472
0
        }
473
0
        free (dirhandle->files);
474
0
        dirhandle->files = NULL;
475
0
    }
476
14.3k
}
477
478
void KRB5_CALLCONV
479
krb5int_free_plugin_dir_data (void **ptrs)
480
0
{
481
    /* Nothing special to be done per pointer.  */
482
0
    free(ptrs);
483
0
}
484
485
long KRB5_CALLCONV
486
krb5int_get_plugin_dir_data (struct plugin_dir_handle *dirhandle,
487
                             const char *symname,
488
                             void ***ptrs,
489
                             struct errinfo *ep)
490
0
{
491
0
    long err = 0;
492
0
    void **p = NULL;
493
0
    size_t count = 0;
494
495
    /* XXX Do we need to add a leading "_" to the symbol name on any
496
       modern platforms?  */
497
498
0
    Tprintf("get_plugin_data_sym(%s)\n", symname);
499
500
0
    if (!err) {
501
0
        p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */
502
0
        if (p == NULL) { err = ENOMEM; }
503
0
    }
504
505
0
    if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) {
506
0
        size_t i = 0;
507
508
0
        for (i = 0; !err && (dirhandle->files[i] != NULL); i++) {
509
0
            void *sym = NULL;
510
511
0
            if (krb5int_get_plugin_data (dirhandle->files[i], symname, &sym, ep) == 0) {
512
0
                void **newp = NULL;
513
514
0
                count++;
515
0
                newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */
516
0
                if (newp == NULL) {
517
0
                    err = ENOMEM;
518
0
                } else {
519
0
                    p = newp;
520
0
                    p[count - 1] = sym;
521
0
                    p[count] = NULL;
522
0
                }
523
0
            }
524
0
        }
525
0
    }
526
527
0
    if (!err) {
528
0
        *ptrs = p;
529
0
        p = NULL; /* ptrs takes ownership */
530
0
    }
531
532
0
    free(p);
533
534
0
    return err;
535
0
}
536
537
void KRB5_CALLCONV
538
krb5int_free_plugin_dir_func (void (**ptrs)(void))
539
0
{
540
    /* Nothing special to be done per pointer.  */
541
0
    free(ptrs);
542
0
}
543
544
long KRB5_CALLCONV
545
krb5int_get_plugin_dir_func (struct plugin_dir_handle *dirhandle,
546
                             const char *symname,
547
                             void (***ptrs)(void),
548
                             struct errinfo *ep)
549
0
{
550
0
    long err = 0;
551
0
    void (**p)(void) = NULL;
552
0
    size_t count = 0;
553
554
    /* XXX Do we need to add a leading "_" to the symbol name on any
555
       modern platforms?  */
556
557
0
    Tprintf("get_plugin_data_sym(%s)\n", symname);
558
559
0
    if (!err) {
560
0
        p = calloc (1, sizeof (*p)); /* calloc initializes to NULL */
561
0
        if (p == NULL) { err = ENOMEM; }
562
0
    }
563
564
0
    if (!err && (dirhandle != NULL) && (dirhandle->files != NULL)) {
565
0
        size_t i = 0;
566
567
0
        for (i = 0; !err && (dirhandle->files[i] != NULL); i++) {
568
0
            void (*sym)(void) = NULL;
569
570
0
            if (krb5int_get_plugin_func (dirhandle->files[i], symname, &sym, ep) == 0) {
571
0
                void (**newp)(void) = NULL;
572
573
0
                count++;
574
0
                newp = realloc (p, ((count + 1) * sizeof (*p))); /* +1 for NULL */
575
0
                if (newp == NULL) {
576
0
                    err = ENOMEM;
577
0
                } else {
578
0
                    p = newp;
579
0
                    p[count - 1] = sym;
580
0
                    p[count] = NULL;
581
0
                }
582
0
            }
583
0
        }
584
0
    }
585
586
0
    if (!err) {
587
0
        *ptrs = p;
588
0
        p = NULL; /* ptrs takes ownership */
589
0
    }
590
591
0
    free(p);
592
593
0
    return err;
594
0
}