/* * SPDX-License-Identifier: ISC * * Copyright (c) 2010, 2012-2014, 2021-2022 Todd C. Miller * * 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 #ifdef __linux__ # include # include #endif #include #include #include #if defined(HAVE_SHL_LOAD) # include #elif defined(HAVE_DLOPEN) # include #endif #include #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); 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 */