Coverage Report

Created: 2026-10-16 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssl/crypto/dso/dso_dlfcn.c
Line
Count
Source
1
/*
2
 * Copyright 2000-2026 The OpenSSL Project Authors. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License 2.0 (the "License").  You may not use
5
 * this file except in compliance with the License.  You can obtain a copy
6
 * in the file LICENSE in the source distribution or at
7
 * https://www.openssl.org/source/license.html
8
 */
9
10
/*
11
 * We need to do this early, because stdio.h includes the header files that
12
 * handle _GNU_SOURCE and other similar macros.  Defining it later is simply
13
 * too late, because those headers are protected from re- inclusion.
14
 */
15
#ifndef _GNU_SOURCE
16
#define _GNU_SOURCE /* make sure dladdr is declared */
17
#endif
18
19
#include <stdio.h>
20
21
#include "dso_local.h"
22
#include "internal/e_os.h"
23
24
#ifdef DSO_DLFCN
25
26
#ifdef HAVE_DLFCN_H
27
#ifdef __osf__
28
#define __EXTENSIONS__
29
#endif
30
#include <dlfcn.h>
31
#define HAVE_DLINFO 1
32
#if defined(__SCO_VERSION__) || defined(_SCO_ELF) || (defined(__osf__) && !defined(RTLD_NEXT)) || (defined(__OpenBSD__) && !defined(RTLD_SELF)) || defined(__ANDROID__) || defined(__TANDEM)
33
#undef HAVE_DLINFO
34
#endif
35
#endif
36
37
/* Part of the hack in "dlfcn_load" ... */
38
#define DSO_MAX_TRANSLATED_SIZE 256
39
40
static int dlfcn_load(DSO *dso);
41
static int dlfcn_unload(DSO *dso);
42
static DSO_FUNC_TYPE dlfcn_bind_func(DSO *dso, const char *symname);
43
static char *dlfcn_name_converter(DSO *dso, const char *filename);
44
static char *dlfcn_merger(DSO *dso, const char *filespec1,
45
    const char *filespec2);
46
static int dlfcn_pathbyaddr(void *addr, char *path, int sz);
47
static void *dlfcn_globallookup(const char *name);
48
49
static DSO_METHOD dso_meth_dlfcn = {
50
    "OpenSSL 'dlfcn' shared library method",
51
    dlfcn_load,
52
    dlfcn_unload,
53
    dlfcn_bind_func,
54
    NULL, /* ctrl */
55
    dlfcn_name_converter,
56
    dlfcn_merger,
57
    NULL, /* init */
58
    NULL, /* finish */
59
    dlfcn_pathbyaddr,
60
    dlfcn_globallookup
61
};
62
63
DSO_METHOD *DSO_METHOD_openssl(void)
64
0
{
65
0
    return &dso_meth_dlfcn;
66
0
}
67
68
/*
69
 * Prior to using the dlopen() function, we should decide on the flag we
70
 * send. There's a few different ways of doing this and it's a messy
71
 * venn-diagram to match up which platforms support what. So as we don't have
72
 * autoconf yet, I'm implementing a hack that could be hacked further
73
 * relatively easily to deal with cases as we find them. Initially this is to
74
 * cope with OpenBSD.
75
 */
76
#if defined(__OpenBSD__) || defined(__NetBSD__)
77
#ifdef DL_LAZY
78
#define DLOPEN_FLAG DL_LAZY
79
#else
80
#ifdef RTLD_NOW
81
#define DLOPEN_FLAG RTLD_NOW
82
#else
83
#define DLOPEN_FLAG 0
84
#endif
85
#endif
86
#else
87
0
#define DLOPEN_FLAG RTLD_NOW /* Hope this works everywhere else */
88
#endif
89
90
/*
91
 * For this DSO_METHOD, our meth_data STACK will contain; (i) the handle
92
 * (void*) returned from dlopen().
93
 */
