// SPDX-License-Identifier: GPL-2.0-only /* * vsyscall_lockout.c - check that disabling vsyscall works * Copyright (C) 2021 Red Hat, Inc. */ #include #include #include #include #include #include #include #ifndef ARCH_VSYSCALL_LOCKOUT #define ARCH_VSYSCALL_LOCKOUT 0x5001 #elif ARCH_VSYSCALL_LOCKOUT != 0x5001 #error wrong vlaue for ARCH_VSYSCALL_LOCKOUT #endif static inline long syscall0(int nr) { unsigned long result; __asm__ volatile ("syscall" : "=a" (result) : "0" (nr) : "memory", "cc", "r11", "cx"); return result; } static inline long syscall1(int nr, long arg0) { register long rdi __asm__ ("rdi") = arg0; unsigned long result; __asm__ volatile ("syscall" : "=a" (result) : "0" (nr), "r" (rdi) : "memory", "cc", "r11", "cx"); return result; } static inline long syscall2(int nr, long arg0, long arg1) { register long rdi __asm__ ("rdi") = arg0; register long rsi __asm__ ("rsi") = arg1; unsigned long result; __asm__ volatile ("syscall" : "=a" (result) : "0" (nr), "r" (rdi), "r" (rsi) : "memory", "cc", "r11", "cx"); return result; } static inline long syscall3(int nr, long arg0, long arg1, long arg2) { register long rdi __asm__ ("rdi") = arg0; register long rsi __asm__ ("rsi") = arg1; register long rdx __asm__ ("rdx") = arg2; unsigned long result; __asm__ volatile ("syscall" : "=a" (result) : "0" (nr), "r" (rdi), "r" (rsi), "r" (rdx) : "memory", "cc", "r11", "cx"); return result; } static inline long syscall4(int nr, long arg0, long arg1, long arg2, long arg3) { register long rdi __asm__ ("rdi") = arg0; register long rsi __asm__ ("rsi") = arg1; register long rdx __asm__ ("rdx") = arg2; register long r10 __asm__ ("r10") = arg2; unsigned long result; __asm__ volatile ("syscall" : "=a" (result) : "0" (nr), "r" (rdi), "r" (rsi), "r" (rdx), "r" (r10) : "memory", "cc", "r11", "cx"); return result; } static inline long vsyscall1(long addr, long arg0) { register long rdi __asm__ ("rdi") = arg0; unsigned long result; __asm__ volatile ("callq *%%rax" : "=a" (result) : "0" (addr), "r" (rdi) : "memory", "cc", "r11", "cx"); return result; } static void __attribute__ ((noreturn)) sys_exit(int status) { syscall1(__NR_exit, status); __builtin_unreachable(); } static void sigabrt(void) { syscall2(__NR_kill, syscall0(__NR_getpid), SIGABRT); } static void print_char(char byte) { if (syscall3(__NR_write, 1L, (long) &byte, 1L) < 0) sigabrt(); } static void print_string(const char *p) { while (*p) { print_char(*p); ++p; } } static void print_dec_1(unsigned long val) { if (val != 0) { print_dec_1(val / 10); print_char('0' + (val % 10)); } } static void print_dec(unsigned long val) { if (val == 0) print_char('0'); else print_dec_1(val); } static void print_signed_dec(long val) { if (val < 0) { print_char('-'); print_dec(-(unsigned long)val); } else print_dec(val); } static void print_time(const char *label, struct timeval tv) { print_string(label); print_string(": "); print_dec(tv.tv_sec); print_char(' '); print_dec(tv.tv_usec); print_char('\n'); } static void print_failure(const char *label, long ret) { print_string("error: "); print_string(label); print_string(" failed: "); print_signed_dec(ret); print_char('\n'); } static void xgettimeofday(struct timeval *tv) { long ret = syscall1(__NR_gettimeofday, (long) tv); if (ret != 0) { print_failure("gettimeofday", ret); sigabrt(); } } static void xvgettimeofday(struct timeval *tv) { long ret = vsyscall1(VSYSCALL_ADDR, (long) tv); if (ret) { print_failure("vgettimeofday", ret); sigabrt(); } } static int sys_arch_prctl(int code, unsigned long addr) { return syscall2(__NR_arch_prctl, code, addr); } static __kernel_pid_t xfork(void) { long ret = syscall0(__NR_fork); if (ret < 0) { print_failure("fork", ret); sigabrt(); } return ret; } static void xexecve(const char *pathname, char **argv, char **envp) { long ret; ret = syscall3(__NR_execve, (long) pathname, (long) argv, (long) envp); print_failure("execve", ret); sigabrt(); } static __kernel_pid_t xwaitpid(__kernel_pid_t pid, int *status, int options) { long ret = syscall4(__NR_wait4, pid, (long) status, options, 0); if (ret < 0) { print_failure("wait4", ret); sigabrt(); } return ret; } static int do_lockout(void) { long ret = sys_arch_prctl(ARCH_VSYSCALL_LOCKOUT, 0); if (ret < 0) print_failure("arch_prctl(ARCH_VSYSCALL_LOCKOUT)", ret); return ret; } static long difftime(struct timeval first, struct timeval second) { return second.tv_usec - first.tv_usec + (second.tv_sec - first.tv_sec) * 1000 * 1000; } /* * Second stage: Check that the lockout is not inherited across execve. */ static int main_2(void) { struct timeval vsyscall_time = { -1, -1 }; int status = 0; xvgettimeofday(&vsyscall_time); print_time("vsyscall gettimeofday after fork", vsyscall_time); if (vsyscall_time.tv_sec < 0 || vsyscall_time.tv_usec < 0) status = 1; return status; } static void check_lockout_after_fork(int *status, int twice) { __kernel_pid_t pid; struct timeval vsyscall_time; int wstatus; if (twice) { __kernel_pid_t pid_outer; print_string("checking that lockout is inherited by fork\n"); pid_outer = xfork(); if (pid_outer == 0) { if (do_lockout()) sys_exit(1); /* * Logic for the subprocess follows below. */ } else { xwaitpid(pid_outer, &wstatus, 0); if (wstatus != 0) { print_string("error: unexpected exit status: "); print_signed_dec(wstatus); print_char('\n'); *status = 1; } return; } } else print_string("checking that lockout works after one fork\n"); pid = xfork(); if (pid == 0) { if (!twice && do_lockout()) sys_exit(1); /* * This should trigger a fault. */ xvgettimeofday(&vsyscall_time); sys_exit(0); } xwaitpid(pid, &wstatus, 0); switch (wstatus) { case 0: print_string("error: no crash after lockout\n"); *status = 1; break; case 0x0100: *status = 1; break; case SIGSEGV: print_string("termination after lockout\n"); break; default: print_string("error: unexpected exit status: "); print_signed_dec(wstatus); print_char('\n'); *status = 1; } if (twice) sys_exit(*status); /* * Status in the parent process should be unaffected. */ xvgettimeofday(&vsyscall_time); } static void check_no_lockout_after_execve(char **argv, int *status) { __kernel_pid_t pid; int wstatus; print_string("checking that lockout is not inherited by execve\n"); pid = xfork(); if (pid == 0) { struct timeval vsyscall_time; char *new_argv[] = { argv[0], "2", NULL }; xvgettimeofday(&vsyscall_time); if (do_lockout()) sys_exit(1); /* * Re-exec the second stage. See main_2 above. */ xexecve(argv[0], new_argv, new_argv + 2); } xwaitpid(pid, &wstatus, 0); if (wstatus != 0) { print_string("error: unexpected exit status: "); print_signed_dec(wstatus); print_char('\n'); *status = 1; } } static int main(int argc, char **argv) { struct timeval initial_time = { -1, -1 }; struct timeval vsyscall_time = { -1, -1 }; struct timeval final_time = { -1, -1 }; long vsyscall_diff, final_diff; int status = 0; if (argc > 1) switch (*argv[1]) { case '2': return main_2(); default: print_string("usage: "); print_string(argv[0]); print_string("\n"); return 1; } xgettimeofday(&initial_time); xvgettimeofday(&vsyscall_time); xgettimeofday(&final_time); vsyscall_diff = difftime(initial_time, vsyscall_time); final_diff = difftime(vsyscall_time, final_time); print_time("initial gettimeofday", initial_time); print_time("vsyscall gettimeofday", vsyscall_time); print_time("final gettimeofday", final_time); if (initial_time.tv_sec < 0 || initial_time.tv_usec < 0 || vsyscall_time.tv_sec < 0 || vsyscall_time.tv_usec < 0 || final_time.tv_sec < 0 || final_time.tv_usec < 0) { print_string("error: negative time\n"); status = 1; } print_string("differences: "); print_signed_dec(vsyscall_diff); print_char(' '); print_signed_dec(final_diff); print_char('\n'); if (vsyscall_diff < 0 || final_diff < 0) { /* * This may produce false positives if there is an active NTP. */ print_string("error: time went backwards\n"); status = 1; } check_lockout_after_fork(&status, 0); check_lockout_after_fork(&status, 1); check_no_lockout_after_execve(argv, &status); print_string("testing done, exit status: "); print_signed_dec(status); print_char('\n'); return status; } static void __attribute__ ((used)) main_trampoline(long *rsp) { sys_exit(main(*rsp, (char **) (rsp + 1))); } __asm__ (".text\n\t" ".globl _start\n" "_start:\n\t" ".cfi_startproc\n\t" ".cfi_undefined rip\n\t" "movq %rsp, %rdi\n\t" "callq main_trampoline\n\t" /* Results in psABI %rsp alignment. */ ".cfi_endproc\n\t" ".type _start, @function\n\t" ".size _start, . - _start\n\t" ".previous");