summaryrefslogtreecommitdiffstats
path: root/lib/util/sudo_dso.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/util/sudo_dso.c')
-rw-r--r--lib/util/sudo_dso.c432
1 files changed, 432 insertions, 0 deletions
diff --git a/lib/util/sudo_dso.c b/lib/util/sudo_dso.c
new file mode 100644
index 0000000..0124b20
--- /dev/null
+++ b/lib/util/sudo_dso.c
@@ -0,0 +1,432 @@
+/*
+ * SPDX-License-Identifier: ISC
+ *
+ * Copyright (c) 2010, 2012-2014, 2021-2022 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#ifdef __linux__
+# include <sys/stat.h>
+# include <sys/utsname.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#if defined(HAVE_SHL_LOAD)
+# include <dl.h>
+#elif defined(HAVE_DLOPEN)
+# include <dlfcn.h>
+#endif
+#include <errno.h>
+
+#include <sudo_compat.h>
+#include <sudo_dso.h>
+#include <sudo_util.h>
+
+/*
+ * Pointer for statically compiled symbols.
+ */
+static struct sudo_preload_table *preload_table;
+
+void
+sudo_dso_preload_table_v1(struct sudo_preload_table *table)
+{
+ preload_table = table;
+}
+
+#if defined(HAVE_SHL_LOAD)
+
+# ifndef DYNAMIC_PATH
+# define DYNAMIC_PATH 0
+# endif
+
+void *
+sudo_dso_load_v1(const char *path, int mode)
+{
+ struct sudo_preload_table *pt;
+ int flags = DYNAMIC_PATH | BIND_VERBOSE;
+
+ if (mode == 0)
+ mode = SUDO_DSO_LAZY; /* default behavior */
+
+ /* Check prelinked symbols first. */
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->path != NULL && strcmp(path, pt->path) == 0)
+ return pt->handle;
+ }
+ }
+
+ /* We don't support SUDO_DSO_GLOBAL or SUDO_DSO_LOCAL yet. */
+ if (ISSET(mode, SUDO_DSO_LAZY))
+ flags |= BIND_DEFERRED;
+ if (ISSET(mode, SUDO_DSO_NOW))
+ flags |= BIND_IMMEDIATE;
+
+ return (void *)shl_load(path, flags, 0L);
+}
+
+int
+sudo_dso_unload_v1(void *handle)
+{
+ struct sudo_preload_table *pt;
+
+ /* Check prelinked symbols first. */
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->handle == handle)
+ return 0;
+ }
+ }
+
+ return shl_unload((shl_t)handle);
+}
+
+void *
+sudo_dso_findsym_v1(void *vhandle, const char *symbol)
+{
+ struct sudo_preload_table *pt;
+ shl_t handle = vhandle;
+ void *value = NULL;
+
+ /* Check prelinked symbols first. */
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->handle == handle) {
+ struct sudo_preload_symbol *sym;
+ for (sym = pt->symbols; sym->name != NULL; sym++) {
+ if (strcmp(sym->name, symbol) == 0)
+ return sym->addr;
+ }
+ errno = ENOENT;
+ return NULL;
+ }
+ }
+ }
+
+ /*
+ * Note that the behavior of of SUDO_DSO_NEXT and SUDO_DSO_SELF
+ * differs from most implementations when called from
+ * a shared library.
+ */
+ if (vhandle == SUDO_DSO_NEXT) {
+ /* Iterate over all shared libs looking for symbol. */
+ shl_t myhandle = PROG_HANDLE;
+ struct shl_descriptor *desc;
+ int idx = 0;
+
+ /* Find program's real handle. */
+ if (shl_gethandle(PROG_HANDLE, &desc) == 0)
+ myhandle = desc->handle;
+ while (shl_get(idx++, &desc) == 0) {
+ if (desc->handle == myhandle)
+ continue;
+ if (shl_findsym(&desc->handle, symbol, TYPE_UNDEFINED, &value) == 0)
+ break;
+ }
+ } else {
+ if (vhandle == SUDO_DSO_DEFAULT)
+ handle = NULL;
+ else if (vhandle == SUDO_DSO_SELF)
+ handle = PROG_HANDLE;
+ (void)shl_findsym(&handle, symbol, TYPE_UNDEFINED, &value);
+ }
+
+ return value;
+}
+
+char *
+sudo_dso_strerror_v1(void)
+{
+ return strerror(errno);
+}
+
+#elif defined(HAVE_DLOPEN)
+
+# ifndef RTLD_GLOBAL
+# define RTLD_GLOBAL 0
+# endif
+
+/* Default member names for AIX when dlopen()ing an ar (.a) file. */
+# ifdef RTLD_MEMBER
+# ifdef __LP64__
+# define SUDO_DSO_MEMBER "shr_64.o"
+# else
+# define SUDO_DSO_MEMBER "shr.o"
+# endif
+# endif
+
+# if defined(__linux__)
+/*
+ * On Linux systems that use multi-arch, the actual DSO may be
+ * in a machine-specific subdirectory. If the specified path
+ * contains /lib/ or /libexec/, insert a multi-arch directory
+ * after it.
+ */
+static void *
+dlopen_multi_arch(const char *path, int flags)
+{
+ void *ret = NULL;
+ struct stat sb;
+ char *newpath;
+
+ /* Only try multi-arch if the original path does not exist. */
+ if (stat(path, &sb) == -1 && errno == ENOENT) {
+ newpath = sudo_stat_multiarch(path, &sb);
+ if (newpath != NULL) {
+ ret = dlopen(newpath, flags);
+ free(newpath);
+ }
+ }
+ return ret;
+}
+# else
+static void *
+dlopen_multi_arch(const char *path, int flags)
+{
+ return NULL;
+}
+# endif /* __linux__ */
+
+void *
+sudo_dso_load_v1(const char *path, int mode)
+{
+ struct sudo_preload_table *pt;
+ int flags = 0;
+ void *ret;
+# ifdef RTLD_MEMBER
+ char *cp;
+ size_t pathlen;
+# endif
+
+ /* Check prelinked symbols first. */
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->path != NULL && strcmp(path, pt->path) == 0)
+ return pt->handle;
+ }
+ }
+
+ /* Map SUDO_DSO_* -> RTLD_* */
+ if (ISSET(mode, SUDO_DSO_LAZY))
+ SET(flags, RTLD_LAZY);
+ if (ISSET(mode, SUDO_DSO_NOW))
+ SET(flags, RTLD_NOW);
+ if (ISSET(mode, SUDO_DSO_GLOBAL))
+ SET(flags, RTLD_GLOBAL);
+ if (ISSET(mode, SUDO_DSO_LOCAL))
+ SET(flags, RTLD_LOCAL);
+
+# ifdef RTLD_MEMBER
+ /* Check for AIX shlib.a(member) syntax and dlopen() with RTLD_MEMBER. */
+ pathlen = strlen(path);
+ if (pathlen > 2 && path[pathlen - 1] == ')') {
+ cp = strrchr(path, '(');
+ if (cp != NULL && cp > path + 2 && cp[-2] == '.' && cp[-1] == 'a') {
+ /* Only for archive files (e.g. sudoers.a). */
+ SET(flags, RTLD_MEMBER);
+ }
+ }
+# endif /* RTLD_MEMBER */
+ ret = dlopen(path, flags);
+# if defined(RTLD_MEMBER)
+ /* Special fallback handling for AIX shared objects. */
+ if (ret == NULL && !ISSET(flags, RTLD_MEMBER)) {
+ switch (errno) {
+ case ENOEXEC:
+ /*
+ * If we try to dlopen() an AIX .a file without an explicit member
+ * it will fail with ENOEXEC. Try again using the default member.
+ */
+ if (pathlen > 2 && strcmp(&path[pathlen - 2], ".a") == 0) {
+ int len = asprintf(&cp, "%s(%s)", path, SUDO_DSO_MEMBER);
+ if (len != -1) {
+ ret = dlopen(cp, flags|RTLD_MEMBER);
+ free(cp);
+ }
+ if (ret == NULL) {
+ /* Retry with the original path to get the correct error. */
+ ret = dlopen(path, flags);
+ }
+ }
+ break;
+ case ENOENT:
+ /*
+ * If the .so file is missing but the .a file exists, try to
+ * dlopen() the AIX .a file using the .so name as the member.
+ * This is for compatibility with versions of sudo that use
+ * SVR4-style shared libs, not AIX-style shared libs.
+ */
+ if (pathlen > 3 && strcmp(&path[pathlen - 3], ".so") == 0) {
+ int len = asprintf(&cp, "%.*s.a(%s)", (int)(pathlen - 3),
+ path, sudo_basename(path));
+ if (len != -1) {
+ ret = dlopen(cp, flags|RTLD_MEMBER);
+ free(cp);
+ }
+ if (ret == NULL) {
+ /* Retry with the original path to get the correct error. */
+ ret = dlopen(path, flags);
+ }
+ }
+ break;
+ }
+ }
+# endif /* RTLD_MEMBER */
+ /* On failure, try again with a multi-arch path where possible. */
+ if (ret == NULL)
+ ret = dlopen_multi_arch(path, flags); // -V1048
+
+ return ret;
+}
+
+int
+sudo_dso_unload_v1(void *handle)
+{
+ struct sudo_preload_table *pt;
+
+ /* Check prelinked symbols first. */
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->handle == handle)
+ return 0;
+ }
+ }
+
+ return dlclose(handle);
+}
+
+void *
+sudo_dso_findsym_v1(void *handle, const char *symbol)
+{
+ struct sudo_preload_table *pt;
+
+ /* Check prelinked symbols first. */
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->handle == handle) {
+ struct sudo_preload_symbol *sym;
+ for (sym = pt->symbols; sym->name != NULL; sym++) {
+ if (strcmp(sym->name, symbol) == 0)
+ return sym->addr;
+ }
+ errno = ENOENT;
+ return NULL;
+ }
+ }
+ }
+
+ /*
+ * Not all implementations support the special handles.
+ */
+ if (handle == SUDO_DSO_NEXT) {
+# ifdef RTLD_NEXT
+ handle = RTLD_NEXT;
+# else
+ errno = ENOENT;
+ return NULL;
+# endif
+ } else if (handle == SUDO_DSO_DEFAULT) {
+# ifdef RTLD_DEFAULT
+ handle = RTLD_DEFAULT;
+# else
+ errno = ENOENT;
+ return NULL;
+# endif
+ } else if (handle == SUDO_DSO_SELF) {
+# ifdef RTLD_SELF
+ handle = RTLD_SELF;
+# else
+ errno = ENOENT;
+ return NULL;
+# endif
+ }
+
+ return dlsym(handle, symbol);
+}
+
+char *
+sudo_dso_strerror_v1(void)
+{
+ return dlerror();
+}
+
+#else /* !HAVE_SHL_LOAD && !HAVE_DLOPEN */
+
+/*
+ * Emulate dlopen() using a static list of symbols compiled into sudo.
+ */
+void *
+sudo_dso_load_v1(const char *path, int mode)
+{
+ struct sudo_preload_table *pt;
+
+ /* Check prelinked symbols first. */
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->path != NULL && strcmp(path, pt->path) == 0)
+ return pt->handle;
+ }
+ }
+ return NULL;
+}
+
+int
+sudo_dso_unload_v1(void *handle)
+{
+ struct sudo_preload_table *pt;
+
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->handle == handle)
+ return 0;
+ }
+ }
+ return -1;
+}
+
+void *
+sudo_dso_findsym_v1(void *handle, const char *symbol)
+{
+ struct sudo_preload_table *pt;
+
+ if (preload_table != NULL) {
+ for (pt = preload_table; pt->handle != NULL; pt++) {
+ if (pt->handle == handle) {
+ struct sudo_preload_symbol *sym;
+ for (sym = pt->symbols; sym->name != NULL; sym++) {
+ if (strcmp(sym->name, symbol) == 0)
+ return sym->addr;
+ }
+ }
+ }
+ }
+ errno = ENOENT;
+ return NULL;
+}
+
+char *
+sudo_dso_strerror_v1(void)
+{
+ return strerror(errno);
+}
+#endif /* !HAVE_SHL_LOAD && !HAVE_DLOPEN */