selftests: add tests for process_vm_exec Output: $ make run_tests TAP version 13 1..4 # selftests: process_vm_exec: process_vm_exec # 1..1 # ok 1 275 ns/syscall # # Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0 ok 1 selftests: process_vm_exec: process_vm_exec # selftests: process_vm_exec: process_vm_exec_fault # 1..1 # ok 1 789 ns/signal # # Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0 ok 2 selftests: process_vm_exec: process_vm_exec_fault # selftests: process_vm_exec: ptrace_vm_exec # 1..1 # ok 1 1378 ns/syscall# Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0 ok 3 selftests: process_vm_exec: ptrace_vm_exec # selftests: process_vm_exec: process_vm_exec_syscall # 1..1 # ok 1 write works as expectd # # Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0 ok 4 selftests: process_vm_exec: process_vm_exec_syscall BUG=183648601 TEST=Compiled and verified locally SOURCE=FROMLIST(https://lore.kernel.org/linux-api/20210414055217.543246-1-avagin@gmail.com/`) RELEASE_NOTE=Port process_vm_exec to the COS kernel. Signed-off-by: Andrei Vagin <avagin@google.com> Change-Id: I6dcd9069afbeca223ab212dcf6395add63f685f0 Reviewed-on: https://cos-review.googlesource.com/c/third_party/kernel/+/17936 Reviewed-by: Oleksandr Tymoshenko <ovt@google.com> Tested-by: Oleksandr Tymoshenko <ovt@google.com>
diff --git a/tools/testing/selftests/process_vm_exec/Makefile b/tools/testing/selftests/process_vm_exec/Makefile new file mode 100644 index 0000000..8ca1c84 --- /dev/null +++ b/tools/testing/selftests/process_vm_exec/Makefile
@@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 + +UNAME_M := $(shell uname -m) +ARCH ?= $(shell echo $(UNAME_M) | sed -e s/i.86/x86/ -e s/x86_64/x86/ -e s/aarch64/arm64/) + +CFLAGS += -iquote../../../../include/uapi -iquote../../../../arch/$(ARCH)/include/uapi + +TEST_GEN_PROGS_x86_64 := process_vm_exec process_vm_exec_fault ptrace_vm_exec process_vm_exec_syscall process_vm_exec_signal +TEST_GEN_PROGS_aarch64 := $(TEST_GEN_PROGS_x86_64) +TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(UNAME_M)) + +include ../lib.mk + +$(OUTPUT)/process_vm_exec_signal: CFLAGS += -DPROCESS_VM_EXEC_TEST_SIGNAL
diff --git a/tools/testing/selftests/process_vm_exec/log.h b/tools/testing/selftests/process_vm_exec/log.h new file mode 100644 index 0000000..ef268c2 --- /dev/null +++ b/tools/testing/selftests/process_vm_exec/log.h
@@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __SELFTEST_PROCESS_VM_EXEC_LOG_H__ +#define __SELFTEST_PROCESS_VM_EXEC_LOG_H__ + +#define pr_msg(fmt, lvl, ...) \ + ksft_print_msg("[%s] (%s:%d)\t" fmt "\n", \ + lvl, __FILE__, __LINE__, ##__VA_ARGS__) + +#define pr_p(func, fmt, ...) func(fmt ": %m", ##__VA_ARGS__) + +#define pr_err(fmt, ...) \ + ({ \ + ksft_test_result_error(fmt "\n", ##__VA_ARGS__); \ + -1; \ + }) + +#define pr_fail(fmt, ...) \ + ({ \ + ksft_test_result_fail(fmt "\n", ##__VA_ARGS__); \ + -1; \ + }) + +#define pr_perror(fmt, ...) pr_p(pr_err, fmt, ##__VA_ARGS__) + +#endif
diff --git a/tools/testing/selftests/process_vm_exec/process_vm_exec.c b/tools/testing/selftests/process_vm_exec/process_vm_exec.c new file mode 100644 index 0000000..4f2e7e4 --- /dev/null +++ b/tools/testing/selftests/process_vm_exec/process_vm_exec.c
@@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <sys/user.h> +#include <sys/uio.h> +#include <sys/prctl.h> +#include "asm/unistd.h" +#include <time.h> +#include <sys/mman.h> +#include <stdint.h> + +#include "asm/process_vm_exec.h" +#include "linux/process_vm_exec.h" + +#include "../kselftest.h" +#include "log.h" + +#ifndef __NR_process_vm_exec +#define __NR_process_vm_exec 441 +#endif + +#define TEST_SYSCALL 123 +#define TEST_SYSCALL_RET 456 +#define TEST_MARKER 789 +#define TEST_TIMEOUT 5 +#define TEST_STACK_SIZE 65536 + +#if defined(__x86_64__) +static inline long __syscall1(long n, long a1) +{ + unsigned long ret; + + __asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1) : "rcx", "r11", "memory"); + + return ret; +} +#elif defined(__aarch64__) +#define __asm_syscall(...) do { \ + __asm__ __volatile__ ( "svc 0" \ + : "=r"(x0) : __VA_ARGS__ : "memory", "cc"); \ + return x0; \ + } while (0) + + +static inline long __syscall1(long n, long a) +{ + register long x8 __asm__("x8") = n; + register long x0 __asm__("x0") = a; + __asm_syscall("r"(x8), "0"(x0)); +} +#endif + +int marker; + +static void guest(void) +{ + while (1) + if (__syscall1(TEST_SYSCALL, marker) != TEST_SYSCALL_RET) + abort(); +} + +int main(int argc, char **argv) +{ + struct process_vm_exec_context ctx = { + .version = PROCESS_VM_EXEC_GOOGLE_V1, + }; + struct timespec start, cur; + int status, ret, i; + pid_t pid; + long sysnr; + void *stack; + + ksft_set_plan(1); + + stack = mmap(NULL, TEST_STACK_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); + if (stack == MAP_FAILED) + return pr_perror("mmap"); + + pid = fork(); + if (pid == 0) { + marker = TEST_MARKER; + prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); + kill(getpid(), SIGSTOP); + /* unreachable */ + abort(); + return 0; + } + +#if defined(__x86_64__) + ctx.sigctx.rip = (long)guest; + ctx.sigctx.rsp = (long)stack + TEST_STACK_SIZE; + ctx.sigctx.cs = 0x33; +#elif defined(__aarch64__) + ctx.sigctx.pc = (long)guest; + ctx.sigctx.pstate = 0x40001000; + ctx.sigctx.sp = (long)stack + TEST_STACK_SIZE; +#endif + + sysnr = 0; + clock_gettime(CLOCK_MONOTONIC, &start); + while (1) { + unsigned long long sigmask = 0xffffffff; + siginfo_t siginfo; + + clock_gettime(CLOCK_MONOTONIC, &cur); + if (start.tv_sec + TEST_TIMEOUT < cur.tv_sec || + (start.tv_sec + TEST_TIMEOUT == cur.tv_sec && + start.tv_nsec < cur.tv_nsec)) + break; + + ret = syscall(__NR_process_vm_exec, pid, &ctx, 0, &siginfo, &sigmask, 8); +#ifdef __DEBUG + ksft_print_msg("ret %d signo %d sysno %d ip %lx\n", + ret, siginfo.si_signo, siginfo.si_syscall, ctx.rip); +#endif + if (ret != 0) + pr_fail("unexpected return code: ret %d errno %d", ret, errno); + if (siginfo.si_signo != SIGSYS) + pr_fail("unexpected signal: %d", siginfo.si_signo); + if (siginfo.si_syscall != TEST_SYSCALL) + pr_fail("unexpected syscall: %d", siginfo.si_syscall); +#if defined(__x86_64__) + ctx.sigctx.rax = TEST_SYSCALL_RET; +#elif defined(__aarch64__) + ctx.sigctx.regs[0] = TEST_SYSCALL_RET; +#endif + sysnr++; + } + ksft_test_result_pass("%ld ns/syscall\n", 1000000000 / sysnr); + ksft_exit_pass(); + return 0; +}
diff --git a/tools/testing/selftests/process_vm_exec/process_vm_exec_fault.c b/tools/testing/selftests/process_vm_exec/process_vm_exec_fault.c new file mode 100644 index 0000000..ad8afe8 --- /dev/null +++ b/tools/testing/selftests/process_vm_exec/process_vm_exec_fault.c
@@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <time.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/mman.h> +#include <sys/prctl.h> +#include <sys/wait.h> +#include <sys/user.h> +#include <sys/uio.h> +#include <asm/unistd.h> +#include <sys/ptrace.h> +#include <linux/elf.h> + +#include "asm/process_vm_exec.h" +#include "linux/process_vm_exec.h" + +#include "../kselftest.h" +#include "log.h" + +#ifndef __NR_process_vm_exec +#define __NR_process_vm_exec 441 +#endif + +#define TEST_TIMEOUT 5 +#define TEST_STACK_SIZE 65536 + +#define TEST_VAL 0xaabbccddee + +unsigned long test_val; + +#if defined(__x86_64__) +static inline void fault(long *addr, long val) +{ + __asm__ __volatile__ ( + "movq %%rcx, (%%rax)\n" + : + : "a"(addr), "c"(val) + :); +} +#elif defined(__aarch64__) +static void fault(long *addr, long val) +{ + *addr = val; +} + +static void fault(long *addr, long val) __attribute__((noinline)); +#endif + +int marker; + +static void guest(void) +{ + long *addr = 0; + + while (1) { + addr = (long *)(((long)addr + 1) % 8); + fault(addr, 0); + if (test_val != TEST_VAL) + _exit(1); + } +} + +static long fault_addr; +#ifdef PROCESS_VM_EXEC_TEST_SIGNAL +static void segv(int signo, siginfo_t *info, void *data) +{ + fault_addr = (long)info->si_addr; +} +#endif + +int main(char argc, char **argv) +{ + unsigned long long sigmask = 0xffffffff; + struct process_vm_exec_context ctx = { + .version = PROCESS_VM_EXEC_GOOGLE_V1, + }; + siginfo_t siginfo; + struct timespec start, cur; + unsigned long addr; + int status, ret, i; + char *stack; + pid_t pid; + long faults; + + ksft_set_plan(1); + + stack = mmap(NULL, TEST_STACK_SIZE, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, 0, 0); + if (stack == MAP_FAILED) + return pr_perror("mmap"); + + pid = fork(); + if (pid == 0) { + prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); + kill(getpid(), SIGSTOP); + /* unreachable */ + abort(); + return 0; + } + +#ifdef PROCESS_VM_EXEC_TEST_SIGNAL + { + struct sigaction act = { + .sa_sigaction = segv, + .sa_flags = SA_SIGINFO, + }; + if (sigaction(SIGSEGV, &act, NULL)) + return pr_perror("sigaction"); + sigmask = 0; + } +#endif + +#if defined(__x86_64__) + ctx.sigctx.rip = (long)guest; + ctx.sigctx.rsp = (long)stack + TEST_STACK_SIZE; + ctx.sigctx.cs = 0x33; +#elif defined(__aarch64__) + ctx.sigctx.pc = (long)guest; + ctx.sigctx.pstate = 0x40001000; + ctx.sigctx.sp = (long)stack + TEST_STACK_SIZE; +#endif + + faults = 0; + addr = 0; + clock_gettime(CLOCK_MONOTONIC, &start); + while (1) { + addr = (addr + 1) % 8; + + clock_gettime(CLOCK_MONOTONIC, &cur); + if (start.tv_sec + TEST_TIMEOUT < cur.tv_sec || + (start.tv_sec + TEST_TIMEOUT == cur.tv_sec && + start.tv_nsec < cur.tv_nsec)) + break; + + ret = syscall(__NR_process_vm_exec, pid, &ctx, 0, &siginfo, &sigmask, 8); +#ifndef PROCESS_VM_EXEC_TEST_SIGNAL + fault_addr = (long)siginfo.si_addr; +#endif + if (fault_addr != addr % 8) + return pr_fail("unexpected address: %lx", fault_addr); +#if defined(__x86_64__) + if (addr % 8 != ctx.sigctx.rax) + return pr_fail("unexpected address: %lx", ctx.sigctx.rax); + ctx.sigctx.rax = (long)&test_val; + ctx.sigctx.rcx = TEST_VAL; +#elif defined(__aarch64__) + if (addr % 8 != ctx.sigctx.regs[0]) + return pr_fail("unexpected address: %lx", ctx.sigctx.regs[0]); + ctx.sigctx.regs[0] = (long)&test_val; + ctx.sigctx.regs[1] = TEST_VAL; +#endif + faults++; + } + ksft_test_result_pass("%ld ns/signal\n", 1000000000 / faults); + ksft_exit_pass(); + return 0; +}
diff --git a/tools/testing/selftests/process_vm_exec/process_vm_exec_signal.c b/tools/testing/selftests/process_vm_exec/process_vm_exec_signal.c new file mode 120000 index 0000000..b6f5819 --- /dev/null +++ b/tools/testing/selftests/process_vm_exec/process_vm_exec_signal.c
@@ -0,0 +1 @@ +process_vm_exec_fault.c \ No newline at end of file
diff --git a/tools/testing/selftests/process_vm_exec/process_vm_exec_syscall.c b/tools/testing/selftests/process_vm_exec/process_vm_exec_syscall.c new file mode 100644 index 0000000..581c262 --- /dev/null +++ b/tools/testing/selftests/process_vm_exec/process_vm_exec_syscall.c
@@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/prctl.h> +#include <sys/user.h> +#include <sys/uio.h> +#include <asm/unistd.h> + +#include "asm/process_vm_exec.h" +#include "linux/process_vm_exec.h" + +#include "../kselftest.h" +#include "log.h" + +#ifndef __NR_process_vm_exec +#define __NR_process_vm_exec 441 +#endif + +#ifndef PROCESS_VM_EXEC_SYSCALL +#define PROCESS_VM_EXEC_SYSCALL 0x1 +#endif + +#define TEST_VAL 0x1e511e51 + +int test_val = TEST_VAL; + +int main(int argc, char **argv) +{ + struct process_vm_exec_context ctx = { + .version = PROCESS_VM_EXEC_GOOGLE_V1, + }; + unsigned long long sigmask; + int ret, p[2], val; + siginfo_t siginfo = {}; + pid_t pid; + + ksft_set_plan(1); + + pid = fork(); + if (pid < 0) + return pr_perror("fork"); + if (pid == 0) { + prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); + kill(getpid(), SIGSTOP); + return 0; + } + + test_val = 0; + if (pipe(p)) + return pr_perror("pipe"); + +#if defined(__x86_64__) + ctx.sigctx.rax = __NR_write; + ctx.sigctx.rdi = p[1]; + ctx.sigctx.rsi = (unsigned long) &test_val; + ctx.sigctx.rdx = sizeof(test_val); + ctx.sigctx.r10 = 0; + ctx.sigctx.r8 = 0; + ctx.sigctx.r9 = 0; +#elif defined(__aarch64__) + ctx.sigctx.regs[8] = __NR_write; + ctx.sigctx.regs[0] = p[1]; + ctx.sigctx.regs[1] = (unsigned long) &test_val; + ctx.sigctx.regs[2] = sizeof(test_val); + ctx.sigctx.regs[3] = 0; + ctx.sigctx.regs[4] = 0; + ctx.sigctx.regs[5] = 0; +#endif + sigmask = 0xffffffff; + ret = syscall(__NR_process_vm_exec, pid, &ctx, PROCESS_VM_EXEC_SYSCALL, + &siginfo, &sigmask, 8); + if (ret != 0) + return pr_perror("process_vm_exec"); + if (siginfo.si_signo != 0) + return pr_fail("unexpected signal: %d", siginfo.si_signo); +#if defined(__x86_64__) + if (ctx.sigctx.rax != sizeof(test_val)) + pr_fail("unexpected rax: %lx", ctx.sigctx.rax); +#elif defined(__aarch64__) + if (ctx.sigctx.regs[0] != sizeof(test_val)) + pr_fail("unexpected r0: %lx", ctx.sigctx.regs[0]); +#endif + if (kill(pid, SIGKILL)) + return pr_perror("kill"); + if (wait(NULL) != pid) + return pr_perror("kill"); + if (read(p[0], &val, sizeof(val)) != sizeof(val)) + pr_perror("read"); + if (val != TEST_VAL) + pr_fail("unexpected data: %x", val); + ksft_test_result_pass("process_vm_exec(..., PROCESS_VM_EXEC_SYSCALL, ...) \n"); + ksft_exit_pass(); + return 0; +}
diff --git a/tools/testing/selftests/process_vm_exec/ptrace_vm_exec.c b/tools/testing/selftests/process_vm_exec/ptrace_vm_exec.c new file mode 100644 index 0000000..b322214 --- /dev/null +++ b/tools/testing/selftests/process_vm_exec/ptrace_vm_exec.c
@@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include <sys/ptrace.h> +#include <stdlib.h> +#include <unistd.h> +#include <linux/unistd.h> +#include <stdio.h> +#include <sys/user.h> +#include <sys/uio.h> +#include <time.h> +#include <linux/elf.h> + +#include "../kselftest.h" +#include "log.h" + +#ifndef PTRACE_SYSEMU +#define PTRACE_SYSEMU 31 +#endif + +#if defined(__x86_64__) +static inline long __syscall1(long n, long a1) +{ + unsigned long ret; + + __asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1) : "rcx", "r11", "memory"); + + return ret; +} +#elif defined(__aarch64__) +#define __asm_syscall(...) do { \ + __asm__ __volatile__ ( "svc 0" \ + : "=r"(x0) : __VA_ARGS__ : "memory", "cc"); \ + return x0; \ + } while (0) + + +static inline long __syscall1(long n, long a) +{ + register long x8 __asm__("x8") = n; + register long x0 __asm__("x0") = a; + __asm_syscall("r"(x8), "0"(x0)); +} +#endif + +#define TEST_SYSCALL 444 +#define TEST_SYSCALL_RET 555 +#define TEST_MARKER 789 +#define TEST_TIMEOUT 5 + +static int marker; + +static void guest(void) +{ + while (1) { + int ret; + + ret = __syscall1(TEST_SYSCALL, marker); + if (ret != TEST_SYSCALL_RET) + abort(); + } +} + +int main(int argc, char **argv) +{ + struct user_regs_struct regs = {}; + struct iovec iov; + iov.iov_base = ®s; + iov.iov_len = sizeof(regs); + + + struct timespec start, cur; + int status; + long sysnr; + pid_t pid; + + ksft_set_plan(1); + + pid = fork(); + if (pid == 0) { + marker = TEST_MARKER; + kill(getpid(), SIGSTOP); + /* unreachable */ + abort(); + return 0; + } + + if (waitpid(pid, &status, WUNTRACED) != pid) + return pr_perror("waidpid"); + if (ptrace(PTRACE_ATTACH, pid, 0, 0)) + return pr_perror("PTRACE_ATTACH"); + if (wait(&status) != pid) + return pr_perror("waidpid"); + if (ptrace(PTRACE_CONT, pid, 0, 0)) + return pr_perror("PTRACE_CONT"); + if (waitpid(pid, &status, 0) != pid) + return pr_perror("waidpid"); + + if (ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_EXITKILL)) + return pr_perror("PTRACE_SETOPTIONS"); + if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov) == -1) + return pr_perror("PTRACE_GETREGS"); +#if defined(__x86_64__) + regs.rip = (long)guest; +#elif defined(__aarch64__) + regs.pc = (long)guest; +#endif + + clock_gettime(CLOCK_MONOTONIC, &start); + for (sysnr = 0; ; sysnr++) { + int status; + + clock_gettime(CLOCK_MONOTONIC, &cur); + if (start.tv_sec + TEST_TIMEOUT < cur.tv_sec || + (start.tv_sec + TEST_TIMEOUT == cur.tv_sec && + start.tv_nsec < cur.tv_nsec)) + break; + if (ptrace(PTRACE_SETREGSET, pid, (void*)NT_PRSTATUS, (void*)&iov) == -1) + return pr_perror("PTRACE_GETREGS"); + if (ptrace(PTRACE_SYSEMU, pid, 0, 0)) + return pr_perror("PTRACE_SYSEMU"); + if (waitpid(pid, &status, 0) != pid) + return pr_perror("waitpid"); + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) + return pr_err("unexpected status: %d", status); + if (ptrace(PTRACE_GETREGSET, pid, (void*)NT_PRSTATUS, (void*)&iov) == -1) + return pr_perror("PTRACE_GETREGS"); +#if defined(__x86_64__) + if (regs.rdi != TEST_MARKER) + return pr_err("unexpected marker: %d", regs.rdi); + if (regs.orig_rax != TEST_SYSCALL) + return pr_err("unexpected syscall: %d", regs.orig_rax); + regs.rax = TEST_SYSCALL_RET; +#elif defined(__aarch64__) + if (regs.regs[0] != TEST_MARKER) + return pr_err("unexpected marker"); + if (regs.regs[8] != TEST_SYSCALL) + return pr_err("unexpected syscall: %d", regs.regs[0]); + regs.regs[0] = TEST_SYSCALL_RET; +#endif + } + ksft_test_result_pass("%ld ns/syscall\n", 1000000000 / sysnr); + ksft_exit_pass(); + return 0; +}