For preload DSO make copies of cmnd, argv, envp and map them read-only.

This commit is contained in:
Todd C. Miller
2022-07-25 19:56:54 -06:00
parent 226a6cd754
commit 5516cdcd5b
4 changed files with 114 additions and 19 deletions

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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);