#include <config.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sudo.h>
#include <sudo_exec.h>
#include <sudo_util.h>
#ifdef RTLD_PRELOAD_VAR
typedef void * (*sudo_alloc_fn_t)(size_t, size_t);
typedef void (*sudo_free_fn_t)(void *);
static void *
sudo_allocarray(size_t nmemb, size_t size)
{
return reallocarray(NULL, nmemb, size);
}
static char *
fmtstr(sudo_alloc_fn_t alloc_fn, sudo_free_fn_t free_fn, const char * restrict ofmt, ...)
{
char *cp, *cur, *newstr = NULL;
size_t len, size = 1;
const char *fmt;
va_list ap;
debug_decl(fmtstr, SUDO_DEBUG_UTIL);
va_start(ap, ofmt);
for (fmt = ofmt; *fmt != '\0'; ) {
if (fmt[0] == '%') {
switch (fmt[1]) {
case 'c':
(void)va_arg(ap, int);
FALLTHROUGH;
case '%':
size++;
fmt += 2;
continue;
case 's':
cp = va_arg(ap, char *);
size += strlen(cp ? cp : "(NULL)");
fmt += 2;
continue;
case 'd': {
char numbuf[STRLEN_MAX_SIGNED(int) + 1];
len = (size_t)snprintf(numbuf, sizeof(numbuf), "%d",
va_arg(ap, int));
if (len >= sizeof(numbuf)) {
goto oflow;
}
size += len;
fmt += 2;
continue;
}
default:
break;
}
}
size++;
fmt++;
}
va_end(ap);
newstr = alloc_fn(1, size);
if (newstr == NULL)
debug_return_str(NULL);
cur = newstr;
va_start(ap, ofmt);
for (fmt = ofmt; *fmt != '\0'; ) {
if (fmt[0] == '%') {
switch (fmt[1]) {
case '%':
if (size < 2) {
goto oflow;
}
*cur++ = '%';
size--;
fmt += 2;
continue;
case 'c':
if (size < 2) {
goto oflow;
}
*cur++ = (char )va_arg(ap, int);
size--;
fmt += 2;
continue;
case 's':
cp = va_arg(ap, char *);
len = strlcpy(cur, cp ? cp : "(NULL)", size);
if (len >= size) {
goto oflow;
}
cur += len;
size -= len;
fmt += 2;
continue;
case 'd':
len = (size_t)snprintf(cur, size, "%d", va_arg(ap, int));
if (len >= size) {
goto oflow;
}
cur += len;
size -= len;
fmt += 2;
continue;
default:
break;
}
}
if (size < 2) {
goto oflow;
}
*cur++ = *fmt++;
size++;
}
if (size < 1) {
goto oflow;
}
*cur = '\0';
va_end(ap);
debug_return_str(newstr);
oflow:
va_end(ap);
free_fn(newstr);
sudo_warnx(U_("internal error, %s overflow"), __func__);
debug_return_str(NULL);
}
static char **
sudo_preload_dso_alloc(char *const envp[], const char *preload_var,
const char *dso_file, int intercept_fd,
sudo_alloc_fn_t alloc_fn, sudo_free_fn_t free_fn)
{
const size_t preload_var_len = strlen(preload_var);
char *preload = NULL;
char **nep, **nenvp = NULL;
char *const *ep;
char **preload_ptr = NULL;
char **intercept_ptr = NULL;
char *const empty[1] = { NULL };
bool fd_present = false;
bool dso_present = false;
# ifdef RTLD_PRELOAD_ENABLE_VAR
bool dso_enabled = false;
# else
const bool dso_enabled = true;
# endif
# ifdef _PATH_ASAN_LIB
char *dso_buf = NULL;
# endif
size_t env_size;
debug_decl(sudo_preload_dso_alloc, SUDO_DEBUG_UTIL);
# ifdef _PATH_ASAN_LIB
dso_buf = fmtstr(alloc_fn, free_fn, "%s%c%s", _PATH_ASAN_LIB,
RTLD_PRELOAD_DELIM, dso_file);
if (dso_buf == NULL) {
goto oom;
}
dso_file = dso_buf;
# endif
if (envp == NULL)
envp = empty;
for (env_size = 0; envp[env_size] != NULL; env_size++)
continue;
if (!dso_enabled)
env_size++;
if (intercept_fd != -1)
env_size++;
env_size += 2;
nenvp = alloc_fn(env_size, sizeof(*nenvp));
if (nenvp == NULL)
goto oom;
for (ep = envp, nep = nenvp; *ep != NULL; ep++) {
if (strncmp(*ep, preload_var, preload_var_len) == 0 &&
(*ep)[preload_var_len] == '=') {
const char *cp = *ep + preload_var_len + 1;
const size_t dso_len = strlen(dso_file);
if (preload_ptr != NULL)
continue;
if (strncmp(cp, dso_file, dso_len) == 0) {
if (cp[dso_len] == '\0' || cp[dso_len] == RTLD_PRELOAD_DELIM)
dso_present = true;
}
preload_ptr = nep;
goto copy;
}
if (intercept_fd != -1 && strncmp(*ep, "SUDO_INTERCEPT_FD=",
sizeof("SUDO_INTERCEPT_FD=") - 1) == 0) {
const char *cp = *ep + sizeof("SUDO_INTERCEPT_FD=") - 1;
const char *errstr;
int fd;
if (intercept_ptr != NULL)
continue;
fd = (int)sudo_strtonum(cp, 0, INT_MAX, &errstr);
if (fd == intercept_fd && errstr == NULL)
fd_present = true;
intercept_ptr = nep;
goto copy;
}
# ifdef RTLD_PRELOAD_ENABLE_VAR
if (strncmp(*ep, RTLD_PRELOAD_ENABLE_VAR "=",
sizeof(RTLD_PRELOAD_ENABLE_VAR)) == 0) {
dso_enabled = true;
}
# endif
copy:
*nep++ = *ep;
}
if (!dso_present) {
if (preload_ptr == NULL) {
# ifdef RTLD_PRELOAD_DEFAULT
preload = fmtstr(alloc_fn, free_fn, "%s=%s%c%s", preload_var,
dso_file, RTLD_PRELOAD_DELIM, RTLD_PRELOAD_DEFAULT);
if (preload == NULL) {
goto oom;
}
# else
preload = fmtstr(alloc_fn, free_fn, "%s=%s", preload_var,
dso_file);
if (preload == NULL) {
goto oom;
}
# endif
*nep++ = preload;
} else {
const char *old_val = *preload_ptr + preload_var_len + 1;
preload = fmtstr(alloc_fn, free_fn, "%s=%s%c%s", preload_var,
dso_file, RTLD_PRELOAD_DELIM, old_val);
if (preload == NULL) {
goto oom;
}
*preload_ptr = preload;
}
}
# ifdef RTLD_PRELOAD_ENABLE_VAR
if (!dso_enabled) {
*nenvp++ = RTLD_PRELOAD_ENABLE_VAR "=";
}
# endif
if (!fd_present && intercept_fd != -1) {
char *fdstr = fmtstr(alloc_fn, free_fn, "SUDO_INTERCEPT_FD=%d",
intercept_fd);
if (fdstr == NULL) {
goto oom;
}
if (intercept_ptr != NULL) {
*intercept_ptr = fdstr;
} else {
*nep++ = fdstr;
}
}
*nep = NULL;
# ifdef _PATH_ASAN_LIB
free_fn(dso_buf);
# endif
debug_return_ptr(nenvp);
oom:
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
# ifdef _PATH_ASAN_LIB
free_fn(dso_buf);
# endif
free_fn(preload);
free_fn(nenvp);
debug_return_ptr(NULL);
}
static char **
sudo_preload_dso_path(char *const envp[], const char *dso_file,
int intercept_fd, sudo_alloc_fn_t alloc_fn, sudo_free_fn_t free_fn)
{
char **ret = NULL;
const char *ep;
debug_decl(sudo_preload_dso_path, SUDO_DEBUG_UTIL);
ep = strchr(dso_file, ':');
if (ep == NULL) {
return sudo_preload_dso_alloc(envp, RTLD_PRELOAD_VAR, dso_file,
intercept_fd, alloc_fn, free_fn);
}
if (ep != dso_file) {
#ifdef RTLD_PRELOAD_VAR_32
const size_t len = (size_t)(ep - dso_file);
char name[PATH_MAX];
if (len >= sizeof(name)) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"%.*s: path too long", (int)len, dso_file);
} else {
memcpy(name, dso_file, len);
name[len] = '\0';
ret = sudo_preload_dso_alloc(envp, RTLD_PRELOAD_VAR_32, name,
intercept_fd, alloc_fn, free_fn);
envp = ret;
}
#endif
dso_file = ep + 1;
}
#ifdef RTLD_PRELOAD_VAR_64
if (*dso_file != '\0') {
char **new_envp = sudo_preload_dso_alloc(envp, RTLD_PRELOAD_VAR_64,
dso_file, intercept_fd, alloc_fn, free_fn);
free_fn(ret);
ret = new_envp;
}
#endif
debug_return_ptr(ret);
}
char **
sudo_preload_dso_mmap(char *const envp[], const char *dso_file,
int intercept_fd)
{
return sudo_preload_dso_path(envp, dso_file, intercept_fd,
sudo_mmap_allocarray_v1, sudo_mmap_free_v1);
}
char **
sudo_preload_dso(char *const envp[], const char *dso_file,
int intercept_fd)
{
return sudo_preload_dso_path(envp, dso_file, intercept_fd,
sudo_allocarray, free);
}
#endif