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 Signed-off-by: Andrei Vagin <avagin@xxxxxxxxx> --- .../selftests/process_vm_exec/Makefile | 7 ++ tools/testing/selftests/process_vm_exec/log.h | 26 ++++ .../process_vm_exec/process_vm_exec.c | 105 +++++++++++++++++ .../process_vm_exec/process_vm_exec_fault.c | 111 ++++++++++++++++++ .../process_vm_exec/process_vm_exec_syscall.c | 81 +++++++++++++ .../process_vm_exec/ptrace_vm_exec.c | 111 ++++++++++++++++++ 6 files changed, 441 insertions(+) create mode 100644 tools/testing/selftests/process_vm_exec/Makefile create mode 100644 tools/testing/selftests/process_vm_exec/log.h create mode 100644 tools/testing/selftests/process_vm_exec/process_vm_exec.c create mode 100644 tools/testing/selftests/process_vm_exec/process_vm_exec_fault.c create mode 100644 tools/testing/selftests/process_vm_exec/process_vm_exec_syscall.c create mode 100644 tools/testing/selftests/process_vm_exec/ptrace_vm_exec.c diff --git a/tools/testing/selftests/process_vm_exec/Makefile b/tools/testing/selftests/process_vm_exec/Makefile new file mode 100644 index 000000000000..bdf7fcf0fdd3 --- /dev/null +++ b/tools/testing/selftests/process_vm_exec/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 + +UNAME_M := $(shell uname -m) +TEST_GEN_PROGS_x86_64 := process_vm_exec process_vm_exec_fault ptrace_vm_exec process_vm_exec_syscall +TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(UNAME_M)) + +include ../lib.mk 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 000000000000..ef268c2cf2b8 --- /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 000000000000..aa4009c43e01 --- /dev/null +++ b/tools/testing/selftests/process_vm_exec/process_vm_exec.c @@ -0,0 +1,105 @@ +// 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 "../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 + +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; +} + +int marker; + +static void guest(void) +{ + while (1) + if (__syscall1(TEST_SYSCALL, marker) != TEST_SYSCALL_RET) + abort(); +} + +int main(int argc, char **argv) +{ + struct sigcontext ctx = {}; + struct timespec start, cur; + int status, ret; + pid_t pid; + long sysnr; + void *stack; + + 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); + marker = TEST_MARKER; + kill(getpid(), SIGSTOP); + abort(); + return 0; + } + + ctx.rip = (long)guest; + ctx.rsp = (long)stack + TEST_STACK_SIZE; + ctx.cs = 0x33; + + 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); + ctx.rax = TEST_SYSCALL_RET; + 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 000000000000..b2c49095f386 --- /dev/null +++ b/tools/testing/selftests/process_vm_exec/process_vm_exec_fault.c @@ -0,0 +1,111 @@ +// 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 "../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; + +static inline void fault(unsigned long addr) +{ + unsigned long val = 0; + + __asm__ __volatile__ ( + "movq %%rcx, (%%rax)\n" + : + : "a"(addr), "c"(val) + :); +} + + +int marker; + +static void guest(void) +{ + unsigned long addr = 0; + + while (1) { + addr = (addr + 1) % 8; + fault(addr); + if (test_val != TEST_VAL) + _exit(1); + } +} + +int main(char argc, char **argv) +{ + siginfo_t siginfo; + unsigned long long sigmask = 0xffffffff; + struct sigcontext ctx = {}; + struct timespec start, cur; + unsigned long addr; + int status, ret; + 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); + marker = 789; + kill(getpid(), SIGSTOP); + abort(); + return 0; + } + + ctx.rip = (long)guest; + ctx.rsp = (long)stack + TEST_STACK_SIZE; + ctx.cs = 0x33; + + 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); + if (addr % 8 != ctx.rax) + return pr_fail("unexpected address: %lx", addr); + ctx.rax = (long)&test_val; + ctx.rcx = TEST_VAL; + 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_syscall.c b/tools/testing/selftests/process_vm_exec/process_vm_exec_syscall.c new file mode 100644 index 000000000000..c0a7f6ee5b1a --- /dev/null +++ b/tools/testing/selftests/process_vm_exec/process_vm_exec_syscall.c @@ -0,0 +1,81 @@ +// 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 "../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 sigcontext ctx = {}; + 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"); + + ctx.rax = __NR_write; + ctx.rdi = p[1]; + ctx.rsi = (unsigned long) &test_val; + ctx.rdx = sizeof(test_val); + ctx.r10 = 0; + ctx.r8 = 0; + ctx.r9 = 0; + 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 != SIGSYS) + return pr_fail("unexpected signal: %d", siginfo.si_signo); + if (ctx.rax != sizeof(test_val)) + pr_fail("unexpected rax: %lx", ctx.rax); + 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 000000000000..aac14c2e8f11 --- /dev/null +++ b/tools/testing/selftests/process_vm_exec/ptrace_vm_exec.c @@ -0,0 +1,111 @@ +// 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 "../kselftest.h" +#include "log.h" + +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; +} + +#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 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_GETREGS, pid, NULL, ®s)) + return pr_perror("PTRACE_SETREGS"); + regs.rip = (long)guest; + + 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_SETREGS, pid, NULL, ®s)) + return pr_perror("PTRACE_SETREGS"); + 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_GETREGS, pid, NULL, ®s)) + return pr_perror("PTRACE_GETREGS: %d", regs.rdi); + 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; + } + ksft_test_result_pass("%ld ns/syscall\n", 1000000000 / sysnr); + ksft_exit_pass(); + return 0; +} -- 2.29.2