xref: /openssl/crypto/dso/dso_dlfcn.c (revision 2c536c8b)
1 /*
2  * Copyright 2000-2024 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 "internal/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             return NULL;
212     }
213     /*
214      * If the first file specification is missing, the second one rules.
215      */
216     else if (!filespec1) {
217         merged = OPENSSL_strdup(filespec2);
218         if (merged == NULL)
219             return NULL;
220     } else {
221         /*
222          * This part isn't as trivial as it looks.  It assumes that the
223          * second file specification really is a directory, and makes no
224          * checks whatsoever.  Therefore, the result becomes the
225          * concatenation of filespec2 followed by a slash followed by
226          * filespec1.
227          */
228         int spec2len, len;
229 
230         spec2len = strlen(filespec2);
231         len = spec2len + strlen(filespec1);
232 
233         if (spec2len && filespec2[spec2len - 1] == '/') {
234             spec2len--;
235             len--;
236         }
237         merged = OPENSSL_malloc(len + 2);
238         if (merged == NULL)
239             return NULL;
240         strcpy(merged, filespec2);
241         merged[spec2len] = '/';
242         strcpy(&merged[spec2len + 1], filespec1);
243     }
244     return merged;
245 }
246 
dlfcn_name_converter(DSO * dso,const char * filename)247 static char *dlfcn_name_converter(DSO *dso, const char *filename)
248 {
249     char *translated;
250     int len, rsize, transform;
251 
252     len = strlen(filename);
253     rsize = len + 1;
254     transform = (strchr(filename, '/') == NULL);
255     if (transform) {
256         /* We will convert this to "%s.so" or "lib%s.so" etc */
257         rsize += strlen(DSO_EXTENSION);    /* The length of ".so" */
258         if ((DSO_flags(dso) & DSO_FLAG_NAME_TRANSLATION_EXT_ONLY) == 0)
259             rsize += 3;         /* The length of "lib" */
260     }
261     translated = OPENSSL_malloc(rsize);
262     if (translated == NULL) {
263         ERR_raise(ERR_LIB_DSO, DSO_R_NAME_TRANSLATION_FAILED);
264         return NULL;
265     }
266     if (transform) {
267         if ((DSO_flags(dso) & DSO_FLAG_NAME_TRANSLATION_EXT_ONLY) == 0)
268             BIO_snprintf(translated, rsize, "lib%s" DSO_EXTENSION, filename);
269         else
270             BIO_snprintf(translated, rsize, "%s" DSO_EXTENSION, filename);
271     } else {
272         BIO_snprintf(translated, rsize, "%s", filename);
273     }
274     return translated;
275 }
276 
277 # ifdef __sgi
278 /*-
279 This is a quote from IRIX manual for dladdr(3c):
280 
281      <dlfcn.h> does not contain a prototype for dladdr or definition of
282      Dl_info.  The #include <dlfcn.h>  in the SYNOPSIS line is traditional,
283      but contains no dladdr prototype and no IRIX library contains an
284      implementation.  Write your own declaration based on the code below.
285 
286      The following code is dependent on internal interfaces that are not
287      part of the IRIX compatibility guarantee; however, there is no future
288      intention to change this interface, so on a practical level, the code
289      below is safe to use on IRIX.
290 */
291 #  include <rld_interface.h>
292 #  ifndef _RLD_INTERFACE_DLFCN_H_DLADDR
293 #   define _RLD_INTERFACE_DLFCN_H_DLADDR
294 typedef struct Dl_info {
295     const char *dli_fname;
296     void *dli_fbase;
297     const char *dli_sname;
298     void *dli_saddr;
299     int dli_version;
300     int dli_reserved1;
301     long dli_reserved[4];
302 } Dl_info;
303 #  else
304 typedef struct Dl_info Dl_info;
305 #  endif
306 #  define _RLD_DLADDR             14
307 
dladdr(void * address,Dl_info * dl)308 static int dladdr(void *address, Dl_info *dl)
309 {
310     void *v;
311     v = _rld_new_interface(_RLD_DLADDR, address, dl);
312     return (int)v;
313 }
314 # endif                         /* __sgi */
315 
316 # ifdef _AIX
317 /*-
318  * See IBM's AIX Version 7.2, Technical Reference:
319  *  Base Operating System and Extensions, Volume 1 and 2
320  *  https://www.ibm.com/support/knowledgecenter/ssw_aix_72/com.ibm.aix.base/technicalreferences.htm
321  */
322 #  include <sys/ldr.h>
323 #  include <errno.h>
324 /* ~ 64 * (sizeof(struct ld_info) + _XOPEN_PATH_MAX + _XOPEN_NAME_MAX) */
325 #  define DLFCN_LDINFO_SIZE 86976
326 typedef struct Dl_info {
327     const char *dli_fname;
328 } Dl_info;
329 /*
330  * This dladdr()-implementation will also find the ptrgl (Pointer Glue) virtual
331  * address of a function, which is just located in the DATA segment instead of
332  * the TEXT segment.
333  */
dladdr(void * ptr,Dl_info * dl)334 static int dladdr(void *ptr, Dl_info *dl)
335 {
336     uintptr_t addr = (uintptr_t)ptr;
337     unsigned int found = 0;
338     struct ld_info *ldinfos, *next_ldi, *this_ldi;
339 
340     if ((ldinfos = OPENSSL_malloc(DLFCN_LDINFO_SIZE)) == NULL) {
341         errno = ENOMEM;
342         dl->dli_fname = NULL;
343         return 0;
344     }
345 
346     if ((loadquery(L_GETINFO, (void *)ldinfos, DLFCN_LDINFO_SIZE)) < 0) {
347         /*-
348          * Error handling is done through errno and dlerror() reading errno:
349          *  ENOMEM (ldinfos buffer is too small),
350          *  EINVAL (invalid flags),
351          *  EFAULT (invalid ldinfos ptr)
352          */
353         OPENSSL_free((void *)ldinfos);
354         dl->dli_fname = NULL;
355         return 0;
356     }
357     next_ldi = ldinfos;
358 
359     do {
360         this_ldi = next_ldi;
361         if (((addr >= (uintptr_t)this_ldi->ldinfo_textorg)
362              && (addr < ((uintptr_t)this_ldi->ldinfo_textorg +
363                          this_ldi->ldinfo_textsize)))
364             || ((addr >= (uintptr_t)this_ldi->ldinfo_dataorg)
365                 && (addr < ((uintptr_t)this_ldi->ldinfo_dataorg +
366                             this_ldi->ldinfo_datasize)))) {
367             char *buffer, *member;
368             size_t buffer_sz, member_len;
369 
370             buffer_sz = strlen(this_ldi->ldinfo_filename) + 1;
371             member = this_ldi->ldinfo_filename + buffer_sz;
372             if ((member_len = strlen(member)) > 0)
373                 buffer_sz += 1 + member_len + 1;
374             found = 1;
375             if ((buffer = OPENSSL_malloc(buffer_sz)) != NULL) {
376                 OPENSSL_strlcpy(buffer, this_ldi->ldinfo_filename, buffer_sz);
377                 if (member_len > 0) {
378                     /*
379                      * Need to respect a possible member name and not just
380                      * returning the path name in this case. See docs:
381                      * sys/ldr.h, loadquery() and dlopen()/RTLD_MEMBER.
382                      */
383                     OPENSSL_strlcat(buffer, "(", buffer_sz);
384                     OPENSSL_strlcat(buffer, member, buffer_sz);
385                     OPENSSL_strlcat(buffer, ")", buffer_sz);
386                 }
387                 dl->dli_fname = buffer;
388             } else {
389                 errno = ENOMEM;
390             }
391         } else {
392             next_ldi = (struct ld_info *)((uintptr_t)this_ldi +
393                                           this_ldi->ldinfo_next);
394         }
395     } while (this_ldi->ldinfo_next && !found);
396     OPENSSL_free((void *)ldinfos);
397     return (found && dl->dli_fname != NULL);
398 }
399 # endif                         /* _AIX */
400 
dlfcn_pathbyaddr(void * addr,char * path,int sz)401 static int dlfcn_pathbyaddr(void *addr, char *path, int sz)
402 {
403 # ifdef HAVE_DLINFO
404     Dl_info dli;
405     int len;
406 
407     if (addr == NULL) {
408         union {
409             int (*f) (void *, char *, int);
410             void *p;
411         } t = {
412             dlfcn_pathbyaddr
413         };
414         addr = t.p;
415     }
416 
417     if (dladdr(addr, &dli)) {
418         len = (int)strlen(dli.dli_fname);
419         if (sz <= 0) {
420 #  ifdef _AIX
421             OPENSSL_free((void *)dli.dli_fname);
422 #  endif
423             return len + 1;
424         }
425         if (len >= sz)
426             len = sz - 1;
427         memcpy(path, dli.dli_fname, len);
428         path[len++] = 0;
429 #  ifdef _AIX
430         OPENSSL_free((void *)dli.dli_fname);
431 #  endif
432         return len;
433     }
434 
435     ERR_add_error_data(2, "dlfcn_pathbyaddr(): ", dlerror());
436 # endif
437     return -1;
438 }
439 
dlfcn_globallookup(const char * name)440 static void *dlfcn_globallookup(const char *name)
441 {
442     void *ret = NULL, *handle = dlopen(NULL, RTLD_LAZY);
443 
444     if (handle) {
445         ret = dlsym(handle, name);
446         dlclose(handle);
447     }
448 
449     return ret;
450 }
451 #endif                          /* DSO_DLFCN */
452