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