| #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 { |
| unsigned 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 void clearhandler(int sig) |
| { |
| struct sigaction sa; |
| memset(&sa, 0, sizeof(sa)); |
| sa.sa_handler = SIG_DFL; |
| 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" |
| "mov %ss,%ax\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 > (1ULL << 32) - sizeof(stack16)) |
| errx(1, "stack16 is too high\n"); |
| if ((unsigned long)int3 > (1ULL << 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 |
| }; |
| const struct user_desc npcode16_desc = { |
| .entry_number = 3, |
| .base_addr = (unsigned long)int3, |
| .limit = 4095, |
| .seg_32bit = 1, |
| .contents = 2, /* Code, not conforming */ |
| .read_exec_only = 0, |
| .limit_in_pages = 0, |
| .seg_not_present = 1, |
| .useable = 0 |
| }; |
| const struct user_desc npdata16_desc = { |
| .entry_number = 4, |
| .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 = 1, |
| .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"); |
| if (syscall(SYS_modify_ldt, 1, &npcode16_desc, sizeof npcode16_desc) != 0) |
| err(1, "modify_ldt"); |
| if (syscall(SYS_modify_ldt, 1, &npdata16_desc, sizeof npdata16_desc) != 0) |
| err(1, "modify_ldt"); |
| } |
| |
| static gregset_t initial_regs, requested_regs, resulting_regs; |
| |
| static volatile unsigned short sig_cs, sig_ss; |
| static volatile bool sig_trapped; |
| |
| #ifdef __x86_64__ |
| # define REG_IP REG_RIP |
| # define REG_SP REG_RSP |
| # define REG_AX REG_RAX |
| |
| static unsigned short *ssptr(ucontext_t *ctx) |
| { |
| struct selectors *sels = (void *)&ctx->uc_mcontext.gregs[REG_CSGSFS]; |
| return &sels->ss; |
| } |
| |
| static unsigned short *csptr(ucontext_t *ctx) |
| { |
| struct selectors *sels = (void *)&ctx->uc_mcontext.gregs[REG_CSGSFS]; |
| return &sels->cs; |
| } |
| #else |
| # define REG_IP REG_EIP |
| # define REG_SP REG_ESP |
| # define REG_AX REG_EAX |
| |
| static greg_t *ssptr(ucontext_t *ctx) |
| { |
| return &ctx->uc_mcontext.gregs[REG_SS]; |
| } |
| |
| static greg_t *csptr(ucontext_t *ctx) |
| { |
| return &ctx->uc_mcontext.gregs[REG_CS]; |
| } |
| #endif |
| |
| static int nerrs; |
| |
| 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)); |
| |
| *csptr(ctx) = sig_cs; |
| *ssptr(ctx) = sig_ss; |
| |
| ctx->uc_mcontext.gregs[REG_IP] = |
| (sig_cs == 0x7 || sig_cs == 0x1f) ? 0 : (unsigned long)&int3; |
| ctx->uc_mcontext.gregs[REG_SP] = (unsigned long)0x8badf00d5aadc0deULL; |
| ctx->uc_mcontext.gregs[REG_AX] = 0; |
| |
| memcpy(&requested_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t)); |
| requested_regs[REG_AX] = *ssptr(ctx); /* The asm code does this. */ |
| |
| return; |
| } |
| |
| static void sigtrap(int sig, siginfo_t *info, void *ctx_void) |
| { |
| ucontext_t *ctx = (ucontext_t*)ctx_void; |
| |
| unsigned short ss; |
| asm ("mov %%ss,%0" : "=r" (ss)); |
| |
| greg_t asm_ss = ctx->uc_mcontext.gregs[REG_AX]; |
| if (asm_ss != sig_ss && sig == SIGTRAP) { |
| printf("[FAIL]\tSIGTRAP: ss = %hx, frame ss = %hx, ax = %llx\n", |
| ss, *ssptr(ctx), (unsigned long long)asm_ss); |
| nerrs++; |
| } |
| |
| memcpy(&resulting_regs, &ctx->uc_mcontext.gregs, sizeof(gregset_t)); |
| memcpy(&ctx->uc_mcontext.gregs, &initial_regs, sizeof(gregset_t)); |
| |
| sig_trapped = true; |
| } |
| |
| static char altstack_data[SIGSTKSZ]; |
| |
| int cs_bitness(unsigned short cs) |
| { |
| uint32_t valid = 0, ar; |
| asm ("lar %[cs], %[ar]\n\t" |
| "jnz 1f\n\t" |
| "mov $1, %[valid]\n\t" |
| "1:" |
| : [ar] "=r" (ar), [valid] "+rm" (valid) |
| : [cs] "r" (cs)); |
| |
| if (!valid) |
| return -1; |
| |
| bool db = (ar & (1 << 22)); |
| bool l = (ar & (1 << 21)); |
| |
| if (!(ar & (1<<11))) |
| return -1; /* Not code. */ |
| |
| if (l && !db) |
| return 64; |
| else if (!l && db) |
| return 32; |
| else if (!l && !db) |
| return 16; |
| else |
| return -1; /* Unknown bitness. */ |
| } |
| |
| int find_cs(int bitness) |
| { |
| unsigned short my_cs; |
| |
| asm ("mov %%cs,%0" : "=r" (my_cs)); |
| |
| if (cs_bitness(my_cs) == bitness) |
| return my_cs; |
| if (cs_bitness(my_cs + (2 << 3)) == bitness) |
| return my_cs + (2 << 3); |
| if (my_cs > (2<<3) && cs_bitness(my_cs - (2 << 3)) == bitness) |
| return my_cs - (2 << 3); |
| if (cs_bitness(0x7) == bitness) |
| return 0x7; |
| |
| printf("[WARN]\tCould not find %d-bit CS\n", bitness); |
| return -1; |
| } |
| |
| static int do_test(int cs_bits, bool use_16bit_ss) |
| { |
| int cs = find_cs(cs_bits); |
| if (cs == -1) |
| return 0; |
| |
| if (use_16bit_ss) |
| sig_ss = (1 << 3) | 7; /* LDT selector 1, RPL = 3 */ |
| else |
| asm volatile ("mov %%ss,%0" : "=r" (sig_ss)); |
| |
| sig_cs = cs; |
| |
| printf("[RUN]\t%d-bit CS (%hx), %d-bit SS (%hx)\n", |
| cs_bits, sig_cs, use_16bit_ss ? 16 : 32, sig_ss); |
| |
| raise(SIGUSR1); |
| |
| 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_IP) |
| continue; /* don't care */ |
| if (i == REG_SP) { |
| printf("\tSP: %llx -> %llx\n", (unsigned long long)req, |
| (unsigned long long)res); |
| if (res == (req & 0xFFFFFFFF)) |
| continue; /* OK; not expected to work */ |
| } |
| |
| bool ignore_reg = false; |
| #if __i386__ |
| if (i == REG_UESP) |
| ignore_reg = true; |
| #else |
| if (i == REG_CSGSFS) { |
| struct selectors *req_sels = |
| (void *)&requested_regs[REG_CSGSFS]; |
| struct selectors *res_sels = |
| (void *)&resulting_regs[REG_CSGSFS]; |
| if (req_sels->cs != res_sels->cs) { |
| printf("[FAIL]\tCS mismatch: requested 0x%hx; got 0x%hx\n", |
| req_sels->cs, res_sels->cs); |
| nerrs++; |
| } |
| |
| if (req_sels->ss != res_sels->ss) { |
| printf("[FAIL]\tSS mismatch: requested 0x%hx; got 0x%hx\n", |
| req_sels->ss, res_sels->ss); |
| nerrs++; |
| } |
| |
| continue; |
| } |
| #endif |
| |
| /* Sanity check on the kernel */ |
| if (i == REG_AX && requested_regs[i] != resulting_regs[i]) { |
| printf("[FAIL]\tAX (saved SP) mismatch: requested 0x%llx; got 0x%llx\n", |
| (unsigned long long)requested_regs[i], |
| (unsigned long long)resulting_regs[i]); |
| nerrs++; |
| continue; |
| } |
| |
| if (requested_regs[i] != resulting_regs[i] && !ignore_reg) { |
| printf("[FAIL]\tReg %d mismatch: requested 0x%llx; got 0x%llx\n", |
| i, (unsigned long long)requested_regs[i], |
| (unsigned long long)resulting_regs[i]); |
| nerrs++; |
| } |
| } |
| |
| if (nerrs == 0) |
| printf("[OK]\tall registers okay\n"); |
| |
| return nerrs; |
| } |
| |
| static int test_bad_iret(int cs_bits, unsigned short ss, int force_cs) |
| { |
| int cs = force_cs == -1 ? find_cs(cs_bits) : force_cs; |
| if (cs == -1) |
| return 0; |
| |
| sig_cs = cs; |
| sig_ss = ss; |
| |
| printf("[RUN]\t%d-bit CS (%hx), bogus SS (%hx)\n", |
| cs_bits, sig_cs, sig_ss); |
| |
| sig_trapped = false; |
| raise(SIGUSR1); |
| if (sig_trapped) { |
| printf("[OK]\tGot SIGSEGV\n"); |
| return 0; |
| } else { |
| printf("[FAIL]\tDid not get SIGSEGV\n"); |
| return 1; |
| } |
| } |
| |
| int main() |
| { |
| int total_nerrs = 0; |
| unsigned short my_cs, my_ss; |
| |
| asm volatile ("mov %%cs,%0" : "=r" (my_cs)); |
| asm volatile ("mov %%ss,%0" : "=r" (my_ss)); |
| 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); |
| |
| total_nerrs += do_test(64, false); |
| total_nerrs += do_test(32, false); |
| total_nerrs += do_test(16, false); |
| total_nerrs += do_test(64, true); |
| total_nerrs += do_test(32, true); |
| total_nerrs += do_test(16, true); |
| |
| clearhandler(SIGTRAP); |
| sethandler(SIGSEGV, sigtrap, SA_ONSTACK); |
| |
| test_bad_iret(64, (2 << 3) | 7, -1); |
| test_bad_iret(32, (2 << 3) | 7, -1); |
| test_bad_iret(16, (2 << 3) | 7, -1); |
| |
| test_bad_iret(64, my_cs, -1); |
| test_bad_iret(32, my_cs, -1); |
| test_bad_iret(16, my_cs, -1); |
| |
| /* IRET will fail with #NP */ |
| test_bad_iret(32, my_ss, (3 << 3) | 7); |
| |
| /* IRET will fail with #SS */ |
| test_bad_iret(32, (4 << 3) | 7, -1); |
| |
| return total_nerrs ? 1 : 0; |
| } |