linux/tools/testing/selftests/x86/mov_ss_trap.c
<<
>>
Prefs
   1/* SPDX-License-Identifier: GPL-2.0 */
   2/*
   3 * mov_ss_trap.c: Exercise the bizarre side effects of a watchpoint on MOV SS
   4 *
   5 * This does MOV SS from a watchpointed address followed by various
   6 * types of kernel entries.  A MOV SS that hits a watchpoint will queue
   7 * up a #DB trap but will not actually deliver that trap.  The trap
   8 * will be delivered after the next instruction instead.  The CPU's logic
   9 * seems to be:
  10 *
  11 *  - Any fault: drop the pending #DB trap.
  12 *  - INT $N, INT3, INTO, SYSCALL, SYSENTER: enter the kernel and then
  13 *    deliver #DB.
  14 *  - ICEBP: enter the kernel but do not deliver the watchpoint trap
  15 *  - breakpoint: only one #DB is delivered (phew!)
  16 *
  17 * There are plenty of ways for a kernel to handle this incorrectly.  This
  18 * test tries to exercise all the cases.
  19 *
  20 * This should mostly cover CVE-2018-1087 and CVE-2018-8897.
  21 */
  22#define _GNU_SOURCE
  23
  24#include <stdlib.h>
  25#include <sys/ptrace.h>
  26#include <sys/types.h>
  27#include <sys/wait.h>
  28#include <sys/user.h>
  29#include <sys/syscall.h>
  30#include <unistd.h>
  31#include <errno.h>
  32#include <stddef.h>
  33#include <stdio.h>
  34#include <err.h>
  35#include <string.h>
  36#include <setjmp.h>
  37#include <sys/prctl.h>
  38
  39#define X86_EFLAGS_RF (1UL << 16)
  40
  41#if __x86_64__
  42# define REG_IP REG_RIP
  43#else
  44# define REG_IP REG_EIP
  45#endif
  46
  47unsigned short ss;
  48extern unsigned char breakpoint_insn[];
  49sigjmp_buf jmpbuf;
  50
  51static void enable_watchpoint(void)
  52{
  53        pid_t parent = getpid();
  54        int status;
  55
  56        pid_t child = fork();
  57        if (child < 0)
  58                err(1, "fork");
  59
  60        if (child) {
  61                if (waitpid(child, &status, 0) != child)
  62                        err(1, "waitpid for child");
  63        } else {
  64                unsigned long dr0, dr1, dr7;
  65
  66                dr0 = (unsigned long)&ss;
  67                dr1 = (unsigned long)breakpoint_insn;
  68                dr7 = ((1UL << 1) |     /* G0 */
  69                       (3UL << 16) |    /* RW0 = read or write */
  70                       (1UL << 18) |    /* LEN0 = 2 bytes */
  71                       (1UL << 3));     /* G1, RW1 = insn */
  72
  73                if (ptrace(PTRACE_ATTACH, parent, NULL, NULL) != 0)
  74                        err(1, "PTRACE_ATTACH");
  75
  76                if (waitpid(parent, &status, 0) != parent)
  77                        err(1, "waitpid for child");
  78
  79                if (ptrace(PTRACE_POKEUSER, parent, (void *)offsetof(struct user, u_debugreg[0]), dr0) != 0)
  80                        err(1, "PTRACE_POKEUSER DR0");
  81
  82                if (ptrace(PTRACE_POKEUSER, parent, (void *)offsetof(struct user, u_debugreg[1]), dr1) != 0)
  83                        err(1, "PTRACE_POKEUSER DR1");
  84
  85                if (ptrace(PTRACE_POKEUSER, parent, (void *)offsetof(struct user, u_debugreg[7]), dr7) != 0)
  86                        err(1, "PTRACE_POKEUSER DR7");
  87
  88                printf("\tDR0 = %lx, DR1 = %lx, DR7 = %lx\n", dr0, dr1, dr7);
  89
  90                if (ptrace(PTRACE_DETACH, parent, NULL, NULL) != 0)
  91                        err(1, "PTRACE_DETACH");
  92
  93                exit(0);
  94        }
  95}
  96
  97static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *),
  98                       int flags)
  99{
 100        struct sigaction sa;
 101        memset(&sa, 0, sizeof(sa));
 102        sa.sa_sigaction = handler;
 103        sa.sa_flags = SA_SIGINFO | flags;
 104        sigemptyset(&sa.sa_mask);
 105        if (sigaction(sig, &sa, 0))
 106                err(1, "sigaction");
 107}
 108
 109static char const * const signames[] = {
 110        [SIGSEGV] = "SIGSEGV",
 111        [SIGBUS] = "SIBGUS",
 112        [SIGTRAP] = "SIGTRAP",
 113        [SIGILL] = "SIGILL",
 114};
 115
 116static void sigtrap(int sig, siginfo_t *si, void *ctx_void)
 117{
 118        ucontext_t *ctx = ctx_void;
 119
 120        printf("\tGot SIGTRAP with RIP=%lx, EFLAGS.RF=%d\n",
 121               (unsigned long)ctx->uc_mcontext.gregs[REG_IP],
 122               !!(ctx->uc_mcontext.gregs[REG_EFL] & X86_EFLAGS_RF));
 123}
 124
 125static void handle_and_return(int sig, siginfo_t *si, void *ctx_void)
 126{
 127        ucontext_t *ctx = ctx_void;
 128
 129        printf("\tGot %s with RIP=%lx\n", signames[sig],
 130               (unsigned long)ctx->uc_mcontext.gregs[REG_IP]);
 131}
 132
 133static void handle_and_longjmp(int sig, siginfo_t *si, void *ctx_void)
 134{
 135        ucontext_t *ctx = ctx_void;
 136
 137        printf("\tGot %s with RIP=%lx\n", signames[sig],
 138               (unsigned long)ctx->uc_mcontext.gregs[REG_IP]);
 139
 140        siglongjmp(jmpbuf, 1);
 141}
 142
 143int main()
 144{
 145        unsigned long nr;
 146
 147        asm volatile ("mov %%ss, %[ss]" : [ss] "=m" (ss));
 148        printf("\tSS = 0x%hx, &SS = 0x%p\n", ss, &ss);
 149
 150        if (prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0) == 0)
 151                printf("\tPR_SET_PTRACER_ANY succeeded\n");
 152
 153        printf("\tSet up a watchpoint\n");
 154        sethandler(SIGTRAP, sigtrap, 0);
 155        enable_watchpoint();
 156
 157        printf("[RUN]\tRead from watched memory (should get SIGTRAP)\n");
 158        asm volatile ("mov %[ss], %[tmp]" : [tmp] "=r" (nr) : [ss] "m" (ss));
 159
 160        printf("[RUN]\tMOV SS; INT3\n");
 161        asm volatile ("mov %[ss], %%ss; int3" :: [ss] "m" (ss));
 162
 163        printf("[RUN]\tMOV SS; INT 3\n");
 164        asm volatile ("mov %[ss], %%ss; .byte 0xcd, 0x3" :: [ss] "m" (ss));
 165
 166        printf("[RUN]\tMOV SS; CS CS INT3\n");
 167        asm volatile ("mov %[ss], %%ss; .byte 0x2e, 0x2e; int3" :: [ss] "m" (ss));
 168
 169        printf("[RUN]\tMOV SS; CSx14 INT3\n");
 170        asm volatile ("mov %[ss], %%ss; .fill 14,1,0x2e; int3" :: [ss] "m" (ss));
 171
 172        printf("[RUN]\tMOV SS; INT 4\n");
 173        sethandler(SIGSEGV, handle_and_return, SA_RESETHAND);
 174        asm volatile ("mov %[ss], %%ss; int $4" :: [ss] "m" (ss));
 175
 176#ifdef __i386__
 177        printf("[RUN]\tMOV SS; INTO\n");
 178        sethandler(SIGSEGV, handle_and_return, SA_RESETHAND);
 179        nr = -1;
 180        asm volatile ("add $1, %[tmp]; mov %[ss], %%ss; into"
 181                      : [tmp] "+r" (nr) : [ss] "m" (ss));
 182#endif
 183
 184        if (sigsetjmp(jmpbuf, 1) == 0) {
 185                printf("[RUN]\tMOV SS; ICEBP\n");
 186
 187                /* Some emulators (e.g. QEMU TCG) don't emulate ICEBP. */
 188                sethandler(SIGILL, handle_and_longjmp, SA_RESETHAND);
 189
 190                asm volatile ("mov %[ss], %%ss; .byte 0xf1" :: [ss] "m" (ss));
 191        }
 192
 193        if (sigsetjmp(jmpbuf, 1) == 0) {
 194                printf("[RUN]\tMOV SS; CLI\n");
 195                sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
 196                asm volatile ("mov %[ss], %%ss; cli" :: [ss] "m" (ss));
 197        }
 198
 199        if (sigsetjmp(jmpbuf, 1) == 0) {
 200                printf("[RUN]\tMOV SS; #PF\n");
 201                sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
 202                asm volatile ("mov %[ss], %%ss; mov (-1), %[tmp]"
 203                              : [tmp] "=r" (nr) : [ss] "m" (ss));
 204        }
 205
 206        /*
 207         * INT $1: if #DB has DPL=3 and there isn't special handling,
 208         * then the kernel will die.
 209         */
 210        if (sigsetjmp(jmpbuf, 1) == 0) {
 211                printf("[RUN]\tMOV SS; INT 1\n");
 212                sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
 213                asm volatile ("mov %[ss], %%ss; int $1" :: [ss] "m" (ss));
 214        }
 215
 216#ifdef __x86_64__
 217        /*
 218         * In principle, we should test 32-bit SYSCALL as well, but
 219         * the calling convention is so unpredictable that it's
 220         * not obviously worth the effort.
 221         */
 222        if (sigsetjmp(jmpbuf, 1) == 0) {
 223                printf("[RUN]\tMOV SS; SYSCALL\n");
 224                sethandler(SIGILL, handle_and_longjmp, SA_RESETHAND);
 225                nr = SYS_getpid;
 226                /*
 227                 * Toggle the high bit of RSP to make it noncanonical to
 228                 * strengthen this test on non-SMAP systems.
 229                 */
 230                asm volatile ("btc $63, %%rsp\n\t"
 231                              "mov %[ss], %%ss; syscall\n\t"
 232                              "btc $63, %%rsp"
 233                              : "+a" (nr) : [ss] "m" (ss)
 234                              : "rcx"
 235#ifdef __x86_64__
 236                                , "r11"
 237#endif
 238                        );
 239        }
 240#endif
 241
 242        printf("[RUN]\tMOV SS; breakpointed NOP\n");
 243        asm volatile ("mov %[ss], %%ss; breakpoint_insn: nop" :: [ss] "m" (ss));
 244
 245        /*
 246         * Invoking SYSENTER directly breaks all the rules.  Just handle
 247         * the SIGSEGV.
 248         */
 249        if (sigsetjmp(jmpbuf, 1) == 0) {
 250                printf("[RUN]\tMOV SS; SYSENTER\n");
 251                stack_t stack = {
 252                        .ss_sp = malloc(sizeof(char) * SIGSTKSZ),
 253                        .ss_size = SIGSTKSZ,
 254                };
 255                if (sigaltstack(&stack, NULL) != 0)
 256                        err(1, "sigaltstack");
 257                sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND | SA_ONSTACK);
 258                nr = SYS_getpid;
 259                free(stack.ss_sp);
 260                /* Clear EBP first to make sure we segfault cleanly. */
 261                asm volatile ("xorl %%ebp, %%ebp; mov %[ss], %%ss; SYSENTER" : "+a" (nr)
 262                              : [ss] "m" (ss) : "flags", "rcx"
 263#ifdef __x86_64__
 264                                , "r11"
 265#endif
 266                        );
 267
 268                /* We're unreachable here.  SYSENTER forgets RIP. */
 269        }
 270
 271        if (sigsetjmp(jmpbuf, 1) == 0) {
 272                printf("[RUN]\tMOV SS; INT $0x80\n");
 273                sethandler(SIGSEGV, handle_and_longjmp, SA_RESETHAND);
 274                nr = 20;        /* compat getpid */
 275                asm volatile ("mov %[ss], %%ss; int $0x80"
 276                              : "+a" (nr) : [ss] "m" (ss)
 277                              : "flags"
 278#ifdef __x86_64__
 279                                , "r8", "r9", "r10", "r11"
 280#endif
 281                        );
 282        }
 283
 284        printf("[OK]\tI aten't dead\n");
 285        return 0;
 286}
 287