From 4af14b7b018750bf3584587068211948924738fb Mon Sep 17 00:00:00 2001 From: Matthias Kraft Date: Mon, 19 Mar 2018 13:37:46 -0400 Subject: [PATCH] Add dladdr() for AIX Although it deviates from the actual prototype of DSO_dsobyaddr(), this is now ISO C compliant and gcc -Wpedantic accepts the code. Added DATA segment checking to catch ptrgl virtual addresses. Avoid memleaks with every AIX/dladdr() call. Removed debug-fprintf()s. Added test case for DSO_dsobyaddr(), which will eventually call dladdr(). Removed unecessary AIX ifdefs again. The implementation can only lookup function symbols, no data symbols. Added PIC-flag to aix*-cc build targets. As AIX is missing a dladdr() implementation it is currently uncertain our exit()-handlers can still be called when the application exits. After dlclose() the whole library might have been unloaded already. Signed-off-by: Matthias Kraft Reviewed-by: Richard Levitte Reviewed-by: Rich Salz (Merged from https://github.com/openssl/openssl/pull/5668) --- Configurations/10-main.conf | 2 + crypto/dso/dso_dlfcn.c | 80 ++++++++++++++++++++++++++++++-- crypto/init.c | 15 ++++++ test/recipes/90-test_shlibload.t | 6 ++- test/shlibloadtest.c | 49 ++++++++++++++++++- 5 files changed, 145 insertions(+), 7 deletions(-) diff --git a/Configurations/10-main.conf b/Configurations/10-main.conf index a0a9e175e1..72695d5e77 100644 --- a/Configurations/10-main.conf +++ b/Configurations/10-main.conf @@ -1212,6 +1212,7 @@ my %targets = ( perlasm_scheme => "aix32", dso_scheme => "dlfcn", shared_target => "aix-shared", + shared_cflag => "-qpic", shared_ldflag => "-G", shared_extension => ".so.\$(SHLIB_VERSION_NUMBER)", arflags => "-X32 r", @@ -1232,6 +1233,7 @@ my %targets = ( perlasm_scheme => "aix64", dso_scheme => "dlfcn", shared_target => "aix-shared", + shared_cflag => "-qpic", shared_ldflag => "-G", shared_extension => ".so.\$(SHLIB_VERSION_NUMBER)", arflags => "-X64 r", diff --git a/crypto/dso/dso_dlfcn.c b/crypto/dso/dso_dlfcn.c index 26f98bfc15..7abfe66284 100644 --- a/crypto/dso/dso_dlfcn.c +++ b/crypto/dso/dso_dlfcn.c @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 The OpenSSL Project Authors. All Rights Reserved. + * Copyright 2000-2018 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the OpenSSL license (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy @@ -26,7 +26,7 @@ # endif # include # define HAVE_DLINFO 1 -# if defined(_AIX) || defined(__CYGWIN__) || \ +# if defined(__CYGWIN__) || \ defined(__SCO_VERSION__) || defined(_SCO_ELF) || \ (defined(__osf__) && !defined(RTLD_NEXT)) || \ (defined(__OpenBSD__) && !defined(RTLD_SELF)) || \ @@ -308,6 +308,73 @@ static int dladdr(void *address, Dl_info *dl) } # endif /* __sgi */ +# ifdef _AIX +/*- + * See IBM's AIX Version 7.2, Technical Reference: + * Base Operating System and Extensions, Volume 1 and 2 + * https://www.ibm.com/support/knowledgecenter/ssw_aix_72/com.ibm.aix.base/technicalreferences.htm + */ +# include +# include +/* ~ 64 * (sizeof(struct ld_info) + _XOPEN_PATH_MAX + _XOPEN_NAME_MAX) */ +# define DLFCN_LDINFO_SIZE 86976 +typedef struct Dl_info { + const char *dli_fname; +} Dl_info; +/* + * This dladdr()-implementation will also find the ptrgl (Pointer Glue) virtual + * address of a function, which is just located in the DATA segment instead of + * the TEXT segment. + */ +static int dladdr(void *addr, Dl_info *dl) +{ + unsigned int found = 0; + struct ld_info *ldinfos, *next_ldi, *this_ldi; + + if ((ldinfos = (struct ld_info *)OPENSSL_malloc(DLFCN_LDINFO_SIZE)) == NULL) { + errno = ENOMEM; + dl->dli_fname = NULL; + return 0; + } + + if ((loadquery(L_GETINFO, (void *)ldinfos, DLFCN_LDINFO_SIZE)) < 0) { + /*- + * Error handling is done through errno and dlerror() reading errno: + * ENOMEM (ldinfos buffer is too small), + * EINVAL (invalid flags), + * EFAULT (invalid ldinfos ptr) + */ + OPENSSL_free((void *)ldinfos); + dl->dli_fname = NULL; + return 0; + } + next_ldi = ldinfos; + + do { + this_ldi = next_ldi; + if (((addr >= this_ldi->ldinfo_textorg) + && (addr < (this_ldi->ldinfo_textorg + this_ldi->ldinfo_textsize))) + || ((addr >= this_ldi->ldinfo_dataorg) + && (addr < + (this_ldi->ldinfo_dataorg + this_ldi->ldinfo_datasize)))) { + found = 1; + /* + * Ignoring the possibility of a member name and just returning + * the path name. See docs: sys/ldr.h, loadquery() and + * dlopen()/RTLD_MEMBER. + */ + if ((dl->dli_fname = + OPENSSL_strdup(this_ldi->ldinfo_filename)) == NULL) + errno = ENOMEM; + } else { + next_ldi = (char *)this_ldi + this_ldi->ldinfo_next; + } + } while (this_ldi->ldinfo_next && !found); + OPENSSL_free((void *)ldinfos); + return (found && dl->dli_fname != NULL); +} +# endif /* _AIX */ + static int dlfcn_pathbyaddr(void *addr, char *path, int sz) { # ifdef HAVE_DLINFO @@ -326,12 +393,19 @@ static int dlfcn_pathbyaddr(void *addr, char *path, int sz) if (dladdr(addr, &dli)) { len = (int)strlen(dli.dli_fname); - if (sz <= 0) + if (sz <= 0) { +# ifdef _AIX + OPENSSL_free(dli.dli_fname); +# endif return len + 1; + } if (len >= sz) len = sz - 1; memcpy(path, dli.dli_fname, len); path[len++] = 0; +# ifdef _AIX + OPENSSL_free(dli.dli_fname); +# endif return len; } diff --git a/crypto/init.c b/crypto/init.c index cc3da4fb7d..27be7b6123 100644 --- a/crypto/init.c +++ b/crypto/init.c @@ -119,6 +119,15 @@ DEFINE_RUN_ONCE_STATIC(ossl_init_base) ERR_set_mark(); dso = DSO_dsobyaddr(&base_inited, DSO_FLAG_NO_UNLOAD_ON_FREE); +# ifdef OPENSSL_INIT_DEBUG + fprintf(stderr, "OPENSSL_INIT: obtained DSO reference? %s\n", + (dso == NULL ? "No!" : "Yes.")); + /* + * In case of No!, it is uncertain our exit()-handlers can still be + * called. After dlclose() the whole library might have been unloaded + * already. + */ +# endif DSO_free(dso); ERR_pop_to_mark(); } @@ -685,6 +694,12 @@ int OPENSSL_atexit(void (*handler)(void)) ERR_set_mark(); dso = DSO_dsobyaddr(handlersym.sym, DSO_FLAG_NO_UNLOAD_ON_FREE); +# ifdef OPENSSL_INIT_DEBUG + fprintf(stderr, + "OPENSSL_INIT: OPENSSL_atexit: obtained DSO reference? %s\n", + (dso == NULL ? "No!" : "Yes.")); + /* See same code above in ossl_init_base() for an explanation. */ +# endif DSO_free(dso); ERR_pop_to_mark(); } diff --git a/test/recipes/90-test_shlibload.t b/test/recipes/90-test_shlibload.t index aa8d98de29..04d5265890 100644 --- a/test/recipes/90-test_shlibload.t +++ b/test/recipes/90-test_shlibload.t @@ -1,5 +1,5 @@ #! /usr/bin/env perl -# Copyright 2016 The OpenSSL Project Authors. All Rights Reserved. +# Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved. # # Licensed under the OpenSSL license (the "License"). You may not use # this file except in compliance with the License. You can obtain a copy @@ -20,7 +20,7 @@ use configdata; plan skip_all => "Test only supported in a shared build" if disabled("shared"); -plan tests => 3; +plan tests => 4; my $libcrypto_idx = $unified_info{rename}->{libcrypto} // "libcrypto"; my $libssl_idx = $unified_info{rename}->{libssl} // "libssl"; @@ -35,4 +35,6 @@ ok(run(test(["shlibloadtest", "-ssl_first", $libcrypto, $libssl])), "running shlibloadtest -ssl_first"); ok(run(test(["shlibloadtest", "-just_crypto", $libcrypto, $libssl])), "running shlibloadtest -just_crypto"); +ok(run(test(["shlibloadtest", "-dso_ref", $libcrypto, $libssl])), + "running shlibloadtest -dso_ref"); diff --git a/test/shlibloadtest.c b/test/shlibloadtest.c index 062853fae2..f759a3198c 100644 --- a/test/shlibloadtest.c +++ b/test/shlibloadtest.c @@ -15,16 +15,21 @@ #include #include "testutil.h" +typedef void DSO; + typedef const SSL_METHOD * (*TLS_method_t)(void); typedef SSL_CTX * (*SSL_CTX_new_t)(const SSL_METHOD *meth); typedef void (*SSL_CTX_free_t)(SSL_CTX *); typedef unsigned long (*ERR_get_error_t)(void); typedef unsigned long (*OpenSSL_version_num_t)(void); +typedef DSO * (*DSO_dsobyaddr_t)(void (*addr)(), int flags); +typedef int (*DSO_free_t)(DSO *dso); typedef enum test_types_en { CRYPTO_FIRST, SSL_FIRST, - JUST_CRYPTO + JUST_CRYPTO, + DSO_REFTEST } TEST_TYPE; static TEST_TYPE test_type; @@ -102,6 +107,8 @@ static int test_lib(void) SSL_CTX_free_t mySSL_CTX_free; ERR_get_error_t myERR_get_error; OpenSSL_version_num_t myOpenSSL_version_num; + DSO_dsobyaddr_t myDSO_dsobyaddr; + DSO_free_t myDSO_free; int result = 0; switch (test_type) { @@ -119,9 +126,13 @@ static int test_lib(void) || !TEST_true(shlib_load(path_crypto, &cryptolib))) goto end; break; + case DSO_REFTEST: + if (!TEST_true(shlib_load(path_crypto, &cryptolib))) + goto end; + break; } - if (test_type != JUST_CRYPTO) { + if (test_type != JUST_CRYPTO && test_type != DSO_REFTEST) { if (!TEST_true(shlib_sym(ssllib, "TLS_method", &symbols[0].sym)) || !TEST_true(shlib_sym(ssllib, "SSL_CTX_new", &symbols[1].sym)) || !TEST_true(shlib_sym(ssllib, "SSL_CTX_free", &symbols[2].sym))) @@ -157,6 +168,34 @@ static int test_lib(void) OPENSSL_VERSION_NUMBER & ~COMPATIBILITY_MASK) goto end; + if (test_type == DSO_REFTEST) { +# ifdef DSO_DLFCN + /* + * This is resembling the code used in ossl_init_base() and + * OPENSSL_atexit() to block unloading the library after dlclose(). + * We are not testing this on Windows, because it is done there in a + * completely different way. Especially as a call to DSO_dsobyaddr() + * will always return an error, because DSO_pathbyaddr() is not + * implemented there. + */ + if (!TEST_true(shlib_sym(cryptolib, "DSO_dsobyaddr", &symbols[0].sym)) + || !TEST_true(shlib_sym(cryptolib, "DSO_free", + &symbols[1].sym))) + goto end; + + myDSO_dsobyaddr = (DSO_dsobyaddr_t)symbols[0].func; + myDSO_free = (DSO_free_t)symbols[1].func; + + { + DSO *hndl; + /* use known symbol from crypto module */ + if (!TEST_ptr(hndl = DSO_dsobyaddr((void (*)())ERR_get_error, 0))) + goto end; + DSO_free(hndl); + } +# endif /* DSO_DLFCN */ + } + switch (test_type) { case JUST_CRYPTO: if (!TEST_true(shlib_close(cryptolib))) @@ -172,6 +211,10 @@ static int test_lib(void) || !TEST_true(shlib_close(cryptolib))) goto end; break; + case DSO_REFTEST: + if (!TEST_true(shlib_close(cryptolib))) + goto end; + break; } result = 1; @@ -191,6 +234,8 @@ int setup_tests(void) test_type = SSL_FIRST; } else if (strcmp(p, "-just_crypto") == 0) { test_type = JUST_CRYPTO; + } else if (strcmp(p, "-dso_ref") == 0) { + test_type = JUST_CRYPTO; } else { TEST_error("Unrecognised argument"); return 0; -- 2.34.1