]>
Commit | Line | Data |
---|---|---|
0a15584d AL |
1 | /* |
2 | * single_step_syscall.c - single-steps various x86 syscalls | |
3 | * Copyright (c) 2014-2015 Andrew Lutomirski | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms and conditions of the GNU General Public License, | |
7 | * version 2, as published by the Free Software Foundation. | |
8 | * | |
9 | * This program is distributed in the hope it will be useful, but | |
10 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | * General Public License for more details. | |
13 | * | |
14 | * This is a very simple series of tests that makes system calls with | |
15 | * the TF flag set. This exercises some nasty kernel code in the | |
16 | * SYSENTER case: SYSENTER does not clear TF, so SYSENTER with TF set | |
17 | * immediately issues #DB from CPL 0. This requires special handling in | |
18 | * the kernel. | |
19 | */ | |
20 | ||
21 | #define _GNU_SOURCE | |
22 | ||
23 | #include <sys/time.h> | |
24 | #include <time.h> | |
25 | #include <stdlib.h> | |
26 | #include <sys/syscall.h> | |
27 | #include <unistd.h> | |
28 | #include <stdio.h> | |
29 | #include <string.h> | |
30 | #include <inttypes.h> | |
31 | #include <sys/mman.h> | |
32 | #include <sys/signal.h> | |
33 | #include <sys/ucontext.h> | |
34 | #include <asm/ldt.h> | |
35 | #include <err.h> | |
36 | #include <setjmp.h> | |
37 | #include <stddef.h> | |
38 | #include <stdbool.h> | |
39 | #include <sys/ptrace.h> | |
40 | #include <sys/user.h> | |
41 | ||
42 | static void sethandler(int sig, void (*handler)(int, siginfo_t *, void *), | |
43 | int flags) | |
44 | { | |
45 | struct sigaction sa; | |
46 | memset(&sa, 0, sizeof(sa)); | |
47 | sa.sa_sigaction = handler; | |
48 | sa.sa_flags = SA_SIGINFO | flags; | |
49 | sigemptyset(&sa.sa_mask); | |
50 | if (sigaction(sig, &sa, 0)) | |
51 | err(1, "sigaction"); | |
52 | } | |
53 | ||
54 | static volatile sig_atomic_t sig_traps; | |
55 | ||
56 | #ifdef __x86_64__ | |
57 | # define REG_IP REG_RIP | |
58 | # define WIDTH "q" | |
2a4d0c62 | 59 | # define INT80_CLOBBERS "r8", "r9", "r10", "r11" |
0a15584d AL |
60 | #else |
61 | # define REG_IP REG_EIP | |
62 | # define WIDTH "l" | |
2a4d0c62 | 63 | # define INT80_CLOBBERS |
0a15584d AL |
64 | #endif |
65 | ||
66 | static unsigned long get_eflags(void) | |
67 | { | |
68 | unsigned long eflags; | |
69 | asm volatile ("pushf" WIDTH "\n\tpop" WIDTH " %0" : "=rm" (eflags)); | |
70 | return eflags; | |
71 | } | |
72 | ||
73 | static void set_eflags(unsigned long eflags) | |
74 | { | |
75 | asm volatile ("push" WIDTH " %0\n\tpopf" WIDTH | |
76 | : : "rm" (eflags) : "flags"); | |
77 | } | |
78 | ||
79 | #define X86_EFLAGS_TF (1UL << 8) | |
80 | ||
81 | static void sigtrap(int sig, siginfo_t *info, void *ctx_void) | |
82 | { | |
83 | ucontext_t *ctx = (ucontext_t*)ctx_void; | |
84 | ||
85 | if (get_eflags() & X86_EFLAGS_TF) { | |
86 | set_eflags(get_eflags() & ~X86_EFLAGS_TF); | |
87 | printf("[WARN]\tSIGTRAP handler had TF set\n"); | |
88 | _exit(1); | |
89 | } | |
90 | ||
91 | sig_traps++; | |
92 | ||
93 | if (sig_traps == 10000 || sig_traps == 10001) { | |
94 | printf("[WARN]\tHit %d SIGTRAPs with si_addr 0x%lx, ip 0x%lx\n", | |
95 | (int)sig_traps, | |
96 | (unsigned long)info->si_addr, | |
97 | (unsigned long)ctx->uc_mcontext.gregs[REG_IP]); | |
98 | } | |
99 | } | |
100 | ||
101 | static void check_result(void) | |
102 | { | |
103 | unsigned long new_eflags = get_eflags(); | |
104 | set_eflags(new_eflags & ~X86_EFLAGS_TF); | |
105 | ||
106 | if (!sig_traps) { | |
107 | printf("[FAIL]\tNo SIGTRAP\n"); | |
108 | exit(1); | |
109 | } | |
110 | ||
111 | if (!(new_eflags & X86_EFLAGS_TF)) { | |
112 | printf("[FAIL]\tTF was cleared\n"); | |
113 | exit(1); | |
114 | } | |
115 | ||
116 | printf("[OK]\tSurvived with TF set and %d traps\n", (int)sig_traps); | |
117 | sig_traps = 0; | |
118 | } | |
119 | ||
120 | int main() | |
121 | { | |
122 | int tmp; | |
123 | ||
124 | sethandler(SIGTRAP, sigtrap, 0); | |
125 | ||
126 | printf("[RUN]\tSet TF and check nop\n"); | |
127 | set_eflags(get_eflags() | X86_EFLAGS_TF); | |
128 | asm volatile ("nop"); | |
129 | check_result(); | |
130 | ||
131 | #ifdef __x86_64__ | |
132 | printf("[RUN]\tSet TF and check syscall-less opportunistic sysret\n"); | |
133 | set_eflags(get_eflags() | X86_EFLAGS_TF); | |
134 | extern unsigned char post_nop[]; | |
135 | asm volatile ("pushf" WIDTH "\n\t" | |
136 | "pop" WIDTH " %%r11\n\t" | |
137 | "nop\n\t" | |
138 | "post_nop:" | |
139 | : : "c" (post_nop) : "r11"); | |
140 | check_result(); | |
141 | #endif | |
142 | ||
143 | printf("[RUN]\tSet TF and check int80\n"); | |
144 | set_eflags(get_eflags() | X86_EFLAGS_TF); | |
2a4d0c62 DS |
145 | asm volatile ("int $0x80" : "=a" (tmp) : "a" (SYS_getpid) |
146 | : INT80_CLOBBERS); | |
0a15584d AL |
147 | check_result(); |
148 | ||
149 | /* | |
150 | * This test is particularly interesting if fast syscalls use | |
151 | * SYSENTER: it triggers a nasty design flaw in SYSENTER. | |
152 | * Specifically, SYSENTER does not clear TF, so either SYSENTER | |
153 | * or the next instruction traps at CPL0. (Of course, Intel | |
154 | * mostly forgot to document exactly what happens here.) So we | |
155 | * get a CPL0 fault with usergs (on 64-bit kernels) and possibly | |
156 | * no stack. The only sane way the kernel can possibly handle | |
157 | * it is to clear TF on return from the #DB handler, but this | |
158 | * happens way too early to set TF in the saved pt_regs, so the | |
159 | * kernel has to do something clever to avoid losing track of | |
160 | * the TF bit. | |
161 | * | |
162 | * Needless to say, we've had bugs in this area. | |
163 | */ | |
164 | syscall(SYS_getpid); /* Force symbol binding without TF set. */ | |
165 | printf("[RUN]\tSet TF and check a fast syscall\n"); | |
166 | set_eflags(get_eflags() | X86_EFLAGS_TF); | |
167 | syscall(SYS_getpid); | |
168 | check_result(); | |
169 | ||
170 | /* Now make sure that another fast syscall doesn't set TF again. */ | |
171 | printf("[RUN]\tFast syscall with TF cleared\n"); | |
172 | fflush(stdout); /* Force a syscall */ | |
173 | if (get_eflags() & X86_EFLAGS_TF) { | |
174 | printf("[FAIL]\tTF is now set\n"); | |
175 | exit(1); | |
176 | } | |
177 | if (sig_traps) { | |
178 | printf("[FAIL]\tGot SIGTRAP\n"); | |
179 | exit(1); | |
180 | } | |
181 | printf("[OK]\tNothing unexpected happened\n"); | |
182 | ||
183 | return 0; | |
184 | } |