.. | .. |
---|
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(); |
---|