| .. | .. |
|---|
| 18 | 18 | #include <sched.h> |
|---|
| 19 | 19 | #include <stdbool.h> |
|---|
| 20 | 20 | #include <setjmp.h> |
|---|
| 21 | +#include <sys/uio.h> |
|---|
| 22 | + |
|---|
| 23 | +#include "helpers.h" |
|---|
| 21 | 24 | |
|---|
| 22 | 25 | #ifdef __x86_64__ |
|---|
| 23 | 26 | # define VSYS(x) (x) |
|---|
| .. | .. |
|---|
| 49 | 52 | } |
|---|
| 50 | 53 | |
|---|
| 51 | 54 | /* vsyscalls and vDSO */ |
|---|
| 52 | | -bool should_read_vsyscall = false; |
|---|
| 55 | +bool vsyscall_map_r = false, vsyscall_map_x = false; |
|---|
| 53 | 56 | |
|---|
| 54 | 57 | typedef long (*gtod_t)(struct timeval *tv, struct timezone *tz); |
|---|
| 55 | | -gtod_t vgtod = (gtod_t)VSYS(0xffffffffff600000); |
|---|
| 58 | +const gtod_t vgtod = (gtod_t)VSYS(0xffffffffff600000); |
|---|
| 56 | 59 | gtod_t vdso_gtod; |
|---|
| 57 | 60 | |
|---|
| 58 | 61 | typedef int (*vgettime_t)(clockid_t, struct timespec *); |
|---|
| 59 | 62 | vgettime_t vdso_gettime; |
|---|
| 60 | 63 | |
|---|
| 61 | 64 | typedef long (*time_func_t)(time_t *t); |
|---|
| 62 | | -time_func_t vtime = (time_func_t)VSYS(0xffffffffff600400); |
|---|
| 65 | +const time_func_t vtime = (time_func_t)VSYS(0xffffffffff600400); |
|---|
| 63 | 66 | time_func_t vdso_time; |
|---|
| 64 | 67 | |
|---|
| 65 | 68 | typedef long (*getcpu_t)(unsigned *, unsigned *, void *); |
|---|
| 66 | | -getcpu_t vgetcpu = (getcpu_t)VSYS(0xffffffffff600800); |
|---|
| 69 | +const getcpu_t vgetcpu = (getcpu_t)VSYS(0xffffffffff600800); |
|---|
| 67 | 70 | getcpu_t vdso_getcpu; |
|---|
| 68 | 71 | |
|---|
| 69 | 72 | static void init_vdso(void) |
|---|
| .. | .. |
|---|
| 107 | 110 | maps = fopen("/proc/self/maps", "r"); |
|---|
| 108 | 111 | if (!maps) { |
|---|
| 109 | 112 | printf("[WARN]\tCould not open /proc/self/maps -- assuming vsyscall is r-x\n"); |
|---|
| 110 | | - should_read_vsyscall = true; |
|---|
| 113 | + vsyscall_map_r = true; |
|---|
| 111 | 114 | return 0; |
|---|
| 112 | 115 | } |
|---|
| 113 | 116 | |
|---|
| .. | .. |
|---|
| 133 | 136 | } |
|---|
| 134 | 137 | |
|---|
| 135 | 138 | printf("\tvsyscall permissions are %c-%c\n", r, x); |
|---|
| 136 | | - should_read_vsyscall = (r == 'r'); |
|---|
| 137 | | - if (x != 'x') { |
|---|
| 138 | | - vgtod = NULL; |
|---|
| 139 | | - vtime = NULL; |
|---|
| 140 | | - vgetcpu = NULL; |
|---|
| 141 | | - } |
|---|
| 139 | + vsyscall_map_r = (r == 'r'); |
|---|
| 140 | + vsyscall_map_x = (x == 'x'); |
|---|
| 142 | 141 | |
|---|
| 143 | 142 | found = true; |
|---|
| 144 | 143 | break; |
|---|
| .. | .. |
|---|
| 148 | 147 | |
|---|
| 149 | 148 | if (!found) { |
|---|
| 150 | 149 | printf("\tno vsyscall map in /proc/self/maps\n"); |
|---|
| 151 | | - should_read_vsyscall = false; |
|---|
| 152 | | - vgtod = NULL; |
|---|
| 153 | | - vtime = NULL; |
|---|
| 154 | | - vgetcpu = NULL; |
|---|
| 150 | + vsyscall_map_r = false; |
|---|
| 151 | + vsyscall_map_x = false; |
|---|
| 155 | 152 | } |
|---|
| 156 | 153 | |
|---|
| 157 | 154 | return nerrs; |
|---|
| .. | .. |
|---|
| 183 | 180 | } |
|---|
| 184 | 181 | |
|---|
| 185 | 182 | static jmp_buf jmpbuf; |
|---|
| 183 | +static volatile unsigned long segv_err; |
|---|
| 186 | 184 | |
|---|
| 187 | 185 | static void sigsegv(int sig, siginfo_t *info, void *ctx_void) |
|---|
| 188 | 186 | { |
|---|
| 187 | + ucontext_t *ctx = (ucontext_t *)ctx_void; |
|---|
| 188 | + |
|---|
| 189 | + segv_err = ctx->uc_mcontext.gregs[REG_ERR]; |
|---|
| 189 | 190 | siglongjmp(jmpbuf, 1); |
|---|
| 190 | 191 | } |
|---|
| 191 | 192 | |
|---|
| .. | .. |
|---|
| 238 | 239 | err(1, "syscall gettimeofday"); |
|---|
| 239 | 240 | if (vdso_gtod) |
|---|
| 240 | 241 | ret_vdso = vdso_gtod(&tv_vdso, &tz_vdso); |
|---|
| 241 | | - if (vgtod) |
|---|
| 242 | + if (vsyscall_map_x) |
|---|
| 242 | 243 | ret_vsys = vgtod(&tv_vsys, &tz_vsys); |
|---|
| 243 | 244 | if (sys_gtod(&tv_sys2, &tz_sys) != 0) |
|---|
| 244 | 245 | err(1, "syscall gettimeofday"); |
|---|
| .. | .. |
|---|
| 252 | 253 | } |
|---|
| 253 | 254 | } |
|---|
| 254 | 255 | |
|---|
| 255 | | - if (vgtod) { |
|---|
| 256 | + if (vsyscall_map_x) { |
|---|
| 256 | 257 | if (ret_vsys == 0) { |
|---|
| 257 | 258 | nerrs += check_gtod(&tv_sys1, &tv_sys2, &tz_sys, "vsyscall", &tv_vsys, &tz_vsys); |
|---|
| 258 | 259 | } else { |
|---|
| .. | .. |
|---|
| 273 | 274 | t_sys1 = sys_time(&t2_sys1); |
|---|
| 274 | 275 | if (vdso_time) |
|---|
| 275 | 276 | t_vdso = vdso_time(&t2_vdso); |
|---|
| 276 | | - if (vtime) |
|---|
| 277 | + if (vsyscall_map_x) |
|---|
| 277 | 278 | t_vsys = vtime(&t2_vsys); |
|---|
| 278 | 279 | t_sys2 = sys_time(&t2_sys2); |
|---|
| 279 | 280 | if (t_sys1 < 0 || t_sys1 != t2_sys1 || t_sys2 < 0 || t_sys2 != t2_sys2) { |
|---|
| .. | .. |
|---|
| 294 | 295 | } |
|---|
| 295 | 296 | } |
|---|
| 296 | 297 | |
|---|
| 297 | | - if (vtime) { |
|---|
| 298 | + if (vsyscall_map_x) { |
|---|
| 298 | 299 | if (t_vsys < 0 || t_vsys != t2_vsys) { |
|---|
| 299 | 300 | printf("[FAIL]\tvsyscall failed (ret:%ld output:%ld)\n", t_vsys, t2_vsys); |
|---|
| 300 | 301 | nerrs++; |
|---|
| .. | .. |
|---|
| 330 | 331 | ret_sys = sys_getcpu(&cpu_sys, &node_sys, 0); |
|---|
| 331 | 332 | if (vdso_getcpu) |
|---|
| 332 | 333 | ret_vdso = vdso_getcpu(&cpu_vdso, &node_vdso, 0); |
|---|
| 333 | | - if (vgetcpu) |
|---|
| 334 | + if (vsyscall_map_x) |
|---|
| 334 | 335 | ret_vsys = vgetcpu(&cpu_vsys, &node_vsys, 0); |
|---|
| 335 | 336 | |
|---|
| 336 | 337 | if (ret_sys == 0) { |
|---|
| .. | .. |
|---|
| 369 | 370 | } |
|---|
| 370 | 371 | } |
|---|
| 371 | 372 | |
|---|
| 372 | | - if (vgetcpu) { |
|---|
| 373 | + if (vsyscall_map_x) { |
|---|
| 373 | 374 | if (ret_vsys) { |
|---|
| 374 | 375 | printf("[FAIL]\tvsyscall getcpu() failed\n"); |
|---|
| 375 | 376 | nerrs++; |
|---|
| .. | .. |
|---|
| 410 | 411 | can_read = false; |
|---|
| 411 | 412 | } |
|---|
| 412 | 413 | |
|---|
| 413 | | - if (can_read && !should_read_vsyscall) { |
|---|
| 414 | + if (can_read && !vsyscall_map_r) { |
|---|
| 414 | 415 | printf("[FAIL]\tWe have read access, but we shouldn't\n"); |
|---|
| 415 | 416 | return 1; |
|---|
| 416 | | - } else if (!can_read && should_read_vsyscall) { |
|---|
| 417 | + } else if (!can_read && vsyscall_map_r) { |
|---|
| 417 | 418 | printf("[FAIL]\tWe don't have read access, but we should\n"); |
|---|
| 418 | 419 | return 1; |
|---|
| 420 | + } else if (can_read) { |
|---|
| 421 | + printf("[OK]\tWe have read access\n"); |
|---|
| 419 | 422 | } else { |
|---|
| 420 | | - printf("[OK]\tgot expected result\n"); |
|---|
| 423 | + printf("[OK]\tWe do not have read access: #PF(0x%lx)\n", |
|---|
| 424 | + segv_err); |
|---|
| 421 | 425 | } |
|---|
| 422 | 426 | #endif |
|---|
| 423 | 427 | |
|---|
| 424 | 428 | return 0; |
|---|
| 425 | 429 | } |
|---|
| 426 | 430 | |
|---|
| 431 | +static int test_vsys_x(void) |
|---|
| 432 | +{ |
|---|
| 433 | +#ifdef __x86_64__ |
|---|
| 434 | + if (vsyscall_map_x) { |
|---|
| 435 | + /* We already tested this adequately. */ |
|---|
| 436 | + return 0; |
|---|
| 437 | + } |
|---|
| 438 | + |
|---|
| 439 | + printf("[RUN]\tMake sure that vsyscalls really page fault\n"); |
|---|
| 440 | + |
|---|
| 441 | + bool can_exec; |
|---|
| 442 | + if (sigsetjmp(jmpbuf, 1) == 0) { |
|---|
| 443 | + vgtod(NULL, NULL); |
|---|
| 444 | + can_exec = true; |
|---|
| 445 | + } else { |
|---|
| 446 | + can_exec = false; |
|---|
| 447 | + } |
|---|
| 448 | + |
|---|
| 449 | + if (can_exec) { |
|---|
| 450 | + printf("[FAIL]\tExecuting the vsyscall did not page fault\n"); |
|---|
| 451 | + return 1; |
|---|
| 452 | + } else if (segv_err & (1 << 4)) { /* INSTR */ |
|---|
| 453 | + printf("[OK]\tExecuting the vsyscall page failed: #PF(0x%lx)\n", |
|---|
| 454 | + segv_err); |
|---|
| 455 | + } else { |
|---|
| 456 | + printf("[FAIL]\tExecution failed with the wrong error: #PF(0x%lx)\n", |
|---|
| 457 | + segv_err); |
|---|
| 458 | + return 1; |
|---|
| 459 | + } |
|---|
| 460 | +#endif |
|---|
| 461 | + |
|---|
| 462 | + return 0; |
|---|
| 463 | +} |
|---|
| 464 | + |
|---|
| 465 | +/* |
|---|
| 466 | + * Debuggers expect ptrace() to be able to peek at the vsyscall page. |
|---|
| 467 | + * Use process_vm_readv() as a proxy for ptrace() to test this. We |
|---|
| 468 | + * want it to work in the vsyscall=emulate case and to fail in the |
|---|
| 469 | + * vsyscall=xonly case. |
|---|
| 470 | + * |
|---|
| 471 | + * It's worth noting that this ABI is a bit nutty. write(2) can't |
|---|
| 472 | + * read from the vsyscall page on any kernel version or mode. The |
|---|
| 473 | + * fact that ptrace() ever worked was a nice courtesy of old kernels, |
|---|
| 474 | + * but the code to support it is fairly gross. |
|---|
| 475 | + */ |
|---|
| 476 | +static int test_process_vm_readv(void) |
|---|
| 477 | +{ |
|---|
| 478 | +#ifdef __x86_64__ |
|---|
| 479 | + char buf[4096]; |
|---|
| 480 | + struct iovec local, remote; |
|---|
| 481 | + int ret; |
|---|
| 482 | + |
|---|
| 483 | + printf("[RUN]\tprocess_vm_readv() from vsyscall page\n"); |
|---|
| 484 | + |
|---|
| 485 | + local.iov_base = buf; |
|---|
| 486 | + local.iov_len = 4096; |
|---|
| 487 | + remote.iov_base = (void *)0xffffffffff600000; |
|---|
| 488 | + remote.iov_len = 4096; |
|---|
| 489 | + ret = process_vm_readv(getpid(), &local, 1, &remote, 1, 0); |
|---|
| 490 | + if (ret != 4096) { |
|---|
| 491 | + /* |
|---|
| 492 | + * We expect process_vm_readv() to work if and only if the |
|---|
| 493 | + * vsyscall page is readable. |
|---|
| 494 | + */ |
|---|
| 495 | + printf("[%s]\tprocess_vm_readv() failed (ret = %d, errno = %d)\n", vsyscall_map_r ? "FAIL" : "OK", ret, errno); |
|---|
| 496 | + return vsyscall_map_r ? 1 : 0; |
|---|
| 497 | + } |
|---|
| 498 | + |
|---|
| 499 | + if (vsyscall_map_r) { |
|---|
| 500 | + if (!memcmp(buf, remote.iov_base, sizeof(buf))) { |
|---|
| 501 | + printf("[OK]\tIt worked and read correct data\n"); |
|---|
| 502 | + } else { |
|---|
| 503 | + printf("[FAIL]\tIt worked but returned incorrect data\n"); |
|---|
| 504 | + return 1; |
|---|
| 505 | + } |
|---|
| 506 | + } else { |
|---|
| 507 | + printf("[FAIL]\tprocess_rm_readv() succeeded, but it should have failed in this configuration\n"); |
|---|
| 508 | + return 1; |
|---|
| 509 | + } |
|---|
| 510 | +#endif |
|---|
| 511 | + |
|---|
| 512 | + return 0; |
|---|
| 513 | +} |
|---|
| 427 | 514 | |
|---|
| 428 | 515 | #ifdef __x86_64__ |
|---|
| 429 | | -#define X86_EFLAGS_TF (1UL << 8) |
|---|
| 430 | 516 | static volatile sig_atomic_t num_vsyscall_traps; |
|---|
| 431 | | - |
|---|
| 432 | | -static unsigned long get_eflags(void) |
|---|
| 433 | | -{ |
|---|
| 434 | | - unsigned long eflags; |
|---|
| 435 | | - asm volatile ("pushfq\n\tpopq %0" : "=rm" (eflags)); |
|---|
| 436 | | - return eflags; |
|---|
| 437 | | -} |
|---|
| 438 | | - |
|---|
| 439 | | -static void set_eflags(unsigned long eflags) |
|---|
| 440 | | -{ |
|---|
| 441 | | - asm volatile ("pushq %0\n\tpopfq" : : "rm" (eflags) : "flags"); |
|---|
| 442 | | -} |
|---|
| 443 | 517 | |
|---|
| 444 | 518 | static void sigtrap(int sig, siginfo_t *info, void *ctx_void) |
|---|
| 445 | 519 | { |
|---|
| .. | .. |
|---|
| 455 | 529 | time_t tmp; |
|---|
| 456 | 530 | bool is_native; |
|---|
| 457 | 531 | |
|---|
| 458 | | - if (!vtime) |
|---|
| 532 | + if (!vsyscall_map_x) |
|---|
| 459 | 533 | return 0; |
|---|
| 460 | 534 | |
|---|
| 461 | 535 | printf("[RUN]\tchecking that vsyscalls are emulated\n"); |
|---|
| .. | .. |
|---|
| 497 | 571 | |
|---|
| 498 | 572 | sethandler(SIGSEGV, sigsegv, 0); |
|---|
| 499 | 573 | nerrs += test_vsys_r(); |
|---|
| 574 | + nerrs += test_vsys_x(); |
|---|
| 575 | + |
|---|
| 576 | + nerrs += test_process_vm_readv(); |
|---|
| 500 | 577 | |
|---|
| 501 | 578 | #ifdef __x86_64__ |
|---|
| 502 | 579 | nerrs += test_emulation(); |
|---|