94
95
static int dlfcn_load(DSO *dso)
96
0
{
97
0
    void *ptr = NULL;
98
    /* See applicable comments in dso_dl.c */
99
0
    char *filename = DSO_convert_filename(dso, NULL);
100
0
    int flags = DLOPEN_FLAG;
101
0
    int saveerrno = get_last_sys_error();
102
103
0
    if (filename == NULL) {
104
0
        ERR_raise(ERR_LIB_DSO, DSO_R_NO_FILENAME);
105
0
        goto err;
106
0
    }
107
0
#ifdef RTLD_GLOBAL
108
0
    if (dso->flags & DSO_FLAG_GLOBAL_SYMBOLS)
109
0
        flags |= RTLD_GLOBAL;
110
0
#endif
111
#ifdef _AIX
112
    if (filename[strlen(filename) - 1] == ')')
113
        flags |= RTLD_MEMBER;
114
#endif
115
0
    ptr = dlopen(filename, flags);
116
0
    if (ptr == NULL) {
117
0
        ERR_raise_data(ERR_LIB_DSO, DSO_R_LOAD_FAILED,
118
0
            "filename(%s): %s", filename, dlerror());
119
0
        goto err;
120
0
    }
121
    /*
122
     * Some dlopen() implementations (e.g. solaris) do no preserve errno, even
123
     * on a successful call.
124
     */
125
0
    set_sys_error(saveerrno);
126
0
    if (!sk_void_push(dso->meth_data, (char *)ptr)) {
127
0
        ERR_raise(ERR_LIB_DSO, DSO_R_STACK_ERROR);
128
0
        goto err;
129
0
    }
130
    /* Success */
131
0
    dso->loaded_filename = filename;
132
0
    return 1;
133
0
err:
134
    /* Cleanup! */
135
0
    OPENSSL_free(filename);
136
0
    if (ptr != NULL)
137
0
        dlclose(ptr);
138
0
    return 0;
139
0
}
140
141
static int dlfcn_unload(DSO *dso)
142
0
{
143
0
    void *ptr;
144
0
    if (dso == NULL) {
145
0
        ERR_raise(ERR_LIB_DSO, ERR_R_PASSED_NULL_PARAMETER);
146
0
        return 0;
147
0
    }
148
0
    if (sk_void_num(dso->meth_data) < 1)
149
0
        return 1;
150
0
    ptr = sk_void_pop(dso->meth_data);
151
0
    if (ptr == NULL) {
152
0
        ERR_raise(ERR_LIB_DSO, DSO_R_NULL_HANDLE);
153
        /*
154
         * Should push the value back onto the stack in case of a retry.
155
         */
156
0
        sk_void_push(dso->meth_data, ptr);
157
0
        return 0;
158
0
    }
159
    /* For now I'm not aware of any errors associated with dlclose() */
160
0
    dlclose(ptr);
161
0
    return 1;
162
0
}
163
164
static DSO_FUNC_TYPE dlfcn_bind_func(DSO *dso, const char *symname)
165
0
{
166
0
    void *ptr;
167
0
    union {
168
0
        DSO_FUNC_TYPE sym;
169
0
        void *dlret;
170
0
    } u;
171
172
0
    if ((dso == NULL) || (symname == NULL)) {
173
0
        ERR_raise(ERR_LIB_DSO, ERR_R_PASSED_NULL_PARAMETER);
174
0
        return NULL;
175
0
    }
176
0
    if (sk_void_num(dso->meth_data) < 1) {
177
0
        ERR_raise(ERR_LIB_DSO, DSO_R_STACK_ERROR);
178
0
        return NULL;
179
0
    }
180
0
    ptr = sk_void_value(dso->meth_data, sk_void_num(dso->meth_data) - 1);
181
0
    if (ptr == NULL) {
182
0
        ERR_raise(ERR_LIB_DSO, DSO_R_NULL_HANDLE);
183
0
        return NULL;
184
0
    }
185
0
    u.dlret = dlsym(ptr, symname);
186
0
    if (u.dlret == NULL) {
187
0
        ERR_raise_data(ERR_LIB_DSO, DSO_R_SYM_FAILURE,
188
0
            "symname(%s): %s", symname, dlerror());
189
0
        return NULL;
190
0
    }
191
0
    return u.sym;
192
0
}
193
194
static char *dlfcn_merger(DSO *dso, const char *filespec1,
195
    const char *filespec2)
