staticunsignedlong read_base(enum which_base which)
{ unsignedlong offset; /* * Unless we have FSGSBASE, there's no direct way to do this from * user mode. We can get at it indirectly using signals, though.
*/
/* * If that didn't segfault, try the other end of the address space. * Unless we get really unlucky and run into the vsyscall page, this * is guaranteed to segfault.
*/
printf("[RUN]\tARCH_SET_GS to 0x%lx\n", value); if (syscall(SYS_arch_prctl, ARCH_SET_GS, value) != 0)
err(1, "ARCH_SET_GS");
asmvolatile ("mov %%gs, %0" : "=rm" (sel));
base = read_base(GS); if (base == value) {
printf("[OK]\tGSBASE was set as expected (selector 0x%hx)\n",
sel);
} else {
nerrs++;
printf("[FAIL]\tGSBASE was not as expected: got 0x%lx (selector 0x%hx)\n",
base, sel);
}
if (syscall(SYS_arch_prctl, ARCH_GET_GS, &base) != 0)
err(1, "ARCH_GET_GS"); if (base == value) {
printf("[OK]\tARCH_GET_GS worked as expected (selector 0x%hx)\n",
sel);
} else {
nerrs++;
printf("[FAIL]\tARCH_GET_GS was not as expected: got 0x%lx (selector 0x%hx)\n",
base, sel);
}
}
printf("[RUN]\tARCH_SET_GS to 0x%lx then mov 0 to %%gs%s\n", initial_base, schedule ? " and schedule " : ""); if (syscall(SYS_arch_prctl, ARCH_SET_GS, initial_base) != 0)
err(1, "ARCH_SET_GS");
if (schedule)
usleep(10);
asmvolatile ("mov %0, %%gs" : : "rm" (0));
base = read_base(GS); if (syscall(SYS_arch_prctl, ARCH_GET_GS, &arch_base) != 0)
err(1, "ARCH_GET_GS"); if (base == arch_base) {
printf("[OK]\tGSBASE is 0x%lx\n", base);
} else {
nerrs++;
printf("[FAIL]\tGSBASE changed to 0x%lx but kernel reports 0x%lx\n", base, arch_base);
}
}
/* * ARCH_SET_FS/GS(0) may or may not program a selector of zero. HARD_ZERO * means to force the selector to zero to improve test coverage.
*/ #define HARD_ZERO 0xa1fa5f343cb85fa4
if (syscall(SYS_arch_prctl, ARCH_SET_GS, to_set) != 0)
err(1, "ARCH_SET_GS");
if (hard_zero) asmvolatile ("mov %0, %%gs" : : "rm" ((unsignedshort)0));
unsignedshort sel; asmvolatile ("mov %%gs, %0" : "=rm" (sel));
printf("\tother thread: ARCH_SET_GS(0x%lx)%s -- sel is 0x%hx\n",
to_set, hard_zero ? " and clear gs" : "", sel);
}
static __thread int set_thread_area_entry_number = -1;
staticunsignedshort load_gs(void)
{ /* * Sets GS != 0 and GSBASE != 0 but arranges for the kernel to think * that GSBASE == 0 (i.e. thread.gsbase == 0).
*/
/* Step 1: tell the kernel that we have GSBASE == 0. */ if (syscall(SYS_arch_prctl, ARCH_SET_GS, 0) != 0)
err(1, "ARCH_SET_GS");
bool hard_zero = false; if (local == HARD_ZERO) {
hard_zero = true;
local = 0;
}
printf("[RUN]\tARCH_SET_GS(0x%lx)%s, then schedule to 0x%lx\n",
local, hard_zero ? " and clear gs" : "", remote); if (force_sel)
printf("\tBefore schedule, set selector to 0x%hx\n", force_sel); if (syscall(SYS_arch_prctl, ARCH_SET_GS, local) != 0)
err(1, "ARCH_SET_GS"); if (hard_zero) asmvolatile ("mov %0, %%gs" : : "rm" ((unsignedshort)0));
if (read_base(GS) != local) {
nerrs++;
printf("[FAIL]\tGSBASE wasn't set as expected\n");
}
if (force_sel) { asmvolatile ("mov %0, %%gs" : : "rm" (force_sel));
sel_pre_sched = force_sel;
local = read_base(GS);
/* * Signal delivery is quite likely to change a selector * of 1, 2, or 3 back to 0 due to IRET being defective.
*/ asmvolatile ("mov %0, %%gs" : : "rm" (force_sel));
} else { asmvolatile ("mov %%gs, %0" : "=rm" (sel_pre_sched));
}
asmvolatile ("mov %%gs, %0" : "=rm" (sel_post_sched));
base = read_base(GS); if (base == local && sel_pre_sched == sel_post_sched) {
printf("[OK]\tGS/BASE remained 0x%hx/0x%lx\n",
sel_pre_sched, local);
} elseif (base == local && sel_pre_sched >= 1 && sel_pre_sched <= 3 &&
sel_post_sched == 0) { /* * IRET is misdesigned and will squash selectors 1, 2, or 3 * to zero. Don't fail the test just because this happened.
*/
printf("[OK]\tGS/BASE changed from 0x%hx/0x%lx to 0x%hx/0x%lx because IRET is defective\n",
sel_pre_sched, local, sel_post_sched, base);
} else {
nerrs++;
printf("[FAIL]\tGS/BASE changed from 0x%hx/0x%lx to 0x%hx/0x%lx\n",
sel_pre_sched, local, sel_post_sched, base);
}
}
/* Read the initial base. It should be 1. */
base = ptrace(PTRACE_PEEKUSER, child, base_offset, NULL); if (base == 1) {
printf("[OK]\tGSBASE started at 1\n");
} else {
nerrs++;
printf("[FAIL]\tGSBASE started at 0x%lx\n", base);
}
printf("[RUN]\tSet GS = 0x7, read GSBASE\n");
/* Poke an LDT selector into GS. */ if (ptrace(PTRACE_POKEUSER, child, gs_offset, 0x7) != 0)
err(1, "PTRACE_POKEUSER");
/* And read the base. */
base = ptrace(PTRACE_PEEKUSER, child, base_offset, NULL);
if (base == 0 || base == 1) {
printf("[OK]\tGSBASE reads as 0x%lx with invalid GS\n", base);
} else {
nerrs++;
printf("[FAIL]\tGSBASE=0x%lx (should be 0 or 1)\n", base);
}
}
ptrace(PTRACE_CONT, child, NULL, NULL);
wait(&status); if (!WIFEXITED(status))
printf("[WARN]\tChild didn't exit cleanly.\n");
}
staticvoid test_ptrace_write_gsbase(void)
{ int status;
pid_t child = fork();
if (child < 0)
err(1, "fork");
if (child == 0) {
printf("[RUN]\tPTRACE_POKE(), write GSBASE from ptracer\n");
*shared_scratch = load_gs();
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0)
err(1, "PTRACE_TRACEME");
/* * In a non-FSGSBASE system, the nonzero selector will load * GSBASE (again). But what is tested here is whether the * selector value is changed or not by the GSBASE write in * a ptracer.
*/ if (gs != *shared_scratch) {
nerrs++;
printf("[FAIL]\tGS changed to %lx\n", gs);
/* * On older kernels, poking a nonzero value into the * base would zero the selector. On newer kernels, * this behavior has changed -- poking the base * changes only the base and, if FSGSBASE is not * available, this may have no effect once the tracee * is resumed.
*/ if (gs == 0)
printf("\tNote: this is expected behavior on older kernels.\n");
} elseif (have_fsgsbase && (base != 0xFF)) {
nerrs++;
printf("[FAIL]\tGSBASE changed to %lx\n", base);
} else {
printf("[OK]\tGS remained 0x%hx", *shared_scratch); if (have_fsgsbase)
printf(" and GSBASE changed to 0xFF");
printf("\n");
}
}
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset); if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0)
err(1, "sched_setaffinity to CPU 0"); /* should never fail */
if (pthread_create(&thread, 0, threadproc, 0) != 0)
err(1, "pthread_create");
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.