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