From: Alexandra Hájková Date: Thu, 9 Apr 2020 15:28:18 +0000 (+0200) Subject: Add support for execveat syscall X-Git-Tag: VALGRIND_3_17_0~183 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=6f6ff49ffa10da4e8027220d70791a72437846fd;p=thirdparty%2Fvalgrind.git 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 --- diff --git a/.gitignore b/.gitignore index 085a30b4ad..b2cd3a48e1 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/NEWS b/NEWS index 2eebd8874f..9d89f5ec52 100644 --- a/NEWS +++ b/NEWS @@ -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) diff --git a/coregrind/m_syswrap/priv_syswrap-generic.h b/coregrind/m_syswrap/priv_syswrap-generic.h index 73f9224f76..4717abac65 100644 --- a/coregrind/m_syswrap/priv_syswrap-generic.h +++ b/coregrind/m_syswrap/priv_syswrap-generic.h @@ -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); diff --git a/coregrind/m_syswrap/priv_syswrap-linux.h b/coregrind/m_syswrap/priv_syswrap-linux.h index 349a97e4d1..cdc73c1e6d 100644 --- a/coregrind/m_syswrap/priv_syswrap-linux.h +++ b/coregrind/m_syswrap/priv_syswrap-linux.h @@ -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); diff --git a/coregrind/m_syswrap/syswrap-amd64-linux.c b/coregrind/m_syswrap/syswrap-amd64-linux.c index 0aef84af99..28d90135a4 100644 --- a/coregrind/m_syswrap/syswrap-amd64-linux.c +++ b/coregrind/m_syswrap/syswrap-amd64-linux.c @@ -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 diff --git a/coregrind/m_syswrap/syswrap-generic.c b/coregrind/m_syswrap/syswrap-generic.c index 280c48f1b4..864bda76c5 100644 --- a/coregrind/m_syswrap/syswrap-generic.c +++ b/coregrind/m_syswrap/syswrap-generic.c @@ -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) diff --git a/coregrind/m_syswrap/syswrap-linux.c b/coregrind/m_syswrap/syswrap-linux.c index b32bd214ad..5b5b7eee63 100644 --- a/coregrind/m_syswrap/syswrap-linux.c +++ b/coregrind/m_syswrap/syswrap-linux.c @@ -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 diff --git a/include/vki/vki-linux.h b/include/vki/vki-linux.h index ffda18f183..75b5831650 100644 --- a/include/vki/vki-linux.h +++ b/include/vki/vki-linux.h @@ -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 diff --git a/memcheck/tests/linux/Makefile.am b/memcheck/tests/linux/Makefile.am index 14d4a07162..3111f631b4 100644 --- a/memcheck/tests/linux/Makefile.am +++ b/memcheck/tests/linux/Makefile.am @@ -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 diff --git a/memcheck/tests/linux/check_execveat.c b/memcheck/tests/linux/check_execveat.c new file mode 100644 index 0000000000..ad6107fb10 --- /dev/null +++ b/memcheck/tests/linux/check_execveat.c @@ -0,0 +1,18 @@ +#include +#include +#include +#include + +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; +} diff --git a/memcheck/tests/linux/sys-execveat.c b/memcheck/tests/linux/sys-execveat.c new file mode 100644 index 0000000000..921b8888ff --- /dev/null +++ b/memcheck/tests/linux/sys-execveat.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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); +} + diff --git a/memcheck/tests/linux/sys-execveat.stderr.exp b/memcheck/tests/linux/sys-execveat.stderr.exp new file mode 100644 index 0000000000..a58b0fb6ae --- /dev/null +++ b/memcheck/tests/linux/sys-execveat.stderr.exp @@ -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) + diff --git a/memcheck/tests/linux/sys-execveat.stdout.exp b/memcheck/tests/linux/sys-execveat.stdout.exp new file mode 100644 index 0000000000..c7e51ee1a9 --- /dev/null +++ b/memcheck/tests/linux/sys-execveat.stdout.exp @@ -0,0 +1 @@ +execveat exists diff --git a/memcheck/tests/linux/sys-execveat.vgtest b/memcheck/tests/linux/sys-execveat.vgtest new file mode 100644 index 0000000000..87ac016056 --- /dev/null +++ b/memcheck/tests/linux/sys-execveat.vgtest @@ -0,0 +1,3 @@ +prereq: ./check_execveat +prog: sys-execveat +vgopts: -q