Add support for execveat syscall

Refactor the code to be reusable between execve and
execveat syscalls.

https://bugs.kde.org/show_bug.cgi?id=345077
This commit is contained in:
Alexandra Hájková 2020-04-09 17:28:18 +02:00 committed by Mark Wielaard
parent bfd43aef3e
commit 6f6ff49ffa
14 changed files with 279 additions and 44 deletions

2
.gitignore vendored
View File

@ -1052,6 +1052,8 @@
/memcheck/tests/linux/timerfd-syscall
/memcheck/tests/linux/proc-auxv
/memcheck/tests/linux/sys-openat
/memcheck/tests/linux/sys-execveat
/memcheck/tests/linux/check_execveat
# /memcheck/tests/mips32/
/memcheck/tests/mips32/*.stderr.diff

1
NEWS
View File

@ -39,6 +39,7 @@ To see details of a given bug, visit
https://bugs.kde.org/show_bug.cgi?id=XXXXXX
where XXXXXX is the bug number as listed below.
345077 linux syscall execveat support (linux 3.19)
n-i-bz helgrind: If hg_cli__realloc fails, return NULL.
Release 3.16.0 (27 May 2020)

View File

@ -123,6 +123,11 @@ void handle_sys_pwritev(ThreadId tid, SyscallStatus* status,
Int fd, Addr vector, Int count,
const char *str);
extern
void handle_pre_sys_execve(ThreadId tid, SyscallStatus *status, Addr pathname,
Addr arg_2, Addr arg_3, Bool is_execveat,
Bool check_pathptr);
DECL_TEMPLATE(generic, sys_ni_syscall); // * P -- unimplemented
DECL_TEMPLATE(generic, sys_exit);
DECL_TEMPLATE(generic, sys_fork);

View File

@ -299,6 +299,9 @@ DECL_TEMPLATE(linux, sys_membarrier);
// Linux-specific (new in Linux 3.18)
DECL_TEMPLATE(linux, sys_bpf);
// Linux-specific (new in Linux 3.19)
DECL_TEMPLATE(linux, sys_execveat);
// Linux-specific (new in Linux 4.11)
DECL_TEMPLATE(linux, sys_statx);

View File

@ -856,6 +856,7 @@ static SyscallTableEntry syscall_table[] = {
// LIN__(__NR_kexec_file_load, sys_ni_syscall), // 320
LINXY(__NR_bpf, sys_bpf), // 321
LINX_(__NR_execveat, sys_execveat), // 322
LINXY(__NR_preadv2, sys_preadv2), // 327
LINX_(__NR_pwritev2, sys_pwritev2), // 328

View File

@ -65,7 +65,6 @@
#include "config.h"
void ML_(guess_and_register_stack) (Addr sp, ThreadState* tst)
{
Bool debug = False;
@ -2847,9 +2846,10 @@ void VG_(reap_threads)(ThreadId self)
vg_assert(i_am_the_only_thread());
}
// XXX: prototype here seemingly doesn't match the prototype for i386-linux,
// but it seems to work nonetheless...
PRE(sys_execve)
/* This handles the common part of the PRE macro for execve and execveat. */
void handle_pre_sys_execve(ThreadId tid, SyscallStatus *status, Addr pathname,
Addr arg_2, Addr arg_3, Bool is_execveat,
Bool check_pathptr)
{
HChar* path = NULL; /* path to executable */
HChar** envp = NULL;
@ -2860,27 +2860,39 @@ PRE(sys_execve)
Int i, j, tot_args;
SysRes res;
Bool setuid_allowed, trace_this_child;
const char *str;
char str2[30], str3[30];
PRINT("sys_execve ( %#" FMT_REGWORD "x(%s), %#" FMT_REGWORD "x, %#"
FMT_REGWORD "x )", ARG1, (HChar*)(Addr)ARG1, ARG2, ARG3);
PRE_REG_READ3(vki_off_t, "execve",
char *, filename, char **, argv, char **, envp);
PRE_MEM_RASCIIZ( "execve(filename)", ARG1 );
if (ARG2 != 0) {
/* At least the terminating NULL must be addressable. */
if (!ML_(safe_to_deref)((HChar **) (Addr)ARG2, sizeof(HChar *))) {
if (is_execveat)
str = "execveat";
else
str = "execve";
VG_(strcpy)(str2, str);
VG_(strcpy)(str3, str);
if (arg_2 != 0) {
/* At least the terminating NULL must be addressable. */
if (!ML_(safe_to_deref)((HChar **) (Addr)arg_2, sizeof(HChar *))) {
SET_STATUS_Failure(VKI_EFAULT);
return;
}
ML_(pre_argv_envp)( ARG2, tid, "execve(argv)", "execve(argv[i])" );
VG_(strcat)(str2, "(argv)");
VG_(strcat)(str3, "(argv[i])");
ML_(pre_argv_envp)( arg_2, tid, str2, str3 );
}
if (ARG3 != 0) {
// Reset helper strings to syscall name.
str2[VG_(strlen)(str)] = '\0';
str3[VG_(strlen)(str)] = '\0';
if (arg_3 != 0) {
/* At least the terminating NULL must be addressable. */
if (!ML_(safe_to_deref)((HChar **) (Addr)ARG3, sizeof(HChar *))) {
if (!ML_(safe_to_deref)((HChar **) (Addr)arg_3, sizeof(HChar *))) {
SET_STATUS_Failure(VKI_EFAULT);
return;
}
ML_(pre_argv_envp)( ARG3, tid, "execve(envp)", "execve(envp[i])" );
VG_(strcat)(str2, "(envp)");
VG_(strcat)(str3, "(envp[i])");
ML_(pre_argv_envp)( arg_3, tid, str2, str3 );
}
vg_assert(VG_(is_valid_tid)(tid));
@ -2893,35 +2905,36 @@ PRE(sys_execve)
an effort to check that the execve will work before actually
doing it. */
/* Check that the name at least begins in client-accessible storage. */
if (ARG1 == 0 /* obviously bogus */
|| !VG_(am_is_valid_for_client)( ARG1, 1, VKI_PROT_READ )) {
SET_STATUS_Failure( VKI_EFAULT );
return;
/* Check that the name at least begins in client-accessible storage.
If we didn't create it ourselves in execveat. */
if (check_pathptr
&& !VG_(am_is_valid_for_client)( pathname, 1, VKI_PROT_READ )) {
SET_STATUS_Failure( VKI_EFAULT );
return;
}
// debug-only printing
if (0) {
VG_(printf)("ARG1 = %p(%s)\n", (void*)(Addr)ARG1, (HChar*)(Addr)ARG1);
if (ARG2) {
VG_(printf)("ARG2 = ");
VG_(printf)("pathname = %p(%s)\n", (void*)(Addr)pathname, (HChar*)(Addr)pathname);
if (arg_2) {
VG_(printf)("arg_2 = ");
Int q;
HChar** vec = (HChar**)(Addr)ARG2;
HChar** vec = (HChar**)(Addr)arg_2;
for (q = 0; vec[q]; q++)
VG_(printf)("%p(%s) ", vec[q], vec[q]);
VG_(printf)("\n");
} else {
VG_(printf)("ARG2 = null\n");
VG_(printf)("arg_2 = null\n");
}
}
// Decide whether or not we want to follow along
{ // Make 'child_argv' be a pointer to the child's arg vector
// (skipping the exe name)
const HChar** child_argv = (const HChar**)(Addr)ARG2;
const HChar** child_argv = (const HChar**)(Addr)arg_2;
if (child_argv && child_argv[0] == NULL)
child_argv = NULL;
trace_this_child = VG_(should_we_trace_this_child)( (HChar*)(Addr)ARG1,
trace_this_child = VG_(should_we_trace_this_child)( (HChar*)(Addr)pathname,
child_argv );
}
@ -2929,7 +2942,7 @@ PRE(sys_execve)
// ok, etc. We allow setuid executables to run only in the case when
// we are not simulating them, that is, they to be run natively.
setuid_allowed = trace_this_child ? False : True;
res = VG_(pre_exec_check)((const HChar *)(Addr)ARG1, NULL, setuid_allowed);
res = VG_(pre_exec_check)((const HChar *)(Addr)pathname, NULL, setuid_allowed);
if (sr_isError(res)) {
SET_STATUS_Failure( sr_Err(res) );
return;
@ -2946,7 +2959,7 @@ PRE(sys_execve)
}
/* After this point, we can't recover if the execve fails. */
VG_(debugLog)(1, "syswrap", "Exec of %s\n", (HChar*)(Addr)ARG1);
VG_(debugLog)(1, "syswrap", "Exec of %s\n", (HChar*)(Addr)pathname);
// Terminate gdbserver if it is active.
@ -2982,7 +2995,7 @@ PRE(sys_execve)
}
} else {
path = (HChar*)(Addr)ARG1;
path = (HChar*)(Addr)pathname;
}
// Set up the child's environment.
@ -2996,29 +3009,29 @@ PRE(sys_execve)
//
// Then, if tracing the child, set VALGRIND_LIB for it.
//
if (ARG3 == 0) {
if (arg_3 == 0) {
envp = NULL;
} else {
envp = VG_(env_clone)( (HChar**)(Addr)ARG3 );
envp = VG_(env_clone)( (HChar**)(Addr)arg_3 );
if (envp == NULL) goto hosed;
VG_(env_remove_valgrind_env_stuff)( envp, True /*ro_strings*/, NULL );
}
if (trace_this_child) {
// Set VALGRIND_LIB in ARG3 (the environment)
// Set VALGRIND_LIB in arg_3 (the environment)
VG_(env_setenv)( &envp, VALGRIND_LIB, VG_(libdir));
}
// Set up the child's args. If not tracing it, they are
// simply ARG2. Otherwise, they are
// simply arg_2. Otherwise, they are
//
// [launcher_basename] ++ VG_(args_for_valgrind) ++ [ARG1] ++ ARG2[1..]
// [launcher_basename] ++ VG_(args_for_valgrind) ++ [pathname] ++ arg_2[1..]
//
// except that the first VG_(args_for_valgrind_noexecpass) args
// are omitted.
//
if (!trace_this_child) {
argv = (HChar**)(Addr)ARG2;
argv = (HChar**)(Addr)arg_2;
} else {
vg_assert( VG_(args_for_valgrind) );
vg_assert( VG_(args_for_valgrind_noexecpass) >= 0 );
@ -3033,7 +3046,7 @@ PRE(sys_execve)
// name of client exe
tot_args++;
// args for client exe, skipping [0]
arg2copy = (HChar**)(Addr)ARG2;
arg2copy = (HChar**)(Addr)arg_2;
if (arg2copy && arg2copy[0]) {
for (i = 1; arg2copy[i]; i++)
tot_args++;
@ -3049,7 +3062,7 @@ PRE(sys_execve)
continue;
argv[j++] = * (HChar**) VG_(indexXA)( VG_(args_for_valgrind), i );
}
argv[j++] = (HChar*)(Addr)ARG1;
argv[j++] = (HChar*)(Addr)pathname;
if (arg2copy && arg2copy[0])
for (i = 1; arg2copy[i]; i++)
argv[j++] = arg2copy[i];
@ -3113,9 +3126,9 @@ PRE(sys_execve)
VG_(printf)("env: %s\n", *cpp);
}
// always execute this because it's executing valgrind, not the "target" exe
SET_STATUS_from_SysRes(
VG_(do_syscall3)(__NR_execve, (UWord)path, (UWord)argv, (UWord)envp)
);
VG_(do_syscall3)(__NR_execve, (UWord)path, (UWord)argv, (UWord)envp));
/* If we got here, then the execve failed. We've already made way
too much of a mess to continue, so we have to abort. */
@ -3123,12 +3136,30 @@ PRE(sys_execve)
vg_assert(FAILURE);
VG_(message)(Vg_UserMsg, "execve(%#" FMT_REGWORD "x(%s), %#" FMT_REGWORD
"x, %#" FMT_REGWORD "x) failed, errno %lu\n",
ARG1, (HChar*)(Addr)ARG1, ARG2, ARG3, ERR);
pathname, (HChar*)(Addr)pathname, arg_2, arg_3, ERR);
VG_(message)(Vg_UserMsg, "EXEC FAILED: I can't recover from "
"execve() failing, so I'm dying.\n");
VG_(message)(Vg_UserMsg, "Add more stringent tests in PRE(sys_execve), "
"or work out how to recover.\n");
VG_(exit)(101);
}
// XXX: prototype here seemingly doesn't match the prototype for i386-linux,
// but it seems to work nonetheless...
PRE(sys_execve)
{
PRINT("sys_execve ( %#" FMT_REGWORD "x(%s), %#" FMT_REGWORD "x, %#"
FMT_REGWORD "x )", ARG1, (HChar*)(Addr)ARG1, ARG2, ARG3);
PRE_REG_READ3(vki_off_t, "execve",
char *, filename, char **, argv, char **, envp);
PRE_MEM_RASCIIZ( "execve(filename)", ARG1 );
char *pathname = (char *)ARG1;
Addr arg_2 = (Addr)ARG2;
Addr arg_3 = (Addr)ARG3;
handle_pre_sys_execve(tid, status, (Addr)pathname, arg_2, arg_3, 0, True);
}
PRE(sys_access)

View File

@ -13138,6 +13138,87 @@ POST(sys_io_uring_register)
{
}
PRE(sys_execveat)
{
PRINT("sys_execveat ( %lu, %#lx(%s), %#lx, %#lx, %lu", ARG1, ARG2, (char*)ARG2, ARG3, ARG4, ARG5);
PRE_REG_READ5(vki_off_t, "execveat",
int, fd, char *, filename, char **, argv, char **, envp, int, flags);
PRE_MEM_RASCIIZ( "execveat(filename)", ARG2);
#if !defined(__NR_execveat)
SET_STATUS_Failure(VKI_ENOSYS);
return;
#endif
char *path = (char*) ARG2;
Addr arg_2 = ARG3;
Addr arg_3 = ARG4;
const HChar *buf;
HChar *abs_path = NULL;
Bool check_at_symlink = False;
Bool check_pathptr = True;
if (ML_(safe_to_deref) (path, 1)) {
/* If pathname is absolute, we'll ignore dirfd
* and just pass the pathname, try to determine
* the absolute path otherwise. */
if (path[0] != '/') {
/* Check dirfd is a valid fd. */
if (!ML_(fd_allowed)(ARG1, "execveat", tid, False)) {
SET_STATUS_Failure( VKI_EBADF );
return;
}
/* If pathname is empty and AT_EMPTY_PATH is
set then dirfd describes the whole path. */
if (path[0] == '\0') {
if (ARG5 & VKI_AT_EMPTY_PATH) {
if (VG_(resolve_filename)(ARG1, &buf)) {
VG_(strcpy)(path, buf);
check_pathptr = False;
}
}
}
else if (ARG1 == VKI_AT_FDCWD) {
check_at_symlink = True;
} else
if (ARG5 & VKI_AT_SYMLINK_NOFOLLOW)
check_at_symlink = True;
else if (VG_(resolve_filename)(ARG1, &buf)) {
abs_path = VG_(malloc)("execveat",
(VG_(strlen)(buf) + 1
+ VG_(strlen)(path) + 1));
VG_(sprintf)(abs_path, "%s/%s", buf, path);
path = abs_path;
check_pathptr = False;
}
else
path = NULL;
if (check_at_symlink) {
struct vg_stat statbuf;
SysRes statres;
statres = VG_(stat)(path, &statbuf);
if (sr_isError(statres) || VKI_S_ISLNK(statbuf.mode)) {
SET_STATUS_Failure( VKI_ELOOP );
return;
}
}
}
} else {
SET_STATUS_Failure(VKI_EFAULT);
return;
}
handle_pre_sys_execve(tid, status, (Addr) path, arg_2, arg_3, 1,
check_pathptr);
/* The exec failed, we keep running... cleanup. */
VG_(free)(abs_path);
}
#undef PRE
#undef POST

View File

@ -1296,6 +1296,8 @@ struct vki_seminfo {
#define VKI_EWOULDBLOCK VKI_EAGAIN
#define VKI_ELOOP 40
//----------------------------------------------------------------------
// From linux-2.6.8.1/include/linux/wait.h
//----------------------------------------------------------------------
@ -1502,6 +1504,7 @@ struct vki_flock64 {
};
#define VKI_AT_EMPTY_PATH 0x1000 /* Allow empty relative pathname */
#define VKI_AT_SYMLINK_NOFOLLOW 0x100 /* Do not follow symbolic links. */
//----------------------------------------------------------------------
// From linux-2.6.8.1/include/linux/sysctl.h

View File

@ -28,7 +28,8 @@ EXTRA_DIST = \
proc-auxv.vgtest proc-auxv.stderr.exp getregset.vgtest \
getregset.stderr.exp getregset.stdout.exp \
sys-preadv_pwritev.vgtest sys-preadv_pwritev.stderr.exp \
sys-preadv2_pwritev2.vgtest sys-preadv2_pwritev2.stderr.exp
sys-preadv2_pwritev2.vgtest sys-preadv2_pwritev2.stderr.exp \
sys-execveat.vgtest sys-execveat.stderr.exp sys-execveat.stdout.exp
check_PROGRAMS = \
brk \
@ -47,7 +48,9 @@ check_PROGRAMS = \
syslog-syscall \
sys-statx \
timerfd-syscall \
proc-auxv
proc-auxv \
sys-execveat \
check_execveat
if HAVE_AT_FDCWD
check_PROGRAMS += sys-openat

View File

@ -0,0 +1,18 @@
#include <sys/syscall.h>
#include <errno.h>
#include <unistd.h>
#include <stddef.h>
int main(int argc, char **argv)
{
int has_execveat = 0;
#if defined(__NR_execveat)
errno = 0;
syscall(__NR_execveat, 0, NULL, 0, 0, 0);
has_execveat = (errno != ENOSYS);
#else
has_execveat = 0;
#endif
return has_execveat ? 0 : 1;
}

View File

@ -0,0 +1,64 @@
#include <sys/syscall.h>
#include <errno.h>
#include <dirent.h>
#include <unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
static int sys_execveat (int dirfd, const char *pathname,
char *const argv[], char *const envp[],
int flags)
{
#if defined(__NR_execveat)
return syscall(__NR_execveat, dirfd, pathname, argv, envp, flags);
#else
errno = ENOSYS;
return -1;
#endif
}
int main()
{
char *argv[] = { "foobar", "execveat exists", NULL };
char *envp[] = { NULL };
DIR *dirp;
int fd;
dirp = opendir("/bin");
if (dirp == NULL) {
perror("execveat");
exit(EXIT_FAILURE);
}
fd = dirfd(dirp);
/* Check valgrind will produce expected warnings for the
various wrong arguments. */
do {
char *mem = malloc(16);
void *t = (void *) &mem[0];
void *z = (void *) -1;
int flag = *((int *) &mem[8]);
sys_execveat(-1, "bin/xecho", argv, envp, 0);
sys_execveat(-1, "xecho", argv, envp, 0);
sys_execveat(fd, "xecho", argv, envp, flag);
sys_execveat(fd, "", argv, envp, 0);
sys_execveat(fd, NULL, argv, envp, 0);
sys_execveat(fd, "xecho", t, envp, 0);
sys_execveat(fd, "xecho", z, envp, 0);
} while (0);
/* Check execveat called with the correct arguments works. */
if (sys_execveat(fd, "echo", argv, envp, 0) == -1) {
perror("execveat");
exit(EXIT_FAILURE);
}
closedir(dirp);
exit(EXIT_SUCCESS);
}

View File

@ -0,0 +1,19 @@
Syscall param execveat(flags) contains uninitialised byte(s)
...
by 0x........: sys_execveat (sys-execveat.c:16)
by 0x........: main (sys-execveat.c:48)
Syscall param execveat(filename) points to unaddressable byte(s)
...
by 0x........: sys_execveat (sys-execveat.c:16)
by 0x........: main (sys-execveat.c:50)
Address 0x........ is not stack'd, malloc'd or (recently) free'd
Syscall param execveat(argv) points to uninitialised byte(s)
...
by 0x........: sys_execveat (sys-execveat.c:16)
by 0x........: main (sys-execveat.c:51)
Address 0x........ is 0 bytes inside a block of size 16 alloc'd
at 0x........: malloc (vg_replace_malloc.c:...)
by 0x........: main (sys-execveat.c:41)

View File

@ -0,0 +1 @@
execveat exists

View File

@ -0,0 +1,3 @@
prereq: ./check_execveat
prog: sys-execveat
vgopts: -q