From 5516cdcd5bf51322e5c022acf99f52cc09cd49dc Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Mon, 25 Jul 2022 19:56:54 -0600 Subject: [PATCH] For preload DSO make copies of cmnd, argv, envp and map them read-only. --- include/sudo_util.h | 2 + lib/util/mmap_alloc.c | 22 ++++++++- lib/util/util.exp.in | 1 + src/sudo_intercept.c | 108 +++++++++++++++++++++++++++++++++++------- 4 files changed, 114 insertions(+), 19 deletions(-) diff --git a/include/sudo_util.h b/include/sudo_util.h index 65996438b..1dcf7fa84 100644 --- a/include/sudo_util.h +++ b/include/sudo_util.h @@ -257,6 +257,8 @@ sudo_dso_public void sudo_mmap_free_v1(void *ptr); #define sudo_mmap_free(_a) sudo_mmap_free_v1(_a) sudo_dso_public char *sudo_mmap_strdup_v1(const char *str); #define sudo_mmap_strdup(_a) sudo_mmap_strdup_v1(_a) +sudo_dso_public int sudo_mmap_protect_v1(void *ptr); +#define sudo_mmap_protect(_a) sudo_mmap_protect_v1(_a) /* parseln.c */ sudo_dso_public ssize_t sudo_parseln_v1(char **buf, size_t *bufsize, unsigned int *lineno, FILE *fp); diff --git a/lib/util/mmap_alloc.c b/lib/util/mmap_alloc.c index 9e7bf84d6..ce6237052 100644 --- a/lib/util/mmap_alloc.c +++ b/lib/util/mmap_alloc.c @@ -121,6 +121,24 @@ sudo_mmap_strdup_v1(const char *str) return newstr; } +/* + * Set the page permissions for the allocation represented by "ptr" to + * read-only. Returns 0 on success, -1 on failure. + */ +int +sudo_mmap_protect_v1(void *ptr) +{ + if (ptr != NULL) { + unsigned long *ulp = ptr; + const unsigned long size = ulp[-1]; + return mprotect((void *)&ulp[-1], size, PROT_READ); + } + + /* Can't protect NULL. */ + errno = EINVAL; + return -1; +} + /* * Free "ptr" allocated by sudo_mmap_alloc(). * The allocated size is stored (as unsigned long) in ptr[-1]. @@ -131,7 +149,9 @@ sudo_mmap_free_v1(void *ptr) if (ptr != NULL) { unsigned long *ulp = ptr; const unsigned long size = ulp[-1]; + int saved_errno = errno; - munmap((void *)&ulp[-1], size); + (void)munmap((void *)&ulp[-1], size); + errno = saved_errno; } } diff --git a/lib/util/util.exp.in b/lib/util/util.exp.in index b289174e2..dd5fbc101 100644 --- a/lib/util/util.exp.in +++ b/lib/util/util.exp.in @@ -113,6 +113,7 @@ sudo_mkdir_parents_v1 sudo_mmap_alloc_v1 sudo_mmap_allocarray_v1 sudo_mmap_free_v1 +sudo_mmap_protect_v1 sudo_mmap_strdup_v1 sudo_new_key_val_v1 sudo_parse_gids_v1 diff --git a/src/sudo_intercept.c b/src/sudo_intercept.c index 71bffd1d8..5b11fbf7c 100644 --- a/src/sudo_intercept.c +++ b/src/sudo_intercept.c @@ -62,6 +62,51 @@ extern bool command_allowed(const char *cmnd, char * const argv[], char * const typedef int (*sudo_fn_execve_t)(const char *, char *const *, char *const *); +static void +free_vector(char **vec) +{ + char **cur; + debug_decl(free_vector, SUDO_DEBUG_EXEC); + + if (vec != NULL) { + for (cur = vec; *cur != NULL; cur++) { + sudo_mmap_free(*cur); + } + sudo_mmap_free(vec); + } + + debug_return; +} + +static char ** +copy_vector(char * const *src) +{ + char **copy; + int i, len; + debug_decl(copy_vector, SUDO_DEBUG_EXEC); + + if (src == NULL) + debug_return_ptr(NULL); + + for (len = 0; src[len] != NULL; len++) { + continue; + } + copy = sudo_mmap_allocarray(len + 1, sizeof(char *)); + if (copy == NULL) { + debug_return_ptr(NULL); + } + for (i = 0; i < len; i++) { + copy[i] = sudo_mmap_strdup(src[i]); + if (copy[i] == NULL) { + sudo_mmap_free(copy); + debug_return_ptr(NULL); + } + } + copy[i] = NULL; + + debug_return_ptr(copy); +} + /* * We do PATH resolution here rather than in the policy because we * want to use the PATH in the current environment. @@ -74,6 +119,7 @@ resolve_path(const char *cmnd, char *out_cmnd, size_t out_size) char path[PATH_MAX]; char **p, *cp, *endp; int dirlen, len; + debug_decl(resolve_path, SUDO_DEBUG_EXEC); for (p = environ; (cp = *p) != NULL; p++) { if (strncmp(cp, "PATH=", sizeof("PATH=") - 1) == 0) { @@ -83,7 +129,7 @@ resolve_path(const char *cmnd, char *out_cmnd, size_t out_size) } if (cp == NULL) { errno = ENOENT; - return false; + debug_return_bool(false); } endp = cp + strlen(cp); @@ -110,7 +156,7 @@ resolve_path(const char *cmnd, char *out_cmnd, size_t out_size) errval = ENAMETOOLONG; break; } - return true; + debug_return_bool(true); } switch (errno) { case EACCES: @@ -121,21 +167,21 @@ resolve_path(const char *cmnd, char *out_cmnd, size_t out_size) case ENOENT: break; default: - return false; + debug_return_bool(false); } } errno = errval; - return false; + debug_return_bool(false); } static int exec_wrapper(const char *cmnd, char * const argv[], char * const envp[], bool is_execvp) { + char *cmnd_copy = NULL, **argv_copy = NULL, **envp_copy = NULL; char *ncmnd = NULL, **nargv = NULL, **nenvp = NULL; char cmnd_buf[PATH_MAX]; void *fn = NULL; - unsigned int i; debug_decl(exec_wrapper, SUDO_DEBUG_EXEC); if (cmnd == NULL) { @@ -147,10 +193,10 @@ exec_wrapper(const char *cmnd, char * const argv[], char * const envp[], if (strchr(cmnd, '/') == NULL) { if (!is_execvp) { errno = ENOENT; - debug_return_int(-1); + goto bad; } if (!resolve_path(cmnd, cmnd_buf, sizeof(cmnd_buf))) { - debug_return_int(-1); + goto bad; } cmnd = cmnd_buf; } else { @@ -159,13 +205,37 @@ exec_wrapper(const char *cmnd, char * const argv[], char * const envp[], /* Absolute or relative path name. */ if (stat(cmnd, &sb) == -1) { /* Leave errno unchanged. */ - debug_return_int(-1); + goto bad; } else if (!S_ISREG(sb.st_mode)) { errno = EACCES; - debug_return_int(-1); + goto bad; } } + /* + * Make copies of cmnd, argv, and envp. + */ + cmnd_copy = sudo_mmap_strdup(cmnd); + if (cmnd_copy == NULL) { + debug_return_int(-1); + } + sudo_mmap_protect(cmnd_copy); + cmnd = cmnd_copy; + + argv_copy = copy_vector(argv); + if (argv_copy == NULL) { + goto bad; + } + sudo_mmap_protect(argv_copy); + argv = argv_copy; + + envp_copy = copy_vector(envp); + if (envp_copy == NULL) { + goto bad; + } + sudo_mmap_protect(envp_copy); + envp = envp_copy; + # if defined(HAVE___INTERPOSE) fn = execve; # elif defined(HAVE_DLOPEN) @@ -175,7 +245,7 @@ exec_wrapper(const char *cmnd, char * const argv[], char * const envp[], # endif if (fn == NULL) { errno = EACCES; - debug_return_int(-1); + goto bad; } if (command_allowed(cmnd, argv, envp, &ncmnd, &nargv, &nenvp)) { @@ -191,7 +261,7 @@ exec_wrapper(const char *cmnd, char * const argv[], char * const envp[], continue; shargv = sudo_mmap_allocarray(argc + 2, sizeof(char *)); if (shargv == NULL) - return -1; + goto bad; shargv[0] = "sh"; shargv[1] = ncmnd; memcpy(shargv + 2, nargv + 1, argc * sizeof(char *)); @@ -201,15 +271,17 @@ exec_wrapper(const char *cmnd, char * const argv[], char * const envp[], } else { errno = EACCES; } - if (ncmnd != cmnd) + +bad: + sudo_mmap_free(cmnd_copy); + if (ncmnd != cmnd_copy) sudo_mmap_free(ncmnd); - if (nargv != argv && nargv != NULL) { - for (i = 0; nargv[i] != NULL; i++) - sudo_mmap_free(nargv[i]); - sudo_mmap_free(nargv); - } + free_vector(argv_copy); + if (nargv != argv_copy) + free_vector(nargv); + free_vector(envp_copy); /* Leaks allocated preload vars. */ - if (nenvp != envp) + if (nenvp != envp_copy) sudo_mmap_free(nenvp); debug_return_int(-1);