This is simple test to determine if arch_prctl(ARCH_SET_COMPAT) is working by ptracing switched application with PTRACE_GETREGS - it should return 32-bit registers set. Cc: Cyrill Gorcunov <gorcunov@xxxxxxxxxx> Cc: Pavel Emelyanov <xemul@xxxxxxxxxxxxx> Cc: Konstantin Khorenko <khorenko@xxxxxxxxxxxxx> CC: Dmitry Safonov <0x7f454c46@xxxxxxxxx> Signed-off-by: Dmitry Safonov <dsafonov@xxxxxxxxxxxxx> --- tools/testing/selftests/x86/Makefile | 1 + .../testing/selftests/x86/arch_prctl_set_compat.c | 295 +++++++++++++++++++++ 2 files changed, 296 insertions(+) create mode 100644 tools/testing/selftests/x86/arch_prctl_set_compat.c diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile index b47ebd170690..bdadd12698f4 100644 --- a/tools/testing/selftests/x86/Makefile +++ b/tools/testing/selftests/x86/Makefile @@ -9,6 +9,7 @@ TARGETS_C_BOTHBITS := single_step_syscall sysret_ss_attrs syscall_nt ptrace_sysc TARGETS_C_32BIT_ONLY := entry_from_vm86 syscall_arg_fault test_syscall_vdso unwind_vdso \ test_FCMOV test_FCOMI test_FISTTP \ vdso_restorer +TARGETS_C_64BIT_ONLY := arch_prctl_set_compat TARGETS_C_32BIT_ALL := $(TARGETS_C_BOTHBITS) $(TARGETS_C_32BIT_ONLY) TARGETS_C_64BIT_ALL := $(TARGETS_C_BOTHBITS) $(TARGETS_C_64BIT_ONLY) diff --git a/tools/testing/selftests/x86/arch_prctl_set_compat.c b/tools/testing/selftests/x86/arch_prctl_set_compat.c new file mode 100644 index 000000000000..326a46d4b056 --- /dev/null +++ b/tools/testing/selftests/x86/arch_prctl_set_compat.c @@ -0,0 +1,295 @@ +/* + * arch_prctl_set_compat.c - tests switching to compatible mode from 64-bit + * Copyright (c) 2016 Dmitry Safonov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * This switches to compatible mode with the help from arch_prctl friend. + * Switching is simple syscall, but one need unmap every vma that is + * higher than 32-bit TASK_SIZE, make raw 32/64-bit syscalls. + * So this is also a really good example. By the end tracee is + * compatible task that makes 32-bit syscalls to stop itself. + * For returning in some 32-bit code it may be handy to use sigreturn + * there with formed frame. + * + * Switching from 32-bit compatible application to native is just one + * arch_prctl syscall, so this is for harder task: switching from native to + * compat mode. + */ +#define _GNU_SOURCE + +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdio.h> +#include <signal.h> +#include <stdint.h> + +#include <sys/syscall.h> +#include <asm/prctl.h> +#include <sys/prctl.h> +#include <sys/personality.h> +#include <sys/mman.h> + +#include <sys/uio.h> +#include <sys/ptrace.h> +#include <linux/elf.h> +#include <sys/wait.h> + +#include <sys/stat.h> +#include <fcntl.h> + +#define __stringify_1(x...) #x +#define __stringify(x...) __stringify_1(x) + +#ifndef ARCH_SET_COMPAT +#define ARCH_SET_COMPAT 0x2001 +#define ARCH_SET_NATIVE 0x2002 +#define ARCH_GET_PERSONALITY 0x2003 +#endif + +#define PAGE_SIZE 4096 +#define TASK_SIZE_MAX ((1UL << 47) - PAGE_SIZE) +#define IA32_PAGE_OFFSET 0xFFFFe000 + +/* Just a typical random stack on x86_64 compatible task */ +#define STACK_START 0xffdb8000 +#define STACK_END 0xffdd9000 + +/* Some empty randoms inside compatible address space */ +#define ARG_START 0xf77c8000 +#define ARG_END 0xf77c8000 +#define ENV_START 0xf77c8000 +#define ENV_END 0xf77c8000 + +/* + * After removing all mappings higher than compatible TASK_SIZE, + * we remove libc mapping. That's the reason for plain syscalls + */ +#define __NR_munmap 11 +#define __NR_arch_prctl 158 + +#define __NR32_getpid 20 +#define __NR32_kill 37 + +/* unmaps everything above IA32_PAGE_OFFSET */ +static inline void unmap_uncompat_mappings(void) +{ + unsigned long addr = IA32_PAGE_OFFSET; + unsigned long len = TASK_SIZE_MAX - IA32_PAGE_OFFSET; + + asm volatile( + " movq $"__stringify(__NR_munmap)", %%rax\n" + " syscall\n" + : + : "D" (addr), "S" (len) + ); +} + +static inline void sys_arch_prctl(int code, unsigned long addr) +{ + asm volatile( + " movq $"__stringify(__NR_arch_prctl)", %%rax\n" + " syscall\n" + : + : "D" (code), "S" (addr) + ); +} + +static inline void +prctl_print(int opcode, unsigned long arg1, unsigned long arg2) +{ + long ret = syscall(SYS_prctl, opcode, arg1, arg2, 0, 0); + + if (ret) + fprintf(stderr, "[ERR]\tprctl failed with %ld : %m\n", ret); +} + +/* + * Runed in different task just for test purposes: + * tracer with the help of PTRACE_GETREGS will fetch it's registers set size + * and determine, if it's compatible task. + * Then tracer will kill tracee, sorry for it. + */ +void tracee_func(void) +{ + personality(PER_LINUX32); + + /* emptify arg & env, moving them to compatible address space */ + prctl_print(PR_SET_MM, PR_SET_MM_ARG_START, ARG_START); + prctl_print(PR_SET_MM, PR_SET_MM_ARG_END, ARG_END); + prctl_print(PR_SET_MM, PR_SET_MM_ENV_START, ENV_START); + prctl_print(PR_SET_MM, PR_SET_MM_ENV_END, ENV_END); + + /* stack: get a new one */ + if (mmap((void *)STACK_START, STACK_END - STACK_START, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0) + == MAP_FAILED) { + fprintf(stderr, "[ERR]\tfailed to mmap new stack : %m\n"); + } else { + prctl_print(PR_SET_MM, PR_SET_MM_START_STACK, STACK_START); + asm volatile ( + " mov %%rax,%%rsp\n" + : + : "a" (STACK_END)); + } + /* we are cool guys: we have our own stack */ + + unmap_uncompat_mappings(); + /* + * we are poor boys: unmapped everything with glibc, + * do not use it from now - we are on our own! + */ + + sys_arch_prctl(ARCH_SET_COMPAT, 0); + + /* Now switch to compatibility mode */ + asm volatile ( + " pushq $0x23\n" /* USER32_CS */ + " pushq $1f\n" + " lretq\n" + /* here we are: ready to execute some 32-bit code */ + "1:\n" + ".code32\n" + /* getpid() */ + " movl $"__stringify(__NR32_getpid)", %eax\n" + " int $0x80\n" + " movl %eax, %ebx\n" /* pid */ + /* raise SIGSTOP */ + " movl $"__stringify(__NR32_kill)", %eax\n" + " movl $19, %ecx\n" + " int $0x80\n" + ".code64\n" + ); + +} + +typedef struct { + uint64_t r15, r14, r13, r12, bp, bx, r11, r10, r9, r8; + uint64_t ax, cx, dx, si, di, orig_ax, ip, cs, flags; + uint64_t sp, ss, fs_base, gs_base, ds, es, fs, gs; +} user_regs_64; + +typedef struct { + uint32_t bx, cx, dx, si, di, bp, ax, ds, es, fs, gs; + uint32_t orig_ax, ip, cs, flags, sp, ss; +} user_regs_32; + +typedef union { + user_regs_64 native; + user_regs_32 compat; +} user_regs_struct_t; + +int ptrace_task_compatible(pid_t pid) +{ + struct iovec iov; + user_regs_struct_t r; + size_t reg_size = sizeof(user_regs_64); + + iov.iov_base = &r; + iov.iov_len = reg_size; + + errno = 0; + if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov)) { + fprintf(stderr, "[NOTE]\tCan't get register set: PTRACE_GETREGSET failed for pid %d : %m\n", + pid); + return 0; + } + + return iov.iov_len == sizeof(user_regs_32); +} + +void dump_proc_maps(pid_t pid) +{ +#define BUF_SIZE 1024 + char buf[BUF_SIZE]; + int fd; + size_t nread; + + snprintf(buf, BUF_SIZE, "/proc/%d/maps", pid); + fd = open(buf, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "[NOTE]\tCant open %s to dump : %s\n", buf); + return; + } + + while ((nread = read(fd, buf, sizeof(buf))) > 0) + fwrite(buf, 1, nread, stdout); + + close(fd); +} + +int main(int argc, char **argv) +{ + pid_t pid; + int ret = 0; + int status; + int in_compat = syscall(SYS_arch_prctl, 0x2003, 0); + int dump_maps = 0; + + if (in_compat < 0) { + fprintf(stderr, + "[ERR]\tSYS_arch_prctl returned %d : %m\n", in_compat); + } + + if (in_compat == 1) { + fprintf(stderr, "[SKIP]\tRun in 64-bit x86 userspace\n"); + return 0; + } + + if (argc > 1) + dump_maps = !(strcmp(argv[1], "--dump-proc")); + + if (dump_maps) + dump_proc_maps(getpid()); + + fflush(NULL); + pid = fork(); + if (pid < 0) { + fprintf(stderr, "[SKIP]\tCan't fork : %m\n"); + return 1; + } + + if (pid == 0) {/* child */ + ptrace(PTRACE_TRACEME, 0, 0, 0); + tracee_func(); + } + + /* parent, the tracer */ + waitpid(pid, &status, 0); + if (WIFEXITED(status)) { + fprintf(stderr, "[FAIL]\tTest was suddenly killed\n"); + return 2; + } + + if (WIFSIGNALED(status)) { + fprintf(stderr, "[FAIL]\tTest killed with signal %d\n", + WTERMSIG(status)); + return 3; + } + + if (!WIFSTOPPED(status)) + fprintf(stderr, "[NOTE]\twaitpid() returned, but tracee wasn't stopped\n"); + + if (!ptrace_task_compatible) { + fprintf(stderr, "[FAIL]\tTask didn't become compatible\n"); + ret = 4; + } + + if (dump_maps) + dump_proc_maps(pid); + + kill(pid, SIGKILL); + fprintf(stderr, "[OK]\tSuccessfuly changed mode to compatible\n"); + + return ret; +} + -- 2.7.4 -- To unsubscribe from this list: send the line "unsubscribe linux-kselftest" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html