196
0
{
197
0
    char *merged;
198
199
0
    if (!filespec1 && !filespec2) {
200
0
        ERR_raise(ERR_LIB_DSO, ERR_R_PASSED_NULL_PARAMETER);
201
0
        return NULL;
202
0
    }
203
    /*
204
     * If the first file specification is a rooted path, it rules. same goes
205
     * if the second file specification is missing.
206
     */
207
0
    if (!filespec2 || (filespec1 != NULL && filespec1[0] == '/')) {
208
0
        merged = OPENSSL_strdup(filespec1);
209
0
        if (merged == NULL)
210
0
            return NULL;
211
0
    }
212
    /*
213
     * If the first file specification is missing, the second one rules.
214
     */
215
0
    else if (!filespec1) {
216
0
        merged = OPENSSL_strdup(filespec2);
217
0
        if (merged == NULL)
218
0
            return NULL;
219
0
    } else {
220
        /*
221
         * This part isn't as trivial as it looks.  It assumes that the
222
         * second file specification really is a directory, and makes no
223
         * checks whatsoever.  Therefore, the result becomes the
224
         * concatenation of filespec2 followed by a slash followed by
225
         * filespec1, taking care not to double the separator if filespec2
226
         * already ends with one.
227
         */
228
0
        size_t spec2len = strlen(filespec2);
229
0
        const char *fmt = (spec2len > 0 && filespec2[spec2len - 1] == '/')
230
0
            ? "%s%s"
231
0
            : "%s/%s";
232
233
0
        if (ossl_asprintf(&merged, fmt, filespec2, filespec1) < 0)
234
0
            return NULL;
235
0
    }
236
0
    return merged;
237
0
}
238
239
static char *dlfcn_name_converter(DSO *dso, const char *filename)
240
0
{
241
0
    char *translated = NULL;
242
243
0
    if (strchr(filename, '/') != NULL) {
244
0
        translated = OPENSSL_strdup(filename);
245
0
    } else {
246
0
        const char *fmt = (DSO_flags(dso) & DSO_FLAG_NAME_TRANSLATION_EXT_ONLY)
247
0
            ? "%s" DSO_EXTENSION
248
0
            : "lib%s" DSO_EXTENSION;
249
250
0
        (void)ossl_asprintf(&translated, fmt, filename);
251
0
    }
252
0
    if (translated == NULL)
253
0
        ERR_raise(ERR_LIB_DSO, DSO_R_NAME_TRANSLATION_FAILED);
254
0
    return translated;
255
0
}
256
257
#ifdef __sgi
258
/*-
259
This is a quote from IRIX manual for dladdr(3c):
260
261
     <dlfcn.h> does not contain a prototype for dladdr or definition of
262
     Dl_info.  The #include <dlfcn.h>  in the SYNOPSIS line is traditional,
263
     but contains no dladdr prototype and no IRIX library contains an
264
     implementation.  Write your own declaration based on the code below.
265
266
     The following code is dependent on internal interfaces that are not
267
     part of the IRIX compatibility guarantee; however, there is no future
268
     intention to change this interface, so on a practical level, the code
269
     below is safe to use on IRIX.
270
*/
271
#include <rld_interface.h>
272
#ifndef _RLD_INTERFACE_DLFCN_H_DLADDR
273
#define _RLD_INTERFACE_DLFCN_H_DLADDR
274
typedef struct Dl_info {
275
    const char *dli_fname;
276
    void *dli_fbase;
277
    const char *dli_sname;
278
    void *dli_saddr;
279
    int dli_version;
280
    int dli_reserved1;
281
    long dli_reserved[4];
282
} Dl_info;
283
#else
284
typedef struct Dl_info Dl_info;
285
#endif
286
#define _RLD_DLADDR 14
287
288
static int dladdr(void *address, Dl_info *dl)
289
{
290
    void *v;
291
    v = _rld_new_interface(_RLD_DLADDR, address, dl);
292
    return (int)v;
293
}
294
#endif /* __sgi */
295
296
#ifdef _AIX
297
/*-
298
 * See IBM's AIX Version 7.2, Technical Reference:
299
 *  Base Operating System and Extensions, Volume 1 and 2
300
 *  https://www.ibm.com/support/knowledgecenter/ssw_aix_72/com.ibm.aix.base/technicalreferences.htm
301
 */
302
#include <sys/ldr.h>
303
#include <errno.h>
304
/* ~ 64 * (sizeof(struct ld_info) + _XOPEN_PATH_MAX + _XOPEN_NAME_MAX) */
305
#define DLFCN_LDINFO_SIZE 86976
306
typedef struct Dl_info {
307
    const char *dli_fname;
308
} Dl_info;
309
/*
310
 * This dladdr()-implementation will also find the ptrgl (Pointer Glue) virtual
311
 * address of a function, which is just located in the DATA segment instead of
312
 * the TEXT segment.
313
 */
