#include <config.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#if defined(HAVE_STDINT_H)
# include <stdint.h>
#elif defined(HAVE_INTTYPES_H)
# include <inttypes.h>
#endif
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#if defined(HAVE_ENDIAN_H)
# include <endian.h>
#elif defined(HAVE_SYS_ENDIAN_H)
# include <sys/endian.h>
#elif defined(HAVE_MACHINE_ENDIAN_H)
# include <machine/endian.h>
#else
# include <compat/endian.h>
#endif
#include <sudo.h>
#include <sudo_exec.h>
#ifdef HAVE_PTRACE_INTERCEPT
# include <exec_intercept.h>
# include <exec_ptrace.h>
# ifdef __LP64__
# define COMPAT_FLAG 0x01
# else
# define COMPAT_FLAG 0x00
# endif
# ifndef SECCOMP_RET_KILL_PROCESS
# define SECCOMP_RET_KILL_PROCESS SECCOMP_RET_KILL
# endif
static int seccomp_trap_supported = -1;
# ifdef HAVE_PROCESS_VM_READV
static size_t page_size;
# endif
static size_t arg_max;
# ifdef SECCOMP_AUDIT_ARCH_COMPAT
static inline unsigned long
get_stack_pointer(struct sudo_ptrace_regs *regs)
{
if (regs->compat) {
return compat_reg_sp(regs->u.compat);
} else {
return reg_sp(regs->u.native);
}
}
static inline void
set_sc_retval(struct sudo_ptrace_regs *regs, int retval)
{
if (regs->compat) {
compat_reg_set_retval(regs->u.compat, retval);
} else {
reg_set_retval(regs->u.native, retval);
}
}
static inline int
get_syscallno(struct sudo_ptrace_regs *regs)
{
if (regs->compat) {
return compat_reg_syscall(regs->u.compat);
} else {
return reg_syscall(regs->u.native);
}
}
static inline void
set_syscallno(pid_t pid, struct sudo_ptrace_regs *regs, int syscallno)
{
if (regs->compat) {
compat_reg_set_syscall(regs->u.compat, syscallno);
} else {
reg_set_syscall(regs->u.native, syscallno);
}
}
static inline unsigned long
get_sc_arg1(struct sudo_ptrace_regs *regs)
{
if (regs->compat) {
return compat_reg_arg1(regs->u.compat);
} else {
return reg_arg1(regs->u.native);
}
}
static inline void
set_sc_arg1(struct sudo_ptrace_regs *regs, unsigned long addr)
{
if (regs->compat) {
compat_reg_set_arg1(regs->u.compat, addr);
} else {
reg_set_arg1(regs->u.native, addr);
}
}
static inline unsigned long
get_sc_arg2(struct sudo_ptrace_regs *regs)
{
if (regs->compat) {
return compat_reg_arg2(regs->u.compat);
} else {
return reg_arg2(regs->u.native);
}
}
static inline void
set_sc_arg2(struct sudo_ptrace_regs *regs, unsigned long addr)
{
if (regs->compat) {
compat_reg_set_arg2(regs->u.compat, addr);
} else {
reg_set_arg2(regs->u.native, addr);
}
}
static inline unsigned long
get_sc_arg3(struct sudo_ptrace_regs *regs)
{
if (regs->compat) {
return compat_reg_arg3(regs->u.compat);
} else {
return reg_arg3(regs->u.native);
}
}
# ifdef notyet
static inline void
set_sc_arg3(struct sudo_ptrace_regs *regs, unsigned long addr)
{
if (regs->compat) {
compat_reg_set_arg3(regs->u.compat, addr);
} else {
reg_set_arg3(regs->u.native, addr);
}
}
static inline unsigned long
get_sc_arg4(struct sudo_ptrace_regs *regs)
{
if (regs->compat) {
return compat_reg_arg4(regs->u.compat);
} else {
return reg_arg4(regs->u.native);
}
}
static inline void
set_sc_arg4(struct sudo_ptrace_regs *regs, unsigned long addr)
{
if (regs->compat) {
compat_reg_set_arg4(regs->u.compat, addr);
} else {
reg_set_arg4(regs->u.native, addr);
}
}
# endif
# else
static inline unsigned long
get_stack_pointer(struct sudo_ptrace_regs *regs)
{
return reg_sp(regs->u.native);
}
static inline void
set_sc_retval(struct sudo_ptrace_regs *regs, int retval)
{
reg_set_retval(regs->u.native, retval);
}
static inline int
get_syscallno(struct sudo_ptrace_regs *regs)
{
return reg_syscall(regs->u.native);
}
static inline void
set_syscallno(pid_t pid, struct sudo_ptrace_regs *regs, int syscallno)
{
reg_set_syscall(regs->u.native, syscallno);
}
static inline unsigned long
get_sc_arg1(struct sudo_ptrace_regs *regs)
{
return reg_arg1(regs->u.native);
}
static inline void
set_sc_arg1(struct sudo_ptrace_regs *regs, unsigned long addr)
{
reg_set_arg1(regs->u.native, addr);
}
static inline unsigned long
get_sc_arg2(struct sudo_ptrace_regs *regs)
{
return reg_arg2(regs->u.native);
}
static inline void
set_sc_arg2(struct sudo_ptrace_regs *regs, unsigned long addr)
{
reg_set_arg2(regs->u.native, addr);
}
static inline unsigned long
get_sc_arg3(struct sudo_ptrace_regs *regs)
{
return reg_arg3(regs->u.native);
}
# ifdef notyet
static inline void
set_sc_arg3(struct sudo_ptrace_regs *regs, unsigned long addr)
{
reg_set_arg3(regs->u.native, addr);
}
static inline unsigned long
get_sc_arg4(struct sudo_ptrace_regs *regs)
{
return reg_arg4(regs->u.native);
}
static inline void
set_sc_arg4(struct sudo_ptrace_regs *regs, unsigned long addr)
{
reg_set_arg4(regs->u.native, addr);
}
# endif
# endif
static bool
ptrace_getregs(int pid, struct sudo_ptrace_regs *regs, int compat)
{
struct iovec iov;
debug_decl(ptrace_getregs, SUDO_DEBUG_EXEC);
iov.iov_base = ®s->u;
iov.iov_len = sizeof(regs->u);
# ifdef __mips__
if (ptrace(PTRACE_GETREGS, pid, NULL, iov.iov_base) == -1)
debug_return_bool(false);
# else
if (ptrace(PTRACE_GETREGSET, pid, (void *)NT_PRSTATUS, &iov) == -1)
debug_return_bool(false);
# endif
if (compat == -1) {
# ifdef SECCOMP_AUDIT_ARCH_COMPAT
if (sizeof(regs->u.native) != sizeof(regs->u.compat)) {
compat = iov.iov_len != sizeof(regs->u.native);
} else {
compat = reg_sp(regs->u.native) < 0xffffffff;
}
# else
compat = false;
# endif
}
if (compat) {
regs->compat = true;
regs->wordsize = sizeof(int);
} else {
regs->compat = false;
regs->wordsize = sizeof(long);
}
debug_return_bool(true);
}
static bool
ptrace_setregs(int pid, struct sudo_ptrace_regs *regs)
{
debug_decl(ptrace_setregs, SUDO_DEBUG_EXEC);
# ifdef __mips__
if (ptrace(PTRACE_SETREGS, pid, NULL, ®s->u) == -1)
debug_return_bool(false);
# else
struct iovec iov;
iov.iov_base = ®s->u;
iov.iov_len = sizeof(regs->u);
if (ptrace(PTRACE_SETREGSET, pid, (void *)NT_PRSTATUS, &iov) == -1)
debug_return_bool(false);
# endif
debug_return_bool(true);
}
#ifdef HAVE_PROCESS_VM_READV
static ssize_t
ptrace_readv_string(pid_t pid, unsigned long addr, char *buf, size_t bufsize)
{
const char *cp, *buf0 = buf;
struct iovec local, remote;
ssize_t nread;
debug_decl(ptrace_read_string, SUDO_DEBUG_EXEC);
for (;;) {
if (bufsize == 0) {
sudo_debug_printf(SUDO_DEBUG_ERROR,
"%s: %d: out of space reading string", __func__, (int)pid);
errno = ENOSPC;
debug_return_ssize_t(-1);
}
local.iov_base = buf;
local.iov_len = bufsize;
remote.iov_base = (void *)addr;
remote.iov_len = MIN(bufsize, page_size);
nread = process_vm_readv(pid, &local, 1, &remote, 1, 0);
switch (nread) {
case -1:
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"process_vm_readv(%d, [0x%lx, %zu], 1, [0x%lx, %zu], 1, 0)",
(int)pid, (unsigned long)local.iov_base, local.iov_len,
(unsigned long)remote.iov_base, remote.iov_len);
debug_return_ssize_t(-1);
case 0:
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"process_vm_readv(%d, [0x%lx, %zu], 1, [0x%lx, %zu], 1, 0): %s",
(int)pid, (unsigned long)local.iov_base, local.iov_len,
(unsigned long)remote.iov_base, remote.iov_len, "premature EOF");
debug_return_ssize_t(-1);
default:
cp = memchr(buf, '\0', (size_t)nread);
if (cp != NULL)
debug_return_ssize_t((cp - buf0) + 1);
if ((size_t)nread != page_size) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"process_vm_readv(%d, [0x%lx, %zu], 1, [0x%lx, %zu], 1, 0)"
" -> %zd",
(int)pid, (unsigned long)local.iov_base, local.iov_len,
(unsigned long)remote.iov_base, remote.iov_len, nread);
}
buf += nread;
bufsize -= (size_t)nread;
addr += (size_t)nread;
break;
}
}
debug_return_ssize_t(-1);
}
#endif
static ssize_t
ptrace_read_string(pid_t pid, unsigned long addr, char *buf, size_t bufsize)
{
const char *cp, *buf0 = buf;
unsigned long word;
size_t i;
debug_decl(ptrace_read_string, SUDO_DEBUG_EXEC);
#ifdef HAVE_PROCESS_VM_READV
ssize_t nread = ptrace_readv_string(pid, addr, buf, bufsize);
if (nread != -1 || errno != ENOSYS)
debug_return_ssize_t(nread);
#endif
for (;;) {
word = ptrace(PTRACE_PEEKDATA, pid, addr, NULL);
if (word == (unsigned long)-1) {
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"ptrace(PTRACE_PEEKDATA, %d, 0x%lx, NULL)", (int)pid, addr);
debug_return_ssize_t(-1);
}
cp = (char *)&word;
for (i = 0; i < sizeof(unsigned long); i++) {
if (bufsize == 0) {
sudo_debug_printf(SUDO_DEBUG_ERROR,
"%s: %d: out of space reading string", __func__, (int)pid);
errno = ENOSPC;
debug_return_ssize_t(-1);
}
*buf = cp[i];
if (*buf++ == '\0')
debug_return_ssize_t(buf - buf0);
bufsize--;
}
addr += sizeof(unsigned long);
}
}
static bool
growbuf(char **bufp, size_t *bufsizep, char **curp, size_t *remp)
{
const size_t oldsize = *bufsizep;
char *newbuf;
debug_decl(growbuf, SUDO_DEBUG_EXEC);
newbuf = reallocarray(*bufp, 2, oldsize);
if (newbuf == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
debug_return_bool(false);
}
if (curp != NULL)
*curp = newbuf + (*curp - *bufp);
if (remp != NULL)
*remp += oldsize;
*bufp = newbuf;
*bufsizep = 2 * oldsize;
debug_return_bool(true);
}
static ssize_t
strtab_to_vec(char *strtab, size_t strtab_len, int *countp, char ***vecp,
char **bufp, size_t *bufsizep, size_t remainder)
{
char *strend = strtab + strtab_len;
char **vec, **vp;
int count = 0;
debug_decl(strtab_to_vec, SUDO_DEBUG_EXEC);
while (remainder < 2 * sizeof(char *)) {
if (!growbuf(bufp, bufsizep, &strtab, &remainder))
debug_return_ssize_t(-1);
strend = strtab + strtab_len;
}
vec = (char **)LONGALIGN(strend);
remainder -= (size_t)((char *)vec - strend);
for (vp = vec; strtab < strend; ) {
while (remainder < 2 * sizeof(char *)) {
if (!growbuf(bufp, bufsizep, &strtab, &remainder))
debug_return_ssize_t(-1);
strend = strtab + strtab_len;
vec = (char **)LONGALIGN(strend);
vp = vec + count;
}
*vp++ = (char *)(strtab - *bufp);
remainder -= sizeof(char *);
strtab = memchr(strtab, '\0', (size_t)(strend - strtab));
if (strtab == NULL)
break;
strtab++;
count++;
}
*vp++ = NULL;
*countp = count;
*vecp = (char **)((char *)vec - *bufp);
debug_return_ssize_t((char *)vp - strend);
}
static ssize_t
ptrace_read_vec(pid_t pid, struct sudo_ptrace_regs *regs, unsigned long addr,
int *countp, char ***vecp, char **bufp, size_t *bufsizep, size_t off)
{
# ifdef SECCOMP_AUDIT_ARCH_COMPAT
unsigned long next_word = (unsigned long)-1;
# endif
size_t strtab_len, remainder = *bufsizep - off;
char *strtab = *bufp + off;
unsigned long word;
ssize_t len;
debug_decl(ptrace_read_vec, SUDO_DEBUG_EXEC);
if (addr == 0) {
char **vp;
while (remainder < 2 * sizeof(char *)) {
if (!growbuf(bufp, bufsizep, &strtab, &remainder))
debug_return_ssize_t(-1);
}
vp = (char **)LONGALIGN(strtab);
*vecp = (char **)((char *)vp - *bufp);
*countp = 0;
*vp++ = NULL;
debug_return_ssize_t((char *)vp - strtab);
}
do {
# ifdef SECCOMP_AUDIT_ARCH_COMPAT
if (next_word == (unsigned long)-1) {
word = ptrace(PTRACE_PEEKDATA, pid, addr, NULL);
if (regs->compat) {
# if BYTE_ORDER == BIG_ENDIAN
next_word = word & 0xffffffffU;
word >>= 32;
# else
next_word = word >> 32;
word &= 0xffffffffU;
# endif
}
} else {
word = next_word;
next_word = (unsigned long)-1;
}
# else
word = ptrace(PTRACE_PEEKDATA, pid, addr, NULL);
# endif
switch (word) {
case -1:
sudo_warn("%s: ptrace(PTRACE_PEEKDATA, %d, 0x%lx, NULL)",
__func__, (int)pid, addr);
debug_return_ssize_t(-1);
case 0:
break;
default:
for (;;) {
len = ptrace_read_string(pid, word, strtab, remainder);
if (len != -1)
break;
if (errno != ENOSPC)
debug_return_ssize_t(-1);
if (!growbuf(bufp, bufsizep, &strtab, &remainder))
debug_return_ssize_t(-1);
}
strtab += len;
remainder -= (size_t)len;
addr += regs->wordsize;
continue;
}
} while (word != 0);
strtab_len = (size_t)(strtab - (*bufp + off));
strtab = *bufp + off;
len = strtab_to_vec(strtab, strtab_len, countp, vecp, bufp, bufsizep,
remainder);
if (len == -1)
debug_return_ssize_t(-1);
debug_return_ssize_t((ssize_t)strtab_len + len);
}
#ifdef HAVE_PROCESS_VM_READV
static ssize_t
ptrace_writev_string(pid_t pid, unsigned long addr, const char *str0)
{
const char *str = str0;
size_t len = strlen(str) + 1;
debug_decl(ptrace_writev_string, SUDO_DEBUG_EXEC);
for (;;) {
struct iovec local, remote;
ssize_t nwritten;
local.iov_base = (void *)str;
local.iov_len = len;
remote.iov_base = (void *)addr;
remote.iov_len = len;
nwritten = process_vm_writev(pid, &local, 1, &remote, 1, 0);
switch (nwritten) {
case -1:
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"process_vm_writev(%d, [0x%lx, %zu], 1, [0x%lx, %zu], 1, 0)",
(int)pid, (unsigned long)local.iov_base, local.iov_len,
(unsigned long)remote.iov_base, remote.iov_len);
debug_return_ssize_t(-1);
case 0:
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"process_vm_writev(%d, [0x%lx, %zu], 1, [0x%lx, %zu], 1, 0): %s",
(int)pid, (unsigned long)local.iov_base, local.iov_len,
(unsigned long)remote.iov_base, remote.iov_len,
"zero bytes written");
debug_return_ssize_t(-1);
default:
str += nwritten;
len -= (size_t)nwritten;
addr += (size_t)nwritten;
if (len == 0)
debug_return_ssize_t(str - str0);
break;
}
}
debug_return_ssize_t(-1);
}
#endif
static ssize_t
ptrace_write_string(pid_t pid, unsigned long addr, const char *str)
{
const char *str0 = str;
size_t i;
union {
unsigned long word;
char buf[sizeof(unsigned long)];
} u;
debug_decl(ptrace_write_string, SUDO_DEBUG_EXEC);
#ifdef HAVE_PROCESS_VM_READV
ssize_t nwritten = ptrace_writev_string(pid, addr, str);
if (nwritten != -1 || errno != ENOSYS)
debug_return_ssize_t(nwritten);
#endif
for (;;) {
for (i = 0; i < sizeof(u.buf); i++) {
if (*str == '\0') {
u.buf[i] = '\0';
continue;
}
u.buf[i] = *str++;
}
if (ptrace(PTRACE_POKEDATA, pid, addr, u.word) == -1) {
sudo_warn("%s: ptrace(PTRACE_POKEDATA, %d, 0x%lx, %.*s)",
__func__, (int)pid, addr, (int)sizeof(u.buf), u.buf);
debug_return_ssize_t(-1);
}
if ((u.word & 0xff) == 0) {
debug_return_ssize_t(str - str0 + 1);
}
addr += sizeof(unsigned long);
}
}
#ifdef HAVE_PROCESS_VM_READV
static ssize_t
ptrace_writev_vec(pid_t pid, struct sudo_ptrace_regs *regs, char **vec,
unsigned long addr, unsigned long strtab)
{
const unsigned long addr0 = addr;
const unsigned long strtab0 = strtab;
unsigned long *addrbuf = NULL;
struct iovec *local, *remote;
struct iovec local_addrs, remote_addrs;
size_t i, j, len, off = 0;
ssize_t expected = -1, nwritten, total_written = 0;
debug_decl(ptrace_writev_vec, SUDO_DEBUG_EXEC);
for (len = 0; vec[len] != NULL; len++)
continue;
local = reallocarray(NULL, len, sizeof(struct iovec));
remote = reallocarray(NULL, len, sizeof(struct iovec));
j = regs->compat && (len & 1) != 0;
addrbuf = reallocarray(NULL, len + 1 + j, regs->wordsize);
if (local == NULL || remote == NULL || addrbuf == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto done;
}
for (i = 0, j = 0; i < len; i++) {
unsigned long word = strtab;
const size_t size = strlen(vec[i]) + 1;
local[i].iov_base = vec[i];
local[i].iov_len = size;
remote[i].iov_base = (void *)strtab;
remote[i].iov_len = size;
strtab += size;
# ifdef SECCOMP_AUDIT_ARCH_COMPAT
if (regs->compat) {
if ((i & 1) == 1) {
continue;
}
# if BYTE_ORDER == BIG_ENDIAN
word <<= 32;
if (vec[i + 1] != NULL)
word |= strtab;
# else
if (vec[i + 1] != NULL)
word |= strtab << 32;
# endif
}
# endif
addrbuf[j++] = word;
addr += sizeof(unsigned long);
}
if (!regs->compat || (len & 1) == 0) {
addrbuf[j] = 0;
}
local_addrs.iov_base = addrbuf;
local_addrs.iov_len = (len + 1) * regs->wordsize;
remote_addrs.iov_base = (void *)addr0;
remote_addrs.iov_len = local_addrs.iov_len;
if (process_vm_writev(pid, &local_addrs, 1, &remote_addrs, 1, 0) == -1)
goto done;
expected = (ssize_t)(strtab - strtab0);
for (;;) {
nwritten = process_vm_writev(pid, local + off, len - off,
remote + off, len - off, 0);
switch (nwritten) {
case -1:
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"process_vm_writev(%d, 0x%lx, %zu, 0x%lx, %zu, 0)",
(int)pid, (unsigned long)local + off, len - off,
(unsigned long)remote + off, len - off);
goto done;
case 0:
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"process_vm_writev(%d, 0x%lx, %zu, 0x%lx, %zu, 0): %s",
(int)pid, (unsigned long)local + off, len - off,
(unsigned long)remote + off, len - off,
"zero bytes written");
goto done;
default:
total_written += nwritten;
if (total_written >= expected)
goto done;
while (off < len) {
nwritten -= (ssize_t)local[off].iov_len;
off++;
if (nwritten <= 0)
break;
}
if (off == len) {
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
"overflow while resuming process_vm_writev()");
goto done;
}
break;
}
}
done:
free(local);
free(remote);
free(addrbuf);
if (total_written == expected)
debug_return_ssize_t(total_written);
debug_return_ssize_t(-1);
}
#endif
static ssize_t
ptrace_write_vec(pid_t pid, struct sudo_ptrace_regs *regs, char **vec,
unsigned long addr, unsigned long strtab)
{
const unsigned long strtab0 = strtab;
ssize_t nwritten;
size_t i;
debug_decl(ptrace_write_vec, SUDO_DEBUG_EXEC);
#ifdef HAVE_PROCESS_VM_READV
nwritten = ptrace_writev_vec(pid, regs, vec, addr, strtab);
if (nwritten != -1 || errno != ENOSYS)
debug_return_ssize_t(nwritten);
#endif
for (i = 0; vec[i] != NULL; i++) {
unsigned long word = strtab;
nwritten = ptrace_write_string(pid, strtab, vec[i]);
if (nwritten == -1)
debug_return_ssize_t(-1);
strtab += (size_t)nwritten;
# ifdef SECCOMP_AUDIT_ARCH_COMPAT
if (regs->compat) {
if ((i & 1) == 1) {
continue;
}
# if BYTE_ORDER == BIG_ENDIAN
word <<= 32;
if (vec[i + 1] != NULL)
word |= strtab;
# else
if (vec[i + 1] != NULL)
word |= strtab << 32;
# endif
}
# endif
if (ptrace(PTRACE_POKEDATA, pid, addr, word) == -1) {
sudo_warn("%s: ptrace(PTRACE_POKEDATA, %d, 0x%lx, 0x%lx)",
__func__, (int)pid, addr, word);
debug_return_ssize_t(-1);
}
addr += sizeof(unsigned long);
}
if (!regs->compat || (i & 1) == 0) {
if (ptrace(PTRACE_POKEDATA, pid, addr, NULL) == -1) {
sudo_warn("%s: ptrace(PTRACE_POKEDATA, %d, 0x%lx, NULL)",
__func__, (int)pid, addr);
debug_return_ssize_t(-1);
}
}
debug_return_ssize_t((ssize_t)(strtab - strtab0));
}
static bool
proc_read_link(pid_t pid, const char *name, char *buf, size_t bufsize)
{
ssize_t len;
char path[PATH_MAX];
debug_decl(proc_read_link, SUDO_DEBUG_EXEC);
len = snprintf(path, sizeof(path), "/proc/%d/%s", (int)pid, name);
if (len > 0 && len < ssizeof(path)) {
len = readlink(path, buf, bufsize - 1);
if (len != -1) {
buf[len] = '\0';
debug_return_bool(true);
}
}
debug_return_bool(false);
}
static char *
get_execve_info(pid_t pid, struct sudo_ptrace_regs *regs, char **pathname_out,
int *argc_out, char ***argv_out, int *envc_out, char ***envp_out)
{
char *argbuf, **argv, **envp, *pathname = NULL;
unsigned long argv_addr, envp_addr, path_addr;
size_t bufsize, off = 0;
int i, argc, envc = 0;
ssize_t nread;
debug_decl(get_execve_info, SUDO_DEBUG_EXEC);
bufsize = PATH_MAX + arg_max;
argbuf = malloc(bufsize);
if (argbuf == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto bad;
}
path_addr = get_sc_arg1(regs);
argv_addr = get_sc_arg2(regs);
envp_addr = get_sc_arg3(regs);
sudo_debug_printf(SUDO_DEBUG_INFO,
"%s: %d: path 0x%lx, argv 0x%lx, envp 0x%lx", __func__,
(int)pid, path_addr, argv_addr, envp_addr);
if (path_addr != 0) {
nread = ptrace_read_string(pid, path_addr, argbuf, bufsize);
if (nread == -1) {
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"unable to read execve pathname for process %d", (int)pid);
goto bad;
}
off = (size_t)nread;
}
nread = ptrace_read_vec(pid, regs, argv_addr, &argc, &argv, &argbuf,
&bufsize, off);
if (nread == -1) {
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"unable to read execve argv for process %d", (int)pid);
goto bad;
}
off += (size_t)nread;
if (argc == 0) {
while (bufsize - off < sizeof(char *)) {
if (!growbuf(&argbuf, &bufsize, NULL, NULL))
goto bad;
}
off += sizeof(char *);
}
nread = ptrace_read_vec(pid, regs, envp_addr, &envc, &envp, &argbuf,
&bufsize, off);
if (nread == -1) {
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"unable to read execve envp for process %d", (int)pid);
goto bad;
}
if (path_addr != 0)
pathname = argbuf;
argv = (char **)(argbuf + (unsigned long)argv);
for (i = 0; i < argc; i++) {
argv[i] = argbuf + (unsigned long)argv[i];
}
envp = (char **)(argbuf + (unsigned long)envp);
for (i = 0; i < envc; i++) {
envp[i] = argbuf + (unsigned long)envp[i];
}
sudo_debug_execve(SUDO_DEBUG_DIAG, pathname, argv, envp);
*pathname_out = pathname;
*argc_out = argc;
*argv_out = argv;
*envc_out = envc;
*envp_out = envp;
debug_return_ptr(argbuf);
bad:
free(argbuf);
debug_return_ptr(NULL);
}
static bool
ptrace_fail_syscall(pid_t pid, struct sudo_ptrace_regs *regs, int ecode)
{
sigset_t chldmask;
bool ret = false;
int status;
debug_decl(ptrace_fail_syscall, SUDO_DEBUG_EXEC);
set_syscallno(pid, regs, -1);
if (!ptrace_setregs(pid, regs)) {
sudo_warn(U_("unable to set registers for process %d"), (int)pid);
debug_return_bool(false);
}
sigemptyset(&chldmask);
sigaddset(&chldmask, SIGCHLD);
sigprocmask(SIG_BLOCK, &chldmask, NULL);
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
for (;;) {
if (waitpid(pid, &status, __WALL) != -1)
break;
if (errno == EINTR)
continue;
sudo_warn(U_("%s: %s"), __func__, "waitpid");
goto done;
}
if (!WIFSTOPPED(status)) {
sudo_warnx(U_("process %d exited unexpectedly"), (int)pid);
goto done;
}
set_sc_retval(regs, -ecode);
if (!ptrace_setregs(pid, regs)) {
sudo_warn(U_("unable to set registers for process %d"), (int)pid);
goto done;
}
ret = true;
done:
sigprocmask(SIG_UNBLOCK, &chldmask, NULL);
debug_return_bool(ret);
}
static bool
have_seccomp_action(const char *action)
{
char line[LINE_MAX];
bool ret = false;
FILE *fp;
debug_decl(have_seccomp_action, SUDO_DEBUG_EXEC);
fp = fopen("/proc/sys/kernel/seccomp/actions_avail", "r");
if (fp != NULL) {
if (fgets(line, sizeof(line), fp) != NULL) {
char *cp, *last;
for ((cp = strtok_r(line, " \t\n", &last)); cp != NULL;
(cp = strtok_r(NULL, " \t\n", &last))) {
if (strcmp(cp, action) == 0) {
ret = true;
break;
}
}
}
fclose(fp);
}
debug_return_bool(ret);
}
bool
set_exec_filter(void)
{
struct sock_filter exec_filter[] = {
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, arch)),
# ifdef SECCOMP_AUDIT_ARCH_COMPAT2
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SECCOMP_AUDIT_ARCH_COMPAT2, 0, 4),
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, COMPAT2_execve, 1, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, COMPAT2_execveat, 0, 13),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRACE | COMPAT_FLAG),
# endif
# ifdef SECCOMP_AUDIT_ARCH_COMPAT
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SECCOMP_AUDIT_ARCH_COMPAT, 0, 4),
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, COMPAT_execve, 1, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, COMPAT_execveat, 0, 8),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRACE | COMPAT_FLAG),
# endif
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SECCOMP_AUDIT_ARCH, 1, 0),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
# ifdef X32_execve
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, X32_execve, 3, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, X32_execveat, 2, 0),
# else
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execve, 3, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execveat, 2, 3),
# endif
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execve, 1, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_execveat, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRACE),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)
};
const struct sock_fprog exec_fprog = {
nitems(exec_filter),
exec_filter
};
debug_decl(set_exec_filter, SUDO_DEBUG_EXEC);
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &exec_fprog) == -1) {
sudo_warn("%s", U_("unable to set seccomp filter"));
debug_return_bool(false);
}
debug_return_bool(true);
}
int
exec_ptrace_seize(pid_t child)
{
const long ptrace_opts = PTRACE_O_TRACESECCOMP|PTRACE_O_TRACECLONE|
PTRACE_O_TRACEFORK|PTRACE_O_TRACEVFORK|
PTRACE_O_TRACEEXEC;
int ret = -1;
int status;
debug_decl(exec_ptrace_seize, SUDO_DEBUG_EXEC);
#ifdef HAVE_PROCESS_VM_READV
page_size = (size_t)sysconf(_SC_PAGESIZE);
if (page_size == (size_t)-1)
page_size = 4096;
#endif
arg_max = (size_t)sysconf(_SC_ARG_MAX);
if (arg_max == (size_t)-1)
arg_max = 128 * 1024;
if (ptrace(PTRACE_SEIZE, child, NULL, ptrace_opts) == -1) {
if (errno != EPERM) {
sudo_warn("%s: ptrace(PTRACE_SEIZE, %d, NULL, 0x%lx)",
__func__, (int)child, ptrace_opts);
goto done;
}
sudo_debug_printf(SUDO_DEBUG_WARN,
"%s: unable to trace process %d, already being traced?",
__func__, (int)child);
ret = false;
}
if (kill(child, SIGUSR1) == -1) {
sudo_warn("kill(%d, SIGUSR1)", (int)child);
goto done;
}
if (!ret)
goto done;
for (;;) {
if (waitpid(child, &status, __WALL) != -1)
break;
if (errno == EINTR)
continue;
sudo_warn(U_("%s: %s"), __func__, "waitpid");
goto done;
}
if (!WIFSTOPPED(status)) {
sudo_warnx(U_("process %d exited unexpectedly"), (int)child);
goto done;
}
if (ptrace(PTRACE_CONT, child, NULL, (void *)SIGUSR1) == -1) {
sudo_warn("%s: ptrace(PTRACE_CONT, %d, NULL, SIGUSR1)",
__func__, (int)child);
goto done;
}
ret = true;
done:
debug_return_int(ret);
}
static bool
pathname_matches(const char *path1, const char *path2, bool do_stat)
{
struct stat sb1, sb2;
debug_decl(pathname_matches, SUDO_DEBUG_EXEC);
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: compare %s to %s", __func__,
path1 ? path1 : "(NULL)", path2 ? path2 : "(NULL)");
if (path1 == NULL || path2 == NULL)
debug_return_bool(false);
if (strcmp(path1, path2) == 0)
debug_return_bool(true);
if (do_stat && stat(path1, &sb1) == 0 && stat(path2, &sb2) == 0) {
if (sb1.st_dev == sb2.st_dev && sb1.st_ino == sb2.st_ino)
debug_return_bool(true);
}
debug_return_bool(false);
}
static int
script_matches(const char *script, const char *execpath, int argc,
char * const *argv)
{
char * const *orig_argv = argv;
size_t linesize = 0;
char *interp, *interp_args, *line = NULL;
char magic[2];
int count;
FILE *fp = NULL;
ssize_t len;
debug_decl(get_interpreter, SUDO_DEBUG_EXEC);
for (count = 0; count < 4; count++) {
if (fp != NULL)
fclose(fp);
fp = fopen(script, "r");
if (fp == NULL) {
sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_ERRNO,
"%s: unable to open %s for reading", __func__, script);
goto done;
}
if (fread(magic, 1, 2, fp) != 2 || memcmp(magic, "#!", 2) != 0) {
sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: %s: not a script",
__func__, script);
goto done;
}
len = getdelim(&line, &linesize, '\n', fp);
if (len == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: %s: can't get interpreter",
__func__, script);
goto done;
}
while (len > 0 && isspace((unsigned char)line[len - 1])) {
len--;
line[len] = '\0';
}
sudo_debug_printf(SUDO_DEBUG_DEBUG, "%s: %s: shebang line \"%s\"",
__func__, script, line);
for (interp = line; isspace((unsigned char)*interp); interp++)
continue;
interp_args = strpbrk(interp, " \t");
if (interp_args != NULL) {
*interp_args++ = '\0';
while (isspace((unsigned char)*interp_args))
interp_args++;
}
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: interpreter %s, args \"%s\"",
__func__, interp, interp_args ? interp_args : "");
if (!pathname_matches(execpath, interp, true)) {
if (argc > 0 && strcmp(interp, argv[1]) == 0) {
if (interp_args == NULL ||
(argc > 1 && strcmp(interp_args, argv[2]) == 0)) {
script = interp;
argv++;
argc--;
if (interp_args != NULL) {
argv++;
argc--;
}
continue;
}
}
}
if (argc > 0 && interp_args != NULL) {
if (strcmp(interp_args, argv[1]) != 0) {
sudo_warnx(
U_("interpreter argument , expected \"%s\", got \"%s\""),
interp_args, argc > 1 ? argv[1] : "(NULL)");
goto done;
}
argv++;
}
argv++;
break;
}
done:
free(line);
if (fp != NULL)
fclose(fp);
debug_return_int((int)(argv - orig_argv));
}
static ssize_t
proc_read_vec(pid_t pid, const char *name, int *countp, char ***vecp,
char **bufp, size_t *bufsizep, size_t off)
{
size_t strtab_len, remainder = *bufsizep - off;
char path[PATH_MAX], *strtab = *bufp + off;
ssize_t len, nread;
int fd;
debug_decl(proc_read_vec, SUDO_DEBUG_EXEC);
len = snprintf(path, sizeof(path), "/proc/%d/%s", (int)pid, name);
if (len >= ssizeof(path))
debug_return_ssize_t(-1);
fd = open(path, O_RDONLY);
if (fd == -1)
debug_return_ssize_t(-1);
do {
nread = read(fd, strtab, remainder);
if (nread < 0) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to read %s", __func__, path);
close(fd);
debug_return_ssize_t(-1);
}
strtab += nread;
remainder -= (size_t)nread;
if (remainder < sizeof(char *)) {
while (!growbuf(bufp, bufsizep, &strtab, &remainder)) {
close(fd);
debug_return_ssize_t(-1);
}
}
} while (nread != 0);
close(fd);
if (strtab - *bufp >= 2 && strtab[-1] == '\0' && strtab[-2] == '\0') {
strtab--;
remainder++;
}
strtab_len = (size_t)(strtab - (*bufp + off));
strtab = *bufp + off;
len = strtab_to_vec(strtab, strtab_len, countp, vecp, bufp, bufsizep,
remainder);
if (len == -1)
debug_return_ssize_t(-1);
debug_return_ssize_t((ssize_t)strtab_len + len);
}
static bool
execve_args_match(const char *pathname, int argc, char * const *argv,
int envc, char * const *envp, bool do_stat,
struct intercept_closure *closure)
{
bool ret = true;
int i;
debug_decl(execve_args_match, SUDO_DEBUG_EXEC);
if (!pathname_matches(pathname, closure->command, do_stat)) {
if (do_stat) {
int skip = script_matches(closure->command, pathname,
argc, argv);
if (skip != 0) {
argv += skip;
argc -= skip;
goto check_argv;
}
}
sudo_warnx(
U_("pathname mismatch, expected \"%s\", got \"%s\""),
closure->command, pathname ? pathname : "(NULL)");
ret = false;
}
check_argv:
for (i = 0; i < argc; i++) {
if (closure->run_argv[i] == NULL) {
ret = false;
sudo_warnx(
U_("%s[%d] mismatch, expected \"%s\", got \"%s\""),
"argv", i, "(NULL)", argv[i] ? argv[i] : "(NULL)");
break;
}
if (argv[i] == NULL) {
ret = false;
sudo_warnx(
U_("%s[%d] mismatch, expected \"%s\", got \"%s\""),
"argv", i, closure->run_argv[i], "(NULL)");
break;
}
if (strcmp(argv[i], closure->run_argv[i]) != 0) {
if (i == 0) {
const char *base;
if (argv[0][0] == '/') {
if (closure->run_argv[0][0] != '/') {
base = sudo_basename(argv[0]);
if (strcmp(base, closure->run_argv[0]) == 0)
continue;
}
} else {
if (closure->run_argv[0][0] == '/') {
base = sudo_basename(closure->run_argv[0]);
if (strcmp(argv[0], base) == 0)
continue;
}
}
}
ret = false;
sudo_warnx(
U_("%s[%d] mismatch, expected \"%s\", got \"%s\""),
"argv", i, closure->run_argv[i], argv[i]);
}
}
for (i = 0; i < envc; i++) {
if (closure->run_envp[i] == NULL) {
ret = false;
sudo_warnx(
U_("%s[%d] mismatch, expected \"%s\", got \"%s\""),
"envp", i, "(NULL)", envp[i] ? envp[i] : "(NULL)");
break;
} else if (envp[i] == NULL) {
ret = false;
sudo_warnx(
U_("%s[%d] mismatch, expected \"%s\", got \"%s\""),
"envp", i, closure->run_envp[i], "(NULL)");
break;
} else if (strcmp(envp[i], closure->run_envp[i]) != 0) {
ret = false;
sudo_warnx(
U_("%s[%d] mismatch, expected \"%s\", got \"%s\""),
"envp", i, closure->run_envp[i], envp[i]);
}
}
debug_return_bool(ret);
}
static bool
verify_execve_args(pid_t pid, struct sudo_ptrace_regs *regs,
struct intercept_closure *closure)
{
char *pathname, **argv, **envp, *buf;
int argc, envc;
bool ret = false;
debug_decl(verify_execve_args, SUDO_DEBUG_EXEC);
buf = get_execve_info(pid, regs, &pathname, &argc, &argv,
&envc, &envp);
if (buf != NULL) {
ret = execve_args_match(pathname, argc, argv, envc, envp, false, closure);
free(buf);
}
debug_return_bool(ret);
}
static bool
ptrace_verify_post_exec(pid_t pid, struct sudo_ptrace_regs *regs,
struct intercept_closure *closure)
{
char **argv, **envp, *argbuf = NULL;
char pathname[PATH_MAX];
sigset_t chldmask;
bool ret = false;
int argc, envc, i, status;
size_t bufsize;
ssize_t len;
debug_decl(ptrace_verify_post_exec, SUDO_DEBUG_EXEC);
sigemptyset(&chldmask);
sigaddset(&chldmask, SIGCHLD);
sigprocmask(SIG_BLOCK, &chldmask, NULL);
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
for (;;) {
if (waitpid(pid, &status, __WALL) != -1)
break;
if (errno == EINTR)
continue;
sudo_warn(U_("%s: %s"), __func__, "waitpid");
goto done;
}
if (!WIFSTOPPED(status)) {
sudo_warnx(U_("process %d exited unexpectedly"), (int)pid);
goto done;
}
if (status >> 8 != (SIGTRAP | (PTRACE_EVENT_EXEC << 8))) {
sudo_warnx(U_("process %d unexpected status 0x%x"), (int)pid, status);
goto done;
}
if (!proc_read_link(pid, "exe", pathname, sizeof(pathname))) {
sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: unable to read /proc/%d/exe",
__func__, (int)pid);
ret = true;
goto done;
}
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %d: verify %s", __func__,
(int)pid, pathname);
bufsize = arg_max;
argbuf = malloc(bufsize);
if (argbuf == NULL) {
sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
goto done;
}
len = proc_read_vec(pid, "cmdline", &argc, &argv, &argbuf, &bufsize, 0);
if (len == -1) {
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"unable to read execve argv for process %d", (int)pid);
goto done;
}
len = proc_read_vec(pid, "environ", &envc, &envp, &argbuf, &bufsize,
(size_t)len);
if (len == -1) {
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"unable to read execve envp for process %d", (int)pid);
goto done;
}
argv = (char **)(argbuf + (unsigned long)argv);
for (i = 0; i < argc; i++) {
argv[i] = argbuf + (unsigned long)argv[i];
}
envp = (char **)(argbuf + (unsigned long)envp);
for (i = 0; i < envc; i++) {
envp[i] = argbuf + (unsigned long)envp[i];
}
ret = execve_args_match(pathname, argc, argv, envc, envp, true, closure);
if (!ret) {
sudo_debug_printf(SUDO_DEBUG_ERROR,
"%s: %d new execve args don't match closure", __func__, (int)pid);
}
done:
free(argbuf);
sigprocmask(SIG_UNBLOCK, &chldmask, NULL);
debug_return_bool(ret);
}
static bool
ptrace_intercept_execve(pid_t pid, struct intercept_closure *closure)
{
char *pathname, **argv, **envp, *buf;
const unsigned int flags = closure->details->flags;
int argc, envc, syscallno;
struct sudo_ptrace_regs regs;
bool path_mismatch = false;
bool argv_mismatch = false;
char cwd[PATH_MAX], *orig_argv0;
unsigned long msg;
bool ret = false;
int i, oldcwd = -1;
debug_decl(ptrace_intercept_execve, SUDO_DEBUG_EXEC);
if (closure->initial_command != 0) {
closure->initial_command--;
debug_return_bool(true);
}
if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, &msg) == -1) {
sudo_warn(U_("unable to get event message for process %d"), (int)pid);
debug_return_bool(false);
}
memset(®s, 0, sizeof(regs));
if (!ptrace_getregs(pid, ®s, msg)) {
sudo_warn(U_("unable to get registers for process %d"), (int)pid);
debug_return_bool(false);
}
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %d: compat: %s, wordsize: %u",
__func__, (int)pid, regs.compat ? "true" : "false", regs.wordsize);
# ifdef SECCOMP_AUDIT_ARCH_COMPAT
if (regs.compat) {
syscallno = get_syscallno(®s);
switch (syscallno) {
case COMPAT_execve:
break;
case COMPAT_execveat:
debug_return_bool(true);
break;
default:
sudo_warnx("%s: unexpected compat system call %d",
__func__, syscallno);
debug_return_bool(false);
}
} else
# endif
{
syscallno = get_syscallno(®s);
switch (syscallno) {
# ifdef X32_execve
case X32_execve:
# endif
case __NR_execve:
break;
# ifdef X32_execveat
case X32_execveat:
# endif
case __NR_execveat:
debug_return_bool(true);
break;
default:
sudo_warnx("%s: unexpected system call %d", __func__, syscallno);
debug_return_bool(false);
}
}
if (!proc_read_link(pid, "cwd", cwd, sizeof(cwd)))
(void)strlcpy(cwd, "unknown", sizeof(cwd));
buf = get_execve_info(pid, ®s, &pathname, &argc, &argv,
&envc, &envp);
if (buf == NULL) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: %d: unable to get execve info", __func__, (int)pid);
if (errno == EIO)
errno = EFAULT;
ptrace_fail_syscall(pid, ®s, errno);
goto done;
}
if (pathname == NULL) {
ptrace_fail_syscall(pid, ®s, EINVAL);
goto done;
}
orig_argv0 = argv[0] ? argv[0] : (char *)"";
argv[0] = pathname;
if (argc == 0) {
argv[1] = NULL;
argc = 1;
argv_mismatch = true;
}
sudo_debug_printf(SUDO_DEBUG_INFO, "%s: %d: checking policy for %s",
__func__, (int)pid, pathname);
if (!intercept_check_policy(pathname, argc, argv, envc, envp, cwd,
&oldcwd, closure)) {
if (closure->errstr != NULL)
sudo_warnx("%s", U_(closure->errstr));
}
switch (closure->state) {
case POLICY_TEST:
path_mismatch = true;
argv_mismatch = true;
if (closure->command == NULL)
closure->command = pathname;
if (closure->run_argv == NULL)
closure->run_argv = argv;
if (closure->run_envp == NULL)
closure->run_envp = envp;
FALLTHROUGH;
case POLICY_ACCEPT:
if (strcmp(pathname, closure->command) != 0)
path_mismatch = true;
if (!path_mismatch) {
if (strcmp(argv[0], orig_argv0) != 0) {
argv[0] = orig_argv0;
free(closure->run_argv[0]);
closure->run_argv[0] = strdup(orig_argv0);
if (closure->run_argv[0] == NULL) {
sudo_warnx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
}
}
for (i = 0; closure->run_argv[i] != NULL && argv[i] != NULL; i++) {
if (strcmp(closure->run_argv[i], argv[i]) != 0) {
argv_mismatch = true;
break;
}
}
if (closure->run_argv[i] != NULL || argv[i] != NULL)
argv_mismatch = true;
if (path_mismatch || argv_mismatch) {
unsigned long sp = get_stack_pointer(®s) - 128;
unsigned long strtab;
size_t space = 0;
ssize_t nwritten;
sudo_debug_execve(SUDO_DEBUG_DIAG, closure->command,
closure->run_argv, envp);
if (argv_mismatch) {
space += ((size_t)argc + 1 + regs.compat) * regs.wordsize;
for (argc = 0; closure->run_argv[argc] != NULL; argc++) {
space += strlen(closure->run_argv[argc]) + 1;
}
}
if (path_mismatch) {
space += strlen(closure->command) + 1;
}
sp -= WORDALIGN(space, regs);
strtab = sp;
if (argv_mismatch) {
set_sc_arg2(®s, sp);
strtab += ((size_t)argc + 1 + regs.compat) * regs.wordsize;
nwritten = ptrace_write_vec(pid, ®s, closure->run_argv,
sp, strtab);
if (nwritten == -1)
goto done;
strtab += (unsigned long)nwritten;
}
if (path_mismatch) {
set_sc_arg1(®s, strtab);
nwritten = ptrace_write_string(pid, strtab, closure->command);
if (nwritten == -1)
goto done;
}
if (!ptrace_setregs(pid, ®s)) {
sudo_warn(U_("unable to set registers for process %d"),
(int)pid);
goto done;
}
if (closure->state == POLICY_TEST) {
if (!verify_execve_args(pid, ®s, closure)) {
sudo_debug_printf(SUDO_DEBUG_ERROR,
"%s: new execve args don't match closure", __func__);
}
}
}
if (closure->state == POLICY_ACCEPT && ISSET(flags, CD_INTERCEPT)) {
if (ISSET(flags, CD_INTERCEPT_VERIFY)) {
if (!ptrace_verify_post_exec(pid, ®s, closure)) {
if (errno != ESRCH)
kill(pid, SIGKILL);
}
}
}
break;
case POLICY_REJECT:
errno = EACCES;
FALLTHROUGH;
default:
ptrace_fail_syscall(pid, ®s, errno);
break;
}
ret = true;
done:
if (oldcwd != -1) {
if (fchdir(oldcwd) == -1) {
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
"%s: unable to restore saved cwd", __func__);
}
close(oldcwd);
}
free(buf);
intercept_closure_reset(closure);
debug_return_bool(ret);
}
bool
exec_ptrace_stopped(pid_t pid, int status, void *intercept)
{
struct intercept_closure *closure = intercept;
const int stopsig = WSTOPSIG(status);
const int sigtrap = status >> 8;
long signo = 0;
bool group_stop = false;
debug_decl(exec_ptrace_stopped, SUDO_DEBUG_EXEC);
if (sigtrap == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8))) {
if (!ptrace_intercept_execve(pid, closure)) {
sudo_debug_printf(SUDO_DEBUG_ERROR,
"%s: %d failed to intercept execve", __func__, (int)pid);
}
} else if (sigtrap == (SIGTRAP | (PTRACE_EVENT_EXEC << 8))) {
sudo_debug_printf(SUDO_DEBUG_ERROR,
"%s: %d PTRACE_EVENT_EXEC", __func__, (int)pid);
} else if (sigtrap == (SIGTRAP | (PTRACE_EVENT_CLONE << 8)) ||
sigtrap == (SIGTRAP | (PTRACE_EVENT_VFORK << 8)) ||
sigtrap == (SIGTRAP | (PTRACE_EVENT_FORK << 8))) {
unsigned long new_pid;
if (sudo_debug_needed(SUDO_DEBUG_INFO)) {
if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, &new_pid) != -1) {
sudo_debug_printf(SUDO_DEBUG_INFO,
"%s: %d forked new child %lu", __func__, (int)pid, new_pid);
} else {
sudo_debug_printf(
SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO|SUDO_DEBUG_ERRNO,
"ptrace(PTRACE_GETEVENTMSG, %d, NULL, %p)", (int)pid,
&new_pid);
}
}
} else {
switch (stopsig) {
case SIGSTOP:
case SIGTSTP:
case SIGTTIN:
case SIGTTOU:
if (status >> 16 == PTRACE_EVENT_STOP) {
sudo_debug_printf(SUDO_DEBUG_INFO,
"%s: %d: group-stop signal %d",
__func__, (int)pid, stopsig);
group_stop = true;
break;
}
FALLTHROUGH;
default:
sudo_debug_printf(SUDO_DEBUG_INFO,
"%s: %d: signal-delivery-stop signal %d",
__func__, (int)pid, stopsig);
signo = stopsig;
break;
}
}
if (group_stop) {
if (ptrace(PTRACE_LISTEN, pid, NULL, 0L) == -1 && errno != ESRCH)
sudo_warn("%s: ptrace(PTRACE_LISTEN, %d, NULL, 0L)",
__func__, (int)pid);
} else {
if (ptrace(PTRACE_CONT, pid, NULL, signo) == -1 && errno != ESRCH)
sudo_warn("%s: ptrace(PTRACE_CONT, %d, NULL, %ld)",
__func__, (int)pid, signo);
}
debug_return_bool(group_stop);
}
bool
exec_ptrace_intercept_supported(void)
{
# ifdef __mips__
return false;
# else
if (seccomp_trap_supported == -1)
seccomp_trap_supported = have_seccomp_action("trap");
return seccomp_trap_supported == true;
# endif
}
bool
exec_ptrace_subcmds_supported(void)
{
if (seccomp_trap_supported == -1)
seccomp_trap_supported = have_seccomp_action("trap");
return seccomp_trap_supported == true;
}
#else
bool
exec_ptrace_stopped(pid_t pid, int status, void *intercept)
{
return true;
}
int
exec_ptrace_seize(pid_t child)
{
return true;
}
bool
exec_ptrace_intercept_supported(void)
{
return false;
}
bool
exec_ptrace_subcmds_supported(void)
{
return false;
}
#endif
void
exec_ptrace_fix_flags(struct command_details *details)
{
debug_decl(exec_ptrace_fix_flags, SUDO_DEBUG_EXEC);
if (ISSET(details->flags, CD_USE_PTRACE)) {
if (ISSET(details->flags, CD_INTERCEPT)) {
if (!exec_ptrace_intercept_supported())
CLR(details->flags, CD_USE_PTRACE);
} else if (ISSET(details->flags, CD_LOG_SUBCMDS)) {
if (!exec_ptrace_subcmds_supported())
CLR(details->flags, CD_USE_PTRACE);
} else {
CLR(details->flags, CD_USE_PTRACE);
}
}
debug_return;
}