sudo/lib/util/sudo_dso.c

433 lines
10 KiB
C

/*
* 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 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 */