From 3e74e2e3bb3936c95a0bae992c94c04d54ce246f Mon Sep 17 00:00:00 2001 From: Nicolas Williams Date: Wed, 4 Sep 2013 22:23:13 -0500 Subject: [PATCH] Fix some DLL hell: use dladdr() to find plugin dir Normally one would dlopen() a shared object's basename, not its absolute path. However, lib/krb5/plugin.c, in an effort to be zero-conf-ish, wants to readdir() to find plugins to load, and in the process it ends up defeating the RTLD's search-the-caller's-rpath. This commit partially addresses this by allowing the use of $ORIGIN in plugin_dir values and using them for the default (except on OS X). This allows multiple Heimdal versions installed on the same host, but with different plugin ABIs, to co-exist. A step forward for doing make check on hosts where Heimdal is installed. For now we hardcode $ORIGIN/../lib/plugin/krb5 (linux, Solaris, *BSD), or $ORIGIN (Windows; for assemblies objects need to be in the same directory) and we eval $ORIGIN by using dladdr() (Linux, Solaris) or GetModuleHandleEx() (Win32, via a dladdr() wrapper in libroken) to find the path to libkrb5 whose dirname to use as $ORIGIN. For Windows, because we need the plugins to be in the same directory as libkrb5, we require a prefix on plugin DLLs ("plugin_krb5_") to distinguish them from other objects. We should add a special token to mean "look in $ORIGIN, sure, but dlopen() the plugin basenames only (so the RTLD can search the rpath)". --- cf/dlopen.m4 | 8 +++++++ configure.ac | 1 + include/config.h.w32 | 4 ++++ lib/krb5/context.c | 10 ++++++++- lib/krb5/plugin.c | 49 ++++++++++++++++++++++++++++++++++++++++--- lib/roken/dlfcn.hin | 7 +++++++ lib/roken/dlfcn_w32.c | 35 +++++++++++++++++++++++++++++++ 7 files changed, 110 insertions(+), 4 deletions(-) diff --git a/cf/dlopen.m4 b/cf/dlopen.m4 index 17e3c6203..1756843fc 100644 --- a/cf/dlopen.m4 +++ b/cf/dlopen.m4 @@ -9,3 +9,11 @@ AC_DEFUN([rk_DLOPEN], [ #endif],[0,0]) AM_CONDITIONAL(HAVE_DLOPEN, test "$ac_cv_funclib_dlopen" != no) ]) + +AC_DEFUN([rk_DLADDR], [ + AC_FIND_FUNC_NO_LIBS(dladdr, dl,[ +#ifdef HAVE_DLFCN_H +#include +#endif],[0,0]) + AM_CONDITIONAL(HAVE_DLADDR, test "$ac_cv_funclib_dladdr" != no) +]) diff --git a/configure.ac b/configure.ac index 1ea467a18..647b2fa20 100644 --- a/configure.ac +++ b/configure.ac @@ -500,6 +500,7 @@ AC_MSG_RESULT($ac_rk_have___sync_add_and_fetch) AC_FUNC_MMAP KRB_CAPABILITIES +rk_DLADDR AC_CHECK_GETPWNAM_R_POSIX diff --git a/include/config.h.w32 b/include/config.h.w32 index 1b0048b1f..49e81c59f 100644 --- a/include/config.h.w32 +++ b/include/config.h.w32 @@ -303,6 +303,10 @@ static const char *const rcsid[] = { (const char *)rcsid, "@(#)" msg } /* MSVC doesn't provide a , but we implement it in lib/roken. */ #define HAVE_DLOPEN 1 +/* Define to 1 if you have the `dladdr' function. */ +/* MSVC doesn't provide a , but we implement it in lib/roken. */ +#define HAVE_DLADDR 1 + /* Define to 1 if you have the `dn_expand' function. */ /* #undef HAVE_DN_EXPAND */ diff --git a/lib/krb5/context.c b/lib/krb5/context.c index d69738bb5..f4dc4404a 100644 --- a/lib/krb5/context.c +++ b/lib/krb5/context.c @@ -348,8 +348,12 @@ kt_ops_copy(krb5_context context, const krb5_context src_context) } static const char *sysplugin_dirs[] = { - LIBDIR "/plugin/krb5", + "$ORIGIN/../lib/plugin/krb5", +#ifdef _WIN32 + "$ORIGIN/../lib", +#endif #ifdef __APPLE__ + LIBDIR "/plugin/krb5", "/Library/KerberosPlugins/KerberosFrameworkPlugins", "/System/Library/KerberosPlugins/KerberosFrameworkPlugins", #endif @@ -362,10 +366,14 @@ init_context_once(void *ctx) krb5_context context = ctx; char **dirs; +#ifdef _WIN32 + dirs = rk_UNCONST(sysplugin_dirs); +#else dirs = krb5_config_get_strings(context, NULL, "libdefaults", "plugin_dir", NULL); if (dirs == NULL) dirs = rk_UNCONST(sysplugin_dirs); +#endif _krb5_load_plugins(context, "krb5", (const char **)dirs); diff --git a/lib/krb5/plugin.c b/lib/krb5/plugin.c index a4328c1ca..088ffb57c 100644 --- a/lib/krb5/plugin.c +++ b/lib/krb5/plugin.c @@ -219,6 +219,38 @@ plug_dealloc(void *ptr) dlclose(p->dsohandle); } +static char * +resolve_origin(const char *di) +{ +#ifdef HAVE_DLADDR + Dl_info dl_info; + const char *dname; + char *path, *p; +#endif + + if (strncmp(di, "$ORIGIN/", sizeof("$ORIGIN/") - 1) && + strcmp(di, "$ORIGIN")) + return strdup(di); + +#ifndef HAVE_DLADDR + return strdup(LIBDIR "/plugin/krb5"); +#else + di += sizeof("$ORIGIN") - 1; + + if (dladdr(_krb5_load_plugins, &dl_info) == 0) + return strdup(LIBDIR "/plugin/krb5"); + + dname = dl_info.dli_fname; + p = strrchr(dname, '/'); + if (p) + *p = '\0'; + + if (asprintf(&path, "%s%s", dname, di) == -1) + return NULL; + return path; +#endif +} + /** * Load plugins (new system) for the given module @name (typicall @@ -239,6 +271,7 @@ _krb5_load_plugins(krb5_context context, const char *name, const char **paths) struct dirent *entry; krb5_error_code ret; const char **di; + char *dirname = NULL; DIR *d; HEIMDAL_MUTEX_lock(&plugin_mutex); @@ -264,7 +297,11 @@ _krb5_load_plugins(krb5_context context, const char *name, const char **paths) heim_release(s); for (di = paths; *di != NULL; di++) { - d = opendir(*di); + free(dirname); + dirname = resolve_origin(*di); + if (dirname == NULL) + continue; + d = opendir(dirname); if (d == NULL) continue; rk_cloexec_dir(d); @@ -279,16 +316,21 @@ _krb5_load_plugins(krb5_context context, const char *name, const char **paths) if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0'))) continue; +#ifdef _WIN32 + if (strncmp(n, "plugin_krb5_", sizeof("plugin_krb5_") - 1)) + continue; +#endif + ret = 0; #ifdef __APPLE__ { /* support loading bundles on MacOS */ size_t len = strlen(n); if (len > 7 && strcmp(&n[len - 7], ".bundle") == 0) - ret = asprintf(&path, "%s/%s/Contents/MacOS/%.*s", *di, n, (int)(len - 7), n); + ret = asprintf(&path, "%s/%s/Contents/MacOS/%.*s", dirname, n, (int)(len - 7), n); } #endif if (ret < 0 || path == NULL) - ret = asprintf(&path, "%s/%s", *di, n); + ret = asprintf(&path, "%s/%s", dirname, n); if (ret < 0 || path == NULL) continue; @@ -318,6 +360,7 @@ _krb5_load_plugins(krb5_context context, const char *name, const char **paths) } closedir(d); } + free(dirname); HEIMDAL_MUTEX_unlock(&plugin_mutex); heim_release(module); #endif /* HAVE_DLOPEN */ diff --git a/lib/roken/dlfcn.hin b/lib/roken/dlfcn.hin index bca8cb054..54162999e 100644 --- a/lib/roken/dlfcn.hin +++ b/lib/roken/dlfcn.hin @@ -32,6 +32,8 @@ #ifndef __dlfcn_h__ #define __dlfcn_h__ +#include + #ifndef ROKEN_LIB_FUNCTION #ifdef _WIN32 #define ROKEN_LIB_FUNCTION @@ -75,6 +77,11 @@ dlopen(const char *, int); ROKEN_LIB_FUNCTION DLSYM_RET_TYPE ROKEN_LIB_CALL dlsym(void *, const char *); +typedef struct Dl_info { + char *dli_fname; + char _dli_buf[MAX_PATH + 2]; +} Dl_info, Dl_info_t; + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/lib/roken/dlfcn_w32.c b/lib/roken/dlfcn_w32.c index 0885324a8..7c3feb242 100644 --- a/lib/roken/dlfcn_w32.c +++ b/lib/roken/dlfcn_w32.c @@ -163,3 +163,38 @@ dlsym(void * vhm, const char * func_name) return (DLSYM_RET_TYPE)(ULONG_PTR)GetProcAddress(hm, func_name); } +ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL +dladdr(void *addr, Dl_info *dli) +{ + HMODULE hm; + int ret; + DWORD nsize; + char *p; + + memset(dli, 0, sizeof(*dli)); + + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCTSTR)addr, &hm)) + return -1; + + nsize = GetModuleFileName(hm, dli->_dli_buf, sizeof(dli->_dli_buf)); + dli->_dli_buf[sizeof(dli->_dli_buf) - 1] = '\0'; + if (nsize >= sizeof(dli->_dli_buf)) + return 0; /* truncated? can't be... */ + + /* + * Normalize path component separators, since our caller may want to + * portably take the dirname or basename of dli->dli_fname, + * searching for the last '/'. + */ + for (p = dli->_dli_buf; + p < &dli->_dli_buf[sizeof(dli->_dli_buf) - 1] && *p; + p++) { + if (*p == '\\') + *p = '/'; + } + + dli->dli_fname = dli->_dli_buf; + return 1; +}