| #define _GNU_SOURCE |
| |
| #include <sys/time.h> |
| #include <time.h> |
| #include <stdlib.h> |
| #include <sys/syscall.h> |
| #include <unistd.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <inttypes.h> |
| #include <sys/mman.h> |
| #include <sys/signal.h> |
| #include <sys/ucontext.h> |
| #include <asm/ldt.h> |
| #include <err.h> |
| #include <setjmp.h> |
| #include <stddef.h> |
| #include <stdbool.h> |
| #include <sys/ptrace.h> |
| #include <sys/user.h> |
| |
| struct selectors { |
| short cs, gs, fs, ss; |
| }; |
| |
| static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *), |
| int flags) |
| { |
| struct sigaction sa; |
| memset(&sa, 0, sizeof(sa)); |
| sa.sa_sigaction = handler; |
| sa.sa_flags = SA_SIGINFO | flags; |
| sigemptyset(&sa.sa_mask); |
| if (sigaction(sig, &sa, 0)) |
| err(1, "sigaction"); |
| |
| } |
| |
| static unsigned char stack16[65536] __attribute__((aligned(4096))); |
| |
| asm (".pushsection .text\n\t" |
| ".type int3, @function\n\t" |
| ".align 4096\n\t" |
| "int3:\n\t" |
| "int3\n\t" |
| ".size int3, . - int3\n\t" |
| ".align 4096, 0xcc\n\t" |
| ".popsection"); |
| extern char int3[4096]; |
| |
| static void setup_ldt(void) |
| { |
| if ((unsigned long)stack16 > (1UL << 32) - sizeof(stack16)) |
| errx(1, "stack16 is too high\n"); |
| if ((unsigned long)int3 > (1UL << 32) - sizeof(int3)) |
| errx(1, "int3 is too high\n"); |
| |
| // Borrowed from a test case by hpa |
| const struct user_desc code16_desc = { |
| .entry_number = 0, |
| .base_addr = (unsigned long)int3, |
| .limit = 4095, |
| .seg_32bit = 0, |
| .contents = 2, /* Code, not conforming */ |
| .read_exec_only = 0, |
| .limit_in_pages = 0, |
| .seg_not_present = 0, |
| .useable = 0 |
| }; |
| const struct user_desc data16_desc = { |
| .entry_number = 1, |
| .base_addr = (unsigned long)stack16, |
| .limit = 0xffff, |
| .seg_32bit = 0, |
| .contents = 0, /* Data, grow-up */ |
| .read_exec_only = 0, |
| .limit_in_pages = 0, |
| .seg_not_present = 0, |
| .useable = 0 |
| }; |
| |
| if (syscall(SYS_modify_ldt, 1, &code16_desc, sizeof code16_desc) != 0) |
| err(1, "modify_ldt"); |
| if (syscall(SYS_modify_ldt, 1, &data16_desc, sizeof data16_desc) != 0) |
| err(1, "modify_ldt"); |
| } |
| |
| static gregset_t initial_regs, requested_regs, resulting_regs; |
| |
| static bool sig_twiddle_cs, sig_twiddle_ss; |
| |
| static void sigusr1(int sig, siginfo_t *info, void *ctx_void) |
| { |
| ucontext_t *ctx = (ucontext_t*)ctx_void; |
| |
| memcpy(&initial_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t)); |
| |
| struct selectors *sels = (void *)&ctx->uc_mcontext.gregs[REG_CSGSFS]; |
| if (sig_twiddle_cs) |
| sels->cs = (0 << 3) | 7; /* LDT selector 0, RPL = 3 */ |
| if (sig_twiddle_ss) |
| sels->ss = (1 << 3) | 7; /* LDT selector 1, RPL = 3 */ |
| |
| /* Avoid spurious failures. */ |
| asm volatile ("mov %0,%%ds\n\t" |
| "mov %0,%%es\n\t" |
| : : "r" ((1 << 3) | 7) /* LDT selector 1, RPL = 3 */ |
| ); |
| |
| ctx->uc_mcontext.gregs[REG_RIP] = |
| sig_twiddle_cs ? 0 : (unsigned long)&int3; |
| ctx->uc_mcontext.gregs[REG_RSP] = 0x8badf00d5aadc0de; |
| |
| memcpy(&requested_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t)); |
| |
| return; |
| } |
| |
| static void sigtrap(int sig, siginfo_t *info, void *ctx_void) |
| { |
| ucontext_t *ctx = (ucontext_t*)ctx_void; |
| struct selectors *sels = (void *)&ctx->uc_mcontext.gregs[REG_CSGSFS]; |
| |
| unsigned short ss; |
| asm ("mov %%ss,%0" : "=r" (ss)); |
| printf("\tSIGTRAP: ss = %hx, frame ss = %hx\n", ss, sels->ss); |
| |
| memcpy(&resulting_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t)); |
| memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t)); |
| } |
| |
| static char altstack_data[SIGSTKSZ]; |
| |
| static int do_test(bool twiddle_cs, bool twiddle_ss) |
| { |
| printf("\ttwiddle_cs = %d, twiddle_ss = %d\n", |
| (int)twiddle_cs, (int)twiddle_ss); |
| sig_twiddle_cs = twiddle_cs; |
| sig_twiddle_ss = twiddle_ss; |
| |
| raise(SIGUSR1); |
| |
| int nerrs = 0; |
| |
| for (int i = 0; i < NGREG; i++) { |
| greg_t req = requested_regs[i], res = resulting_regs[i]; |
| if (i == REG_TRAPNO || i == REG_RIP) |
| continue; /* don't care */ |
| if (i == REG_RSP) { |
| printf("\tRSP: %llx -> %llx\n", req, res); |
| if (res == (req & 0xFFFFFFFF)) |
| continue; /* OK; not expected to work */ |
| } |
| if (requested_regs[i] != resulting_regs[i]) { |
| printf("\tReg %d mismatch: requested 0x%llx; got 0x%llx\n", |
| i, requested_regs[i], resulting_regs[i]); |
| nerrs++; |
| } |
| } |
| |
| if (nerrs) |
| printf("[FAIL]\t%d registers were corrupted\n", nerrs); |
| else |
| printf("[OK]\tall registers okay\n"); |
| |
| return nerrs; |
| } |
| |
| int main() |
| { |
| int nerrs = 0; |
| |
| setup_ldt(); |
| |
| stack_t stack = { |
| .ss_sp = altstack_data, |
| .ss_size = SIGSTKSZ, |
| }; |
| if (sigaltstack(&stack, NULL) != 0) |
| err(1, "sigaltstack"); |
| |
| sethandler(SIGUSR1, sigusr1, 0); |
| sethandler(SIGTRAP, sigtrap, SA_ONSTACK); |
| |
| nerrs += do_test(false, false); |
| nerrs += do_test(true, false); |
| nerrs += do_test(false, true); |
| nerrs += do_test(true, true); |
| |
| return nerrs ? 1 : 0; |
| } |