xref: /freebsd/crypto/openssl/crypto/dso/dso_dlfcn.c (revision 0d0c8621fd181e507f0fb50ffcca606faf66a8c2)
1 /*
2  * Copyright 2000-2021 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 "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__) || defined(__TANDEM)
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 
41 static int dlfcn_load(DSO *dso);
42 static int dlfcn_unload(DSO *dso);
43 static DSO_FUNC_TYPE dlfcn_bind_func(DSO *dso, const char *symname);
44 static char *dlfcn_name_converter(DSO *dso, const char *filename);
45 static char *dlfcn_merger(DSO *dso, const char *filespec1,
46                           const char *filespec2);
47 static int dlfcn_pathbyaddr(void *addr, char *path, int sz);
48 static void *dlfcn_globallookup(const char *name);
49 
50 static 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 
DSO_METHOD_openssl(void)64 DSO_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 
dlfcn_load(DSO * dso)96 static 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         ERR_raise(ERR_LIB_DSO, 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         ERR_raise_data(ERR_LIB_DSO, DSO_R_LOAD_FAILED,
119                        "filename(%s): %s", 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         ERR_raise(ERR_LIB_DSO, 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 
dlfcn_unload(DSO * dso)142 static int dlfcn_unload(DSO *dso)
143 {
144     void *ptr;
145     if (dso == NULL) {
146         ERR_raise(ERR_LIB_DSO, 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         ERR_raise(ERR_LIB_DSO, 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 
dlfcn_bind_func(DSO * dso,const char * symname)165 static 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         ERR_raise(ERR_LIB_DSO, ERR_R_PASSED_NULL_PARAMETER);
175         return NULL;
176     }
177     if (sk_void_num(dso->meth_data) < 1) {
178         ERR_raise(ERR_LIB_DSO, 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         ERR_raise(ERR_LIB_DSO, DSO_R_NULL_HANDLE);
184         return NULL;
185     }
186     u.dlret = dlsym(ptr, symname);
187     if (u.dlret == NULL) {
188         ERR_raise_data(ERR_LIB_DSO, DSO_R_SYM_FAILURE,
189                        "symname(%s): %s", symname, dlerror());
190         return NULL;
191     }
192     return u.sym;
193 }
194 
dlfcn_merger(DSO * dso,const char * filespec1,const char * filespec2)195 static char *dlfcn_merger(DSO *dso, const char *filespec1,
196                           const char *filespec2)
197 {
198     char *merged;
199 
200     if (!filespec1 && !filespec2) {
201         ERR_raise(ERR_LIB_DSO, 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             ERR_raise(ERR_LIB_DSO, 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             ERR_raise(ERR_LIB_DSO, 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             ERR_raise(ERR_LIB_DSO, 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 
dlfcn_name_converter(DSO * dso,const char * filename)253 static 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         ERR_raise(ERR_LIB_DSO, 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             BIO_snprintf(translated, rsize, "lib%s" DSO_EXTENSION, filename);
275         else
276             BIO_snprintf(translated, rsize, "%s" DSO_EXTENSION, filename);
277     } else {
278         BIO_snprintf(translated, rsize, "%s", filename);
279     }
280     return translated;
281 }
282 
283 # ifdef __sgi
284 /*-
285 This is a quote from IRIX manual for dladdr(3c):
286 
287      <dlfcn.h> does not contain a prototype for dladdr or definition of
288      Dl_info.  The #include <dlfcn.h>  in the SYNOPSIS line is traditional,
289      but contains no dladdr prototype and no IRIX library contains an
290      implementation.  Write your own declaration based on the code below.
291 
292      The following code is dependent on internal interfaces that are not
293      part of the IRIX compatibility guarantee; however, there is no future
294      intention to change this interface, so on a practical level, the code
295      below is safe to use on IRIX.
296 */
297 #  include <rld_interface.h>
298 #  ifndef _RLD_INTERFACE_DLFCN_H_DLADDR
299 #   define _RLD_INTERFACE_DLFCN_H_DLADDR
300 typedef struct Dl_info {
301     const char *dli_fname;
302     void *dli_fbase;
303     const char *dli_sname;
304     void *dli_saddr;
305     int dli_version;
306     int dli_reserved1;
307     long dli_reserved[4];
308 } Dl_info;
309 #  else
310 typedef struct Dl_info Dl_info;
311 #  endif
312 #  define _RLD_DLADDR             14
313 
dladdr(void * address,Dl_info * dl)314 static int dladdr(void *address, Dl_info *dl)
315 {
316     void *v;
317     v = _rld_new_interface(_RLD_DLADDR, address, dl);
318     return (int)v;
319 }
320 # endif                         /* __sgi */
321 
322 # ifdef _AIX
323 /*-
324  * See IBM's AIX Version 7.2, Technical Reference:
325  *  Base Operating System and Extensions, Volume 1 and 2
326  *  https://www.ibm.com/support/knowledgecenter/ssw_aix_72/com.ibm.aix.base/technicalreferences.htm
327  */
328 #  include <sys/ldr.h>
329 #  include <errno.h>
330 /* ~ 64 * (sizeof(struct ld_info) + _XOPEN_PATH_MAX + _XOPEN_NAME_MAX) */
331 #  define DLFCN_LDINFO_SIZE 86976
332 typedef struct Dl_info {
333     const char *dli_fname;
334 } Dl_info;
335 /*
336  * This dladdr()-implementation will also find the ptrgl (Pointer Glue) virtual
337  * address of a function, which is just located in the DATA segment instead of
338  * the TEXT segment.
339  */
dladdr(void * ptr,Dl_info * dl)340 static int dladdr(void *ptr, Dl_info *dl)
341 {
342     uintptr_t addr = (uintptr_t)ptr;
343     unsigned int found = 0;
344     struct ld_info *ldinfos, *next_ldi, *this_ldi;
345 
346     if ((ldinfos = OPENSSL_malloc(DLFCN_LDINFO_SIZE)) == NULL) {
347         errno = ENOMEM;
348         dl->dli_fname = NULL;
349         return 0;
350     }
351 
352     if ((loadquery(L_GETINFO, (void *)ldinfos, DLFCN_LDINFO_SIZE)) < 0) {
353         /*-
354          * Error handling is done through errno and dlerror() reading errno:
355          *  ENOMEM (ldinfos buffer is too small),
356          *  EINVAL (invalid flags),
357          *  EFAULT (invalid ldinfos ptr)
358          */
359         OPENSSL_free((void *)ldinfos);
360         dl->dli_fname = NULL;
361         return 0;
362     }
363     next_ldi = ldinfos;
364 
365     do {
366         this_ldi = next_ldi;
367         if (((addr >= (uintptr_t)this_ldi->ldinfo_textorg)
368              && (addr < ((uintptr_t)this_ldi->ldinfo_textorg +
369                          this_ldi->ldinfo_textsize)))
370             || ((addr >= (uintptr_t)this_ldi->ldinfo_dataorg)
371                 && (addr < ((uintptr_t)this_ldi->ldinfo_dataorg +
372                             this_ldi->ldinfo_datasize)))) {
373             char *buffer, *member;
374             size_t buffer_sz, member_len;
375 
376             buffer_sz = strlen(this_ldi->ldinfo_filename) + 1;
377             member = this_ldi->ldinfo_filename + buffer_sz;
378             if ((member_len = strlen(member)) > 0)
379                 buffer_sz += 1 + member_len + 1;
380             found = 1;
381             if ((buffer = OPENSSL_malloc(buffer_sz)) != NULL) {
382                 OPENSSL_strlcpy(buffer, this_ldi->ldinfo_filename, buffer_sz);
383                 if (member_len > 0) {
384                     /*
385                      * Need to respect a possible member name and not just
386                      * returning the path name in this case. See docs:
387                      * sys/ldr.h, loadquery() and dlopen()/RTLD_MEMBER.
388                      */
389                     OPENSSL_strlcat(buffer, "(", buffer_sz);
390                     OPENSSL_strlcat(buffer, member, buffer_sz);
391                     OPENSSL_strlcat(buffer, ")", buffer_sz);
392                 }
393                 dl->dli_fname = buffer;
394             } else {
395                 errno = ENOMEM;
396             }
397         } else {
398             next_ldi = (struct ld_info *)((uintptr_t)this_ldi +
399                                           this_ldi->ldinfo_next);
400         }
401     } while (this_ldi->ldinfo_next && !found);
402     OPENSSL_free((void *)ldinfos);
403     return (found && dl->dli_fname != NULL);
404 }
405 # endif                         /* _AIX */
406 
dlfcn_pathbyaddr(void * addr,char * path,int sz)407 static int dlfcn_pathbyaddr(void *addr, char *path, int sz)
408 {
409 # ifdef HAVE_DLINFO
410     Dl_info dli;
411     int len;
412 
413     if (addr == NULL) {
414         union {
415             int (*f) (void *, char *, int);
416             void *p;
417         } t = {
418             dlfcn_pathbyaddr
419         };
420         addr = t.p;
421     }
422 
423     if (dladdr(addr, &dli)) {
424         len = (int)strlen(dli.dli_fname);
425         if (sz <= 0) {
426 #  ifdef _AIX
427             OPENSSL_free((void *)dli.dli_fname);
428 #  endif
429             return len + 1;
430         }
431         if (len >= sz)
432             len = sz - 1;
433         memcpy(path, dli.dli_fname, len);
434         path[len++] = 0;
435 #  ifdef _AIX
436         OPENSSL_free((void *)dli.dli_fname);
437 #  endif
438         return len;
439     }
440 
441     ERR_add_error_data(2, "dlfcn_pathbyaddr(): ", dlerror());
442 # endif
443     return -1;
444 }
445 
dlfcn_globallookup(const char * name)446 static void *dlfcn_globallookup(const char *name)
447 {
448     void *ret = NULL, *handle = dlopen(NULL, RTLD_LAZY);
449 
450     if (handle) {
451         ret = dlsym(handle, name);
452         dlclose(handle);
453     }
454 
455     return ret;
456 }
457 #endif                          /* DSO_DLFCN */
458