summaryrefslogtreecommitdiff
path: root/src/exec_preload.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/exec_preload.c')
-rw-r--r--src/exec_preload.c357
1 files changed, 255 insertions, 102 deletions
diff --git a/src/exec_preload.c b/src/exec_preload.c
index 3bc7a15d3..d90fc6322 100644
--- a/src/exec_preload.c
+++ b/src/exec_preload.c
@@ -1,7 +1,7 @@
/*
* SPDX-License-Identifier: ISC
*
- * Copyright (c) 2009-2021 Todd C. Miller <Todd.Miller@sudo.ws>
+ * Copyright (c) 2009-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
@@ -24,6 +24,7 @@
#include <config.h>
#include <fcntl.h>
+#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -31,19 +32,153 @@
#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);
+}
+
+/*
+ * Allocate space for the string described by fmt and return it,
+ * or NULL on error.
+ * Currently only supports %%, %c, %d, and %s escapes.
+ */
+static char *
+fmtstr(sudo_alloc_fn_t alloc_fn, sudo_free_fn_t free_fn, const char *ofmt, ...)
+{
+ char *cp, *cur, *newstr = NULL;
+ size_t len, size = 1;
+ const char *fmt;
+ va_list ap;
+ debug_decl(fmtstr, SUDO_DEBUG_UTIL);
+
+ /* Determine size. */
+ va_start(ap, ofmt);
+ for (fmt = ofmt; *fmt != '\0'; ) {
+ if (fmt[0] == '%') {
+ switch (fmt[1]) {
+ case '%':
+ case 'c':
+ size++;
+ fmt += 2;
+ continue;
+ case 's':
+ cp = va_arg(ap, char *);
+ size += strlen(cp ? cp : "(NULL)");
+ fmt += 2;
+ continue;
+ case 'd': {
+ char numbuf[(((sizeof(int) * 8) + 2) / 3) + 2];
+ len = snprintf(numbuf, sizeof(numbuf), "%d", va_arg(ap, int));
+ if (len >= sizeof(numbuf)) {
+ goto oflow;
+ }
+ size += len;
+ fmt += 2;
+ continue;
+ }
+ default:
+ /* Treat as literal. */
+ break;
+ }
+ }
+ size++;
+ fmt++;
+ }
+ va_end(ap);
+
+ newstr = alloc_fn(1, size);
+ if (newstr == NULL)
+ debug_return_str(NULL);
+
+ /* Format/copy data. */
+ 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++ = 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 = snprintf(cur, size, "%d", va_arg(ap, int));
+ if (len >= size) {
+ goto oflow;
+ }
+ cur += len;
+ size -= len;
+ fmt += 2;
+ continue;
+ default:
+ /* Treat as literal. */
+ break;
+ }
+ }
+ if (size < 2) {
+ goto oflow;
+ }
+ *cur++ = *fmt++;
+ size++;
+ }
+
+ if (size < 1) {
+ goto oflow;
+ }
+ *cur = '\0';
+ va_end(ap);
+
+ debug_return_str(newstr);
+
+oflow:
+ /* We pre-allocate enough space, so this should never happen. */
+ va_end(ap);
+ free_fn(newstr);
+ sudo_warnx(U_("internal error, %s overflow"), __func__);
+ debug_return_str(NULL);
+}
+
/*
* Add a DSO file to LD_PRELOAD or the system equivalent.
*/
-char **
-sudo_preload_dso(char *envp[], const char *dso_file, int intercept_fd)
+static char **
+sudo_preload_dso_alloc(char *const envp[], const char *dso_file,
+ int intercept_fd, sudo_alloc_fn_t alloc_fn, sudo_free_fn_t free_fn)
{
char *preload = NULL;
- char **nenvp = NULL;
- int env_len, len;
- int preload_idx = -1;
- int intercept_idx = -1;
+ 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
@@ -54,16 +189,18 @@ sudo_preload_dso(char *envp[], const char *dso_file, int intercept_fd)
# ifdef _PATH_ASAN_LIB
char *dso_buf = NULL;
# endif
- debug_decl(sudo_preload_dso, SUDO_DEBUG_UTIL);
+ size_t env_size;
+ debug_decl(sudo_preload_dso_alloc, SUDO_DEBUG_UTIL);
# ifdef _PATH_ASAN_LIB
/*
* The address sanitizer DSO needs to be first in the list.
*/
- len = asprintf(&dso_buf, "%s%c%s", _PATH_ASAN_LIB, RTLD_PRELOAD_DELIM,
- dso_file);
- if (len == -1)
- goto oom;
+ 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
@@ -73,140 +210,156 @@ sudo_preload_dso(char *envp[], const char *dso_file, int intercept_fd)
* XXX - need to support 32-bit and 64-bit variants
*/
- /* Count entries in envp, looking for LD_PRELOAD as we go. */
- for (env_len = 0; envp[env_len] != NULL; env_len++) {
- if (strncmp(envp[env_len], RTLD_PRELOAD_VAR "=", sizeof(RTLD_PRELOAD_VAR)) == 0) {
- if (preload_idx == -1) {
- const char *cp = envp[env_len] + sizeof(RTLD_PRELOAD_VAR);
- const size_t dso_len = strlen(dso_file);
-
- /*
- * Check to see if dso_file is already first in the list.
- * We don't bother checking for it later in the list.
- */
- if (strncmp(cp, dso_file, dso_len) == 0) {
- if (cp[dso_len] == '\0' || cp[dso_len] == RTLD_PRELOAD_DELIM)
- dso_present = true;
- }
+ /* Treat a NULL envp as empty, thanks Linux. */
+ if (envp == NULL)
+ envp = empty;
- /* Save index of existing LD_PRELOAD variable. */
- preload_idx = env_len;
- } else {
- /* Remove duplicate LD_PRELOAD. */
- int i;
- for (i = env_len; envp[i] != NULL; i++) {
- envp[i] = envp[i + 1];
- }
+ /* Determine max size for new envp. */
+ 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; /* dso_file + terminating NULL */
+
+ /* Allocate new envp. */
+ nenvp = alloc_fn(env_size, sizeof(*nenvp));
+ if (nenvp == NULL)
+ goto oom;
+
+ /*
+ * Shallow copy envp, with special handling for RTLD_PRELOAD_VAR,
+ * RTLD_PRELOAD_ENABLE_VAR and SUDO_INTERCEPT_FD.
+ */
+ for (ep = envp, nep = nenvp; *ep != NULL; ep++) {
+ if (strncmp(*ep, RTLD_PRELOAD_VAR "=", sizeof(RTLD_PRELOAD_VAR)) == 0) {
+ const char *cp = *ep + sizeof(RTLD_PRELOAD_VAR);
+ const size_t dso_len = strlen(dso_file);
+
+ /* Skip duplicates. */
+ if (preload_ptr != NULL)
+ continue;
+
+ /*
+ * Check to see if dso_file is already first in the list.
+ * We don't bother checking for it later in the list.
+ */
+ if (strncmp(cp, dso_file, dso_len) == 0) {
+ if (cp[dso_len] == '\0' || cp[dso_len] == RTLD_PRELOAD_DELIM)
+ dso_present = true;
}
- continue;
+
+ /* Save pointer to LD_PRELOAD variable. */
+ preload_ptr = nep;
+
+ goto copy;
}
- if (intercept_fd != -1 && strncmp(envp[env_len], "SUDO_INTERCEPT_FD=",
+ if (intercept_fd != -1 && strncmp(*ep, "SUDO_INTERCEPT_FD=",
sizeof("SUDO_INTERCEPT_FD=") - 1) == 0) {
- if (intercept_idx == -1) {
- const char *cp = envp[env_len] + sizeof("SUDO_INTERCEPT_FD=") - 1;
- const char *errstr;
- int fd;
-
- fd = sudo_strtonum(cp, 0, INT_MAX, &errstr);
- if (fd == intercept_fd && errstr == NULL)
- fd_present = true;
-
- /* Save index of existing SUDO_INTERCEPT_FD variable. */
- intercept_idx = env_len;
- } else {
- /* Remove duplicate SUDO_INTERCEPT_FD. */
- int i;
- for (i = env_len; envp[i] != NULL; i++) {
- envp[i] = envp[i + 1];
- }
- }
- continue;
+ const char *cp = *ep + sizeof("SUDO_INTERCEPT_FD=") - 1;
+ const char *errstr;
+ int fd;
+
+ /* Skip duplicates. */
+ if (intercept_ptr != NULL)
+ continue;
+
+ fd = sudo_strtonum(cp, 0, INT_MAX, &errstr);
+ if (fd == intercept_fd && errstr == NULL)
+ fd_present = true;
+
+ /* Save pointer to SUDO_INTERCEPT_FD variable. */
+ intercept_ptr = nep;
+
+ goto copy;
}
# ifdef RTLD_PRELOAD_ENABLE_VAR
- if (strncmp(envp[env_len], RTLD_PRELOAD_ENABLE_VAR "=", sizeof(RTLD_PRELOAD_ENABLE_VAR)) == 0) {
+ if (strncmp(*ep, RTLD_PRELOAD_ENABLE_VAR "=",
+ sizeof(RTLD_PRELOAD_ENABLE_VAR)) == 0) {
dso_enabled = true;
- continue;
}
# endif
- }
-
- /*
- * Make a new copy of envp as needed.
- * It would be nice to realloc the old envp[] but we don't know
- * whether it was dynamically allocated. [TODO: plugin API]
- */
- if (preload_idx == -1 || !dso_enabled || intercept_idx == -1) {
- const int env_size = env_len + 1 + (preload_idx == -1) + dso_enabled + (intercept_idx == -1); // -V547
-
- nenvp = reallocarray(NULL, env_size, sizeof(*nenvp));
- if (nenvp == NULL) {
- sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
- debug_return_ptr(NULL);
- }
- memcpy(nenvp, envp, env_len * sizeof(*envp));
- nenvp[env_len] = NULL;
- envp = nenvp;
+copy:
+ *nep++ = *ep; /* shallow copy */
}
/* Prepend our LD_PRELOAD to existing value or add new entry at the end. */
if (!dso_present) {
- if (preload_idx == -1) {
+ if (preload_ptr == NULL) {
# ifdef RTLD_PRELOAD_DEFAULT
- len = asprintf(&preload, "%s=%s%c%s", RTLD_PRELOAD_VAR, dso_file,
- RTLD_PRELOAD_DELIM, RTLD_PRELOAD_DEFAULT);
- if (len == -1) {
+ preload = fmtstr(alloc_fn, free_fn, "%s=%s%c%s", RTLD_PRELOAD_VAR,
+ dso_file, RTLD_PRELOAD_DELIM, RTLD_PRELOAD_DEFAULT);
+ if (preload == NULL) {
goto oom;
}
# else
- preload = sudo_new_key_val(RTLD_PRELOAD_VAR, dso_file);
+ preload = fmtstr(alloc_fn, free_fn, "%s=%s", RTLD_PRELOAD_VAR,
+ dso_file);
if (preload == NULL) {
goto oom;
}
# endif
- envp[env_len++] = preload;
- envp[env_len] = NULL;
+ *nep++ = preload;
} else {
- const char *old_val = envp[preload_idx] + sizeof(RTLD_PRELOAD_VAR);
- len = asprintf(&preload, "%s=%s%c%s", RTLD_PRELOAD_VAR,
+ const char *old_val = *preload_ptr + sizeof(RTLD_PRELOAD_VAR);
+ preload = fmtstr(alloc_fn, free_fn, "%s=%s%c%s", RTLD_PRELOAD_VAR,
dso_file, RTLD_PRELOAD_DELIM, old_val);
- if (len == -1) {
+ if (preload == NULL) {
goto oom;
}
- envp[preload_idx] = preload;
+ *preload_ptr = preload;
}
}
# ifdef RTLD_PRELOAD_ENABLE_VAR
if (!dso_enabled) {
- envp[env_len++] = RTLD_PRELOAD_ENABLE_VAR "=";
- envp[env_len] = NULL;
+ *nenvp++ = RTLD_PRELOAD_ENABLE_VAR "=";
}
# endif
if (!fd_present && intercept_fd != -1) {
- char *fdstr;
-
- len = asprintf(&fdstr, "SUDO_INTERCEPT_FD=%d", intercept_fd);
- if (len == -1) {
+ char *fdstr = fmtstr(alloc_fn, free_fn, "SUDO_INTERCEPT_FD=%d",
+ intercept_fd);
+ if (fdstr == NULL) {
goto oom;
}
- if (intercept_idx != -1) {
- envp[intercept_idx] = fdstr;
+ if (intercept_ptr != NULL) {
+ *intercept_ptr = fdstr;
} else {
- envp[env_len++] = fdstr;
- envp[env_len] = NULL;
+ *nep++ = fdstr;
}
}
+
+ /* NULL terminate nenvp at last. */
+ *nep = NULL;
+
# ifdef _PATH_ASAN_LIB
- free(dso_buf);
+ free_fn(dso_buf);
# endif
- debug_return_ptr(envp);
+ debug_return_ptr(nenvp);
oom:
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
# ifdef _PATH_ASAN_LIB
- free(dso_buf);
+ free_fn(dso_buf);
# endif
- free(preload);
- free(nenvp);
+ free_fn(preload);
+ free_fn(nenvp);
debug_return_ptr(NULL);
}
+
+char **
+sudo_preload_dso_mmap(char *const envp[], const char *dso_file,
+ int intercept_fd)
+{
+ return sudo_preload_dso_alloc(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_alloc(envp, dso_file, intercept_fd,
+ sudo_allocarray, free);
+}
#endif /* RTLD_PRELOAD_VAR */