Add dladdr() for AIX
authorMatthias Kraft <Matthias.Kraft@softwareag.com>
Mon, 19 Mar 2018 17:37:46 +0000 (13:37 -0400)
committerRich Salz <rsalz@openssl.org>
Wed, 21 Mar 2018 01:33:50 +0000 (21:33 -0400)
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 <makr@gmx.eu>
Reviewed-by: Richard Levitte <levitte@openssl.org>
Reviewed-by: Rich Salz <rsalz@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/5668)

Configurations/10-main.conf
crypto/dso/dso_dlfcn.c
crypto/init.c
test/recipes/90-test_shlibload.t
test/shlibloadtest.c

index a0a9e175e1d9906061cb2bcf1b7bfef52bcfee26..72695d5e77374052ef47c776de2a9f1b8d3a0d9b 100644 (file)
@@ -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",
index 26f98bfc15d33e119ee76ba9f63bac0517cc8d12..7abfe662842272873e2b4d0bc5d8df29889a8149 100644 (file)
@@ -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 <dlfcn.h>
 #  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 <sys/ldr.h>
+#  include <errno.h>
+/* ~ 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;
     }
 
index cc3da4fb7d94f4141393ea2f18217e4d46869b67..27be7b6123f0c452c134967e9133f7c51c71dee9 100644 (file)
@@ -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();
         }
index aa8d98de29e20981d29d4c6b369c2e92d688f78e..04d52658900be5062c824ede67aaa064f5fa5585 100644 (file)
@@ -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");
 
index 062853fae26d981eede458ba09fe5f30a8a63160..f759a3198c40ee26034a818ddbc5b89bdfbd85bc 100644 (file)
 #include <openssl/ossl_typ.h>
 #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;