314
static int dladdr(void *ptr, Dl_info *dl)
315
{
316
    uintptr_t addr = (uintptr_t)ptr;
317
    unsigned int found = 0;
318
    struct ld_info *ldinfos, *next_ldi, *this_ldi;
319
320
    if ((ldinfos = OPENSSL_malloc(DLFCN_LDINFO_SIZE)) == NULL) {
321
        errno = ENOMEM;
322
        dl->dli_fname = NULL;
323
        return 0;
324
    }
325
326
    if ((loadquery(L_GETINFO, (void *)ldinfos, DLFCN_LDINFO_SIZE)) < 0) {
327
        /*-
328
         * Error handling is done through errno and dlerror() reading errno:
329
         *  ENOMEM (ldinfos buffer is too small),
330
         *  EINVAL (invalid flags),
331
         *  EFAULT (invalid ldinfos ptr)
332
         */
333
        OPENSSL_free((void *)ldinfos);
334
        dl->dli_fname = NULL;
335
        return 0;
336
    }
337
    next_ldi = ldinfos;
338
339
    do {
340
        this_ldi = next_ldi;
341
        if (((addr >= (uintptr_t)this_ldi->ldinfo_textorg)
342
                && (addr < ((uintptr_t)this_ldi->ldinfo_textorg + this_ldi->ldinfo_textsize)))
343
            || ((addr >= (uintptr_t)this_ldi->ldinfo_dataorg)
344
                && (addr < ((uintptr_t)this_ldi->ldinfo_dataorg + this_ldi->ldinfo_datasize)))) {
345
            char *buffer, *member;
346
            size_t buffer_sz, member_len;
347
348
            buffer_sz = strlen(this_ldi->ldinfo_filename) + 1;
349
            member = this_ldi->ldinfo_filename + buffer_sz;
350
            if ((member_len = strlen(member)) > 0)
351
                buffer_sz += 1 + member_len + 1;
352
            found = 1;
353
            if ((buffer = OPENSSL_malloc(buffer_sz)) != NULL) {
354
                OPENSSL_strlcpy(buffer, this_ldi->ldinfo_filename, buffer_sz);
355
                if (member_len > 0) {
356
                    /*
357
                     * Need to respect a possible member name and not just
358
                     * returning the path name in this case. See docs:
359
                     * sys/ldr.h, loadquery() and dlopen()/RTLD_MEMBER.
360
                     */
361
                    OPENSSL_strlcat(buffer, "(", buffer_sz);
362
                    OPENSSL_strlcat(buffer, member, buffer_sz);
363
                    OPENSSL_strlcat(buffer, ")", buffer_sz);
364
                }
365
                dl->dli_fname = buffer;
366
            } else {
367
                errno = ENOMEM;
368
            }
369
        } else {
370
            next_ldi = (struct ld_info *)((uintptr_t)this_ldi + this_ldi->ldinfo_next);
371
        }
372
    } while (this_ldi->ldinfo_next && !found);
373
    OPENSSL_free((void *)ldinfos);
374
    return (found && dl->dli_fname != NULL);
375
}
376
#endif /* _AIX */
377
378
static int dlfcn_pathbyaddr(void *addr, char *path, int sz)
379
0
{
380
0
#ifdef HAVE_DLINFO
381
0
    Dl_info dli;
382
0
    int len;
383
384
0
    if (addr == NULL) {
385
0
        union {
386
0
            int (*f)(void *, char *, int);
387
0
            void *p;
388
0
        } t = {
389
0
            dlfcn_pathbyaddr
390
0
        };
391
0
        addr = t.p;
392
0
    }
393
394
0
    if (dladdr(addr, &dli)) {
395
0
        len = (int)strlen(dli.dli_fname);
396
0
        if (sz <= 0) {
397
#ifdef _AIX
398
            OPENSSL_free((void *)dli.dli_fname);
399
#endif
400
0
            return len + 1;
401
0
        }
402
0
        if (len >= sz)
403
0
            len = sz - 1;
404
0
        memcpy(path, dli.dli_fname, len);
405
0
        path[len++] = 0;
406
#ifdef _AIX
407
        OPENSSL_free((void *)dli.dli_fname);
408
#endif
409
0
        return len;
410
0
    }
411
412
0
    ERR_add_error_data(2, "dlfcn_pathbyaddr(): ", dlerror());
413
0
#endif
414
0
    return -1;
415
0
}
416
417
static void *dlfcn_globallookup(const char *name)
418
0
{
419
0
    void *ret = NULL, *handle = dlopen(NULL, RTLD_LAZY);
420
421
0
    if (handle) {
422
0
        ret = dlsym(handle, name);
423
0
        dlclose(handle);
424
0
    }
425
426
0
    return ret;
427
0
}
428
#endif /* DSO_DLFCN */