Add support for getting the execve(2) arguments via ptrace(2).
This will be used to perform a policy check in intercept mode.
This commit is contained in:
@@ -21,17 +21,23 @@
|
|||||||
|
|
||||||
#include <config.h>
|
#include <config.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
#include <signal.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "sudo.h"
|
#include "sudo.h"
|
||||||
#include "sudo_exec.h"
|
#include "sudo_exec.h"
|
||||||
|
|
||||||
#ifdef HAVE_PTRACE_INTERCEPT
|
#ifdef HAVE_PTRACE_INTERCEPT
|
||||||
|
# include <elf.h>
|
||||||
# include <sys/prctl.h>
|
# include <sys/prctl.h>
|
||||||
# include <sys/ptrace.h>
|
# include <sys/ptrace.h>
|
||||||
# include <sys/user.h>
|
# include <sys/user.h>
|
||||||
@@ -40,6 +46,316 @@
|
|||||||
# include <linux/seccomp.h>
|
# include <linux/seccomp.h>
|
||||||
# include <linux/filter.h>
|
# include <linux/filter.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* See syscall(2) for a list of registers used in system calls.
|
||||||
|
* For example code, see tools/testing/selftests/seccomp/seccomp_bpf.c
|
||||||
|
*
|
||||||
|
* The structs and registers vary among the different platforms.
|
||||||
|
* We define user_regs_struct as the struct to use for the
|
||||||
|
* PTRACE_GETREGSET/PTRACE_SETREGSET command and define accessor
|
||||||
|
* macros to get/set the struct members.
|
||||||
|
*/
|
||||||
|
#if defined(__amd64__)
|
||||||
|
# define user_pt_regs user_regs_struct
|
||||||
|
# define reg_syscall(x) (x).orig_rax
|
||||||
|
# define reg_retval(x) (x).rax
|
||||||
|
# define reg_arg1(x) (x).rdi
|
||||||
|
# define reg_arg2(x) (x).rsi
|
||||||
|
# define reg_arg3(x) (x).rdx
|
||||||
|
# define reg_arg4(x) (x).r10
|
||||||
|
#elif defined(__aarch64__)
|
||||||
|
# define reg_syscall(x) (x).regs[8] /* w8 */
|
||||||
|
# define reg_retval(x) (x).regs[0] /* x0 */
|
||||||
|
# define reg_arg1(x) (x).regs[0] /* x0 */
|
||||||
|
# define reg_arg2(x) (x).regs[1] /* x1 */
|
||||||
|
# define reg_arg3(x) (x).regs[2] /* x2 */
|
||||||
|
# define reg_arg4(x) (x).regs[3] /* x3 */
|
||||||
|
#elif defined(__arm__)
|
||||||
|
/* Note: assumes arm EABI, not OABI */
|
||||||
|
/* Untested */
|
||||||
|
# define user_pt_regs pt_regs
|
||||||
|
# define reg_syscall(x) (x).ARM_r7
|
||||||
|
# define reg_retval(x) (x).ARM_r0
|
||||||
|
# define reg_arg1(x) (x).ARM_r0
|
||||||
|
# define reg_arg2(x) (x).ARM_r1
|
||||||
|
# define reg_arg3(x) (x).ARM_r2
|
||||||
|
# define reg_arg4(x) (x).ARM_r3
|
||||||
|
#elif defined (__hppa__)
|
||||||
|
/* Untested */
|
||||||
|
# define user_pt_regs user_regs_struct
|
||||||
|
# define reg_syscall(x) (x).gr[20] /* r20 */
|
||||||
|
# define reg_retval(x) (x).gr[28] /* r28 */
|
||||||
|
# define reg_arg1(x) (x).gr[26] /* r26 */
|
||||||
|
# define reg_arg2(x) (x).gr[25] /* r25 */
|
||||||
|
# define reg_arg3(x) (x).gr[24] /* r24 */
|
||||||
|
# define reg_arg4(x) (x).gr[23] /* r23 */
|
||||||
|
#elif defined(__i386__)
|
||||||
|
# define user_pt_regs user_regs_struct
|
||||||
|
# define reg_syscall(x) (x).orig_eax
|
||||||
|
# define reg_retval(x) (x).eax
|
||||||
|
# define reg_arg1(x) (x).ebx
|
||||||
|
# define reg_arg2(x) (x).ecx
|
||||||
|
# define reg_arg3(x) (x).edx
|
||||||
|
# define reg_arg4(x) (x).esi
|
||||||
|
#elif defined(__powerpc64__)
|
||||||
|
/* Untested */
|
||||||
|
# define user_pt_regs pt_regs
|
||||||
|
# define reg_syscall(x) (x).gpr[0] /* r0 */
|
||||||
|
# define reg_retval(x) (x).gpr[3] /* r3 */
|
||||||
|
# define reg_arg1(x) (x).gpr[3] /* r3 */
|
||||||
|
# define reg_arg2(x) (x).gpr[4] /* r4 */
|
||||||
|
# define reg_arg3(x) (x).gpr[5] /* r5 */
|
||||||
|
# define reg_arg4(x) (x).gpr[6] /* r6 */
|
||||||
|
#elif defined(__powerpc__)
|
||||||
|
/* Untested */
|
||||||
|
# define user_pt_regs pt_regs
|
||||||
|
# define reg_syscall(x) (x).gpr[0] /* r0 */
|
||||||
|
# define reg_retval(x) (x).gpr[3] /* r3 */
|
||||||
|
# define reg_arg1(x) (x).gpr[3] /* r3 */
|
||||||
|
# define reg_arg2(x) (x).gpr[4] /* r4 */
|
||||||
|
# define reg_arg3(x) (x).gpr[5] /* r5 */
|
||||||
|
# define reg_arg4(x) (x).gpr[6] /* r6 */
|
||||||
|
#elif defined(__riscv) && __riscv_xlen == 64
|
||||||
|
/* Untested */
|
||||||
|
# define user_pt_regs user_regs_struct
|
||||||
|
# define reg_syscall(x) (x).a7
|
||||||
|
# define reg_retval(x) (x).a0
|
||||||
|
# define reg_arg1(x) (x).a0
|
||||||
|
# define reg_arg2(x) (x).a1
|
||||||
|
# define reg_arg3(x) (x).a2
|
||||||
|
# define reg_arg4(x) (x).a3
|
||||||
|
#elif defined(__s390__)
|
||||||
|
/* Untested */
|
||||||
|
# define user_pt_regs s390_regs
|
||||||
|
# define reg_syscall(x) (x).gprs[1] /* r1 */
|
||||||
|
# define reg_retval(x) (x).gprs[2] /* r2 */
|
||||||
|
# define reg_arg1(x) (x).gprs[2] /* r2 */
|
||||||
|
# define reg_arg2(x) (x).gprs[3] /* r3 */
|
||||||
|
# define reg_arg3(x) (x).gprs[4] /* r4 */
|
||||||
|
# define reg_arg4(x) (x).gprs[5] /* r6 */
|
||||||
|
#else
|
||||||
|
# error "Do not know how to find your architecture's registers"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the string at addr and store in buf.
|
||||||
|
* Returns the number of bytes stored, including the NUL.
|
||||||
|
*/
|
||||||
|
static size_t
|
||||||
|
ptrace_read_string(pid_t pid, long addr, char *buf, size_t bufsize)
|
||||||
|
{
|
||||||
|
const char *buf0 = buf;
|
||||||
|
const char *cp;
|
||||||
|
long word;
|
||||||
|
unsigned int i;
|
||||||
|
debug_decl(ptrace_read_string, SUDO_DEBUG_EXEC);
|
||||||
|
|
||||||
|
/* Read the string via ptrace(2) one word at a time. */
|
||||||
|
for (;;) {
|
||||||
|
word = ptrace(PTRACE_PEEKTEXT, pid, addr, NULL);
|
||||||
|
if (word == -1) {
|
||||||
|
sudo_warn("ptrace(PTRACE_PEEKTEXT, %d, %ld, NULL)", pid, addr);
|
||||||
|
debug_return_ssize_t(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* XXX - this could be optimized. */
|
||||||
|
cp = (char *)&word;
|
||||||
|
for (i = 0; i < sizeof(long); i++) {
|
||||||
|
if (bufsize == 0) {
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_ERROR,
|
||||||
|
"%s: %d: out of space reading string", __func__, (int)pid);
|
||||||
|
debug_return_ssize_t(-1);
|
||||||
|
}
|
||||||
|
*buf = cp[i];
|
||||||
|
if (*buf++ == '\0')
|
||||||
|
debug_return_ssize_t(buf - buf0);
|
||||||
|
bufsize--;
|
||||||
|
}
|
||||||
|
addr += sizeof(long);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the string vector at addr and store in vec, which must have
|
||||||
|
* sufficient space. Strings are stored in buf.
|
||||||
|
* Returns the number of bytes in buf consumed (including NULs).
|
||||||
|
*/
|
||||||
|
static size_t
|
||||||
|
ptrace_read_vec(pid_t pid, long addr, char **vec, char *buf, size_t bufsize)
|
||||||
|
{
|
||||||
|
char *buf0 = buf;
|
||||||
|
int len = 0;
|
||||||
|
size_t slen;
|
||||||
|
debug_decl(ptrace_read_vec, SUDO_DEBUG_EXEC);
|
||||||
|
|
||||||
|
/* Fill in vector. */
|
||||||
|
for (;;) {
|
||||||
|
long word = ptrace(PTRACE_PEEKTEXT, pid, addr, NULL);
|
||||||
|
switch (word) {
|
||||||
|
case -1:
|
||||||
|
sudo_warn("ptrace(PTRACE_PEEKTEXT, %d, %ld, NULL)", pid, addr);
|
||||||
|
goto bad;
|
||||||
|
case 0:
|
||||||
|
vec[len] = NULL;
|
||||||
|
debug_return_size_t(buf - buf0);
|
||||||
|
default:
|
||||||
|
slen = ptrace_read_string(pid, word, buf, bufsize);
|
||||||
|
if (slen == (size_t)-1)
|
||||||
|
goto bad;
|
||||||
|
vec[len++] = buf;
|
||||||
|
buf += slen + 1;
|
||||||
|
bufsize -= slen + 1;
|
||||||
|
addr += sizeof(word);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bad:
|
||||||
|
while (len > 0) {
|
||||||
|
free(vec[len]);
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
debug_return_size_t(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the length of the string vector at addr or -1 on error.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
ptrace_get_vec_len(pid_t pid, long addr)
|
||||||
|
{
|
||||||
|
int len = 0;
|
||||||
|
debug_decl(ptrace_get_vec_len, SUDO_DEBUG_EXEC);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
long word = ptrace(PTRACE_PEEKTEXT, pid, addr, NULL);
|
||||||
|
switch (word) {
|
||||||
|
case -1:
|
||||||
|
sudo_warn("ptrace(PTRACE_PEEKTEXT, %d, %ld, NULL)", pid, addr);
|
||||||
|
debug_return_int(-1);
|
||||||
|
case 0:
|
||||||
|
debug_return_int(len);
|
||||||
|
default:
|
||||||
|
len++;
|
||||||
|
addr += sizeof(word);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the filename, argv and envp of the execve(2) system call.
|
||||||
|
* Returns a dynamically allocated buffer the parent is responsible for.
|
||||||
|
*/
|
||||||
|
static char *
|
||||||
|
get_execve_args(pid_t pid, char **pathname_out, char ***argv_out, char ***envp_out)
|
||||||
|
{
|
||||||
|
char *argbuf, *strtab, *pathname, **argv, **envp;
|
||||||
|
long path_addr, argv_addr, envp_addr, syscallno;
|
||||||
|
struct user_pt_regs regs;
|
||||||
|
struct iovec iov;
|
||||||
|
int argc, envc;
|
||||||
|
size_t bufsize, len;
|
||||||
|
debug_decl(get_execve_args, SUDO_DEBUG_EXEC);
|
||||||
|
|
||||||
|
bufsize = sysconf(_SC_ARG_MAX) + PATH_MAX;
|
||||||
|
argbuf = malloc(bufsize);
|
||||||
|
if (argbuf == NULL)
|
||||||
|
sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
|
||||||
|
|
||||||
|
/* XXX - for amd64 and i386 use PTRACE_GETREGS/PTRACE_SETREGS instead. */
|
||||||
|
iov.iov_base = ®s;
|
||||||
|
iov.iov_len = sizeof(regs);
|
||||||
|
if (ptrace(PTRACE_GETREGSET, pid, (long)NT_PRSTATUS, &iov) == -1) {
|
||||||
|
sudo_warn(U_("unable to get registers for process %d"), (int)pid);
|
||||||
|
goto bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* System call number is stored in the lower 32-bits on 64-bit platforms. */
|
||||||
|
syscallno = reg_syscall(regs) & 0xffffffff;
|
||||||
|
if (syscallno != __NR_execve) {
|
||||||
|
sudo_warnx("%s: unexpected system call %ld", __func__, syscallno);
|
||||||
|
goto bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* execve(2) takes three arguments: pathname, argv, envp. */
|
||||||
|
path_addr = reg_arg1(regs);
|
||||||
|
argv_addr = reg_arg2(regs);
|
||||||
|
envp_addr = reg_arg3(regs);
|
||||||
|
|
||||||
|
#ifdef notyet
|
||||||
|
/* Cause the syscall to fail by changing its number to -1. */
|
||||||
|
reg_syscall(regs) |= 0xffffffff;
|
||||||
|
if (ptrace(PTRACE_SETREGSET, pid, (long)NT_PRSTATUS, &iov) == -1) {
|
||||||
|
sudo_warn("unable to set registers");
|
||||||
|
goto bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allow the syscall to complete and change return value to EACCES. */
|
||||||
|
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
|
||||||
|
waitpid(pid, NULL, 0);
|
||||||
|
reg_retval(regs) = -EACCES;
|
||||||
|
if (ptrace(PTRACE_SETREGSET, pid, (long)NT_PRSTATUS, &iov) == -1) {
|
||||||
|
sudo_warn("unable to set registers");
|
||||||
|
goto bad;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Count argv and envp */
|
||||||
|
argc = ptrace_get_vec_len(pid, argv_addr);
|
||||||
|
envc = ptrace_get_vec_len(pid, envp_addr);
|
||||||
|
if (argc == -1 || envc == -1)
|
||||||
|
goto bad;
|
||||||
|
|
||||||
|
/* Reserve argv and envp at the start of argbuf so they are alined. */
|
||||||
|
if ((argc + 1 + envc + 1) * sizeof(long) >= bufsize) {
|
||||||
|
sudo_warnx("%s", U_("insufficent space for argv and envp"));
|
||||||
|
goto bad;
|
||||||
|
}
|
||||||
|
argv = (char **)argbuf;
|
||||||
|
envp = argv + argc + 1;
|
||||||
|
strtab = (char *)(envp + envc + 1);
|
||||||
|
bufsize -= strtab - argbuf;
|
||||||
|
|
||||||
|
/* Read argv */
|
||||||
|
len = ptrace_read_vec(pid, argv_addr, argv, strtab, bufsize);
|
||||||
|
if (len == (size_t)-1) {
|
||||||
|
sudo_warn(U_("unable to read execve argv for process %d"), (int)pid);
|
||||||
|
goto bad;
|
||||||
|
}
|
||||||
|
strtab += len;
|
||||||
|
bufsize -= len;
|
||||||
|
|
||||||
|
/* Read envp */
|
||||||
|
len = ptrace_read_vec(pid, envp_addr, envp, strtab, bufsize);
|
||||||
|
if (len == (size_t)-1) {
|
||||||
|
sudo_warn(U_("unable to read execve envp for process %d"), (int)pid);
|
||||||
|
goto bad;
|
||||||
|
}
|
||||||
|
strtab += len;
|
||||||
|
bufsize -= len;
|
||||||
|
|
||||||
|
/* Read the pathname. */
|
||||||
|
len = ptrace_read_string(pid, path_addr, strtab, bufsize);
|
||||||
|
if (len == (size_t)-1) {
|
||||||
|
sudo_warn(U_("unable to read execve pathname for process %d"), (int)pid);
|
||||||
|
goto bad;
|
||||||
|
}
|
||||||
|
pathname = strtab;
|
||||||
|
strtab += len;
|
||||||
|
bufsize -= len;
|
||||||
|
|
||||||
|
sudo_debug_execve(SUDO_DEBUG_INFO, pathname, argv, envp);
|
||||||
|
|
||||||
|
*pathname_out = pathname;
|
||||||
|
*argv_out = argv;
|
||||||
|
*envp_out = envp;
|
||||||
|
|
||||||
|
debug_return_ptr(argbuf);
|
||||||
|
bad:
|
||||||
|
free(argbuf);
|
||||||
|
debug_return_ptr(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check whether seccomp(2) filtering supports ptrace(2) traps.
|
* Check whether seccomp(2) filtering supports ptrace(2) traps.
|
||||||
* Only supported by Linux 4.14 and higher.
|
* Only supported by Linux 4.14 and higher.
|
||||||
@@ -155,15 +471,23 @@ exec_ptrace_handled(pid_t pid, int status)
|
|||||||
bool group_stop = false;
|
bool group_stop = false;
|
||||||
debug_decl(exec_ptrace_handled, SUDO_DEBUG_EXEC);
|
debug_decl(exec_ptrace_handled, SUDO_DEBUG_EXEC);
|
||||||
|
|
||||||
if (sigtrap == (SIGTRAP | (PTRACE_EVENT_EXEC << 8))) {
|
if (sigtrap == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8))) {
|
||||||
|
char *pathname, **argv, **envp, *buf;
|
||||||
|
|
||||||
/* Trapped child exec. */
|
/* Trapped child exec. */
|
||||||
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %d called exec",
|
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %d called exec",
|
||||||
__func__, (int)pid);
|
__func__, (int)pid);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* XXX
|
|
||||||
* Get the exec arguments and perform a policy check either over
|
* Get the exec arguments and perform a policy check either over
|
||||||
* the socketpair (pty case) or via a direct function call (no pty).
|
* the socketpair (pty case) or via a direct function call (no pty).
|
||||||
|
* XXX
|
||||||
*/
|
*/
|
||||||
|
buf = get_execve_args(pid, &pathname, &argv, &envp);
|
||||||
|
if (buf == NULL) {
|
||||||
|
sudo_debug_printf(SUDO_DEBUG_ERROR,
|
||||||
|
"%s: %d: unable to get exec args", __func__, (int)pid);
|
||||||
|
}
|
||||||
} else if (sigtrap == (SIGTRAP | (PTRACE_EVENT_CLONE << 8)) ||
|
} else if (sigtrap == (SIGTRAP | (PTRACE_EVENT_CLONE << 8)) ||
|
||||||
sigtrap == (SIGTRAP | (PTRACE_EVENT_VFORK << 8)) ||
|
sigtrap == (SIGTRAP | (PTRACE_EVENT_VFORK << 8)) ||
|
||||||
sigtrap == (SIGTRAP | (PTRACE_EVENT_FORK << 8))) {
|
sigtrap == (SIGTRAP | (PTRACE_EVENT_FORK << 8))) {
|
||||||
|
Reference in New Issue
Block a user