1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Copyright (c) 2018 Andrew Lutomirski
4 * Copyright (C) 2020 SUSE LLC <mdoucha@suse.cz>
5 *
6 * CVE-2018-1000199
7 *
8 * Test error handling when ptrace(POKEUSER) modified x86 debug registers even
9 * when the call returned error.
10 *
11 * When the bug was present we could create breakpoint in the kernel code,
12 * which shoudn't be possible at all. The original CVE caused a kernel crash by
13 * setting a breakpoint on do_debug kernel function which, when triggered,
14 * caused an infinite loop. However we do not have to crash the kernel in order
15 * to assert if kernel has been fixed or not.
16 *
17 * On newer kernels all we have to do is to try to set a breakpoint, on any
18 * kernel address, then read it back and check if the value has been set or
19 * not.
20 *
21 * The original fix to the CVE however disabled a breakpoint on address change
22 * and the check was deffered to write dr7 that enabled the breakpoint again.
23 * So on older kernels we have to write to dr7 which should fail instead.
24 *
25 * Kernel crash partially fixed in:
26 *
27 * commit f67b15037a7a50c57f72e69a6d59941ad90a0f0f
28 * Author: Linus Torvalds <torvalds@linux-foundation.org>
29 * Date: Mon Mar 26 15:39:07 2018 -1000
30 *
31 * perf/hwbp: Simplify the perf-hwbp code, fix documentation
32 *
33 * On Centos7, this is also a regression test for
34 * commit 27747f8bc355 ("perf/x86/hw_breakpoints: Fix check for kernel-space breakpoints").
35 */
36
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <stddef.h>
40 #include <sys/ptrace.h>
41 #include <sys/user.h>
42 #include <signal.h>
43 #include "tst_test.h"
44 #include "tst_safe_stdio.h"
45
46 #if defined(__i386__) || defined(__x86_64__)
47
48 static pid_t child_pid;
49
50 #if defined(__x86_64__)
51 # define KERN_ADDR_MIN 0xffff800000000000
52 # define KERN_ADDR_MAX 0xffffffffffffffff
53 # define KERN_ADDR_BITS 64
54 #elif defined(__i386__)
55 # define KERN_ADDR_MIN 0xc0000000
56 # define KERN_ADDR_MAX 0xffffffff
57 # define KERN_ADDR_BITS 32
58 #endif
59
60 static int deffered_check;
61
62 static struct tst_kern_exv kvers[] = {
63 {"RHEL8", "4.18.0-49"},
64 {NULL, NULL},
65 };
66
setup(void)67 static void setup(void)
68 {
69 /*
70 * When running in compat mode we can't pass 64 address to ptrace so we
71 * have to skip the test.
72 */
73 if (tst_kernel_bits() != KERN_ADDR_BITS)
74 tst_brk(TCONF, "Cannot pass 64bit kernel address in compat mode");
75
76
77 /*
78 * The original fix for the kernel haven't rejected the kernel address
79 * right away when breakpoint was modified from userspace it was
80 * disabled instead and the EINVAL was returned when dr7 was written to
81 * enable it again. On RHEL8, it has introduced the right fix since
82 * 4.18.0-49.
83 */
84 if (tst_kvercmp2(4, 19, 0, kvers) < 0)
85 deffered_check = 1;
86 }
87
child_main(void)88 static void child_main(void)
89 {
90 raise(SIGSTOP);
91 exit(0);
92 }
93
ptrace_try_kern_addr(unsigned long kern_addr)94 static void ptrace_try_kern_addr(unsigned long kern_addr)
95 {
96 int status;
97 unsigned long addr;
98
99 tst_res(TINFO, "Trying address 0x%lx", kern_addr);
100
101 child_pid = SAFE_FORK();
102
103 if (!child_pid)
104 child_main();
105
106 if (SAFE_WAITPID(child_pid, &status, WUNTRACED) != child_pid)
107 tst_brk(TBROK, "Received event from unexpected PID");
108
109 SAFE_PTRACE(PTRACE_ATTACH, child_pid, NULL, NULL);
110 SAFE_PTRACE(PTRACE_POKEUSER, child_pid,
111 (void *)offsetof(struct user, u_debugreg[0]), (void *)1);
112 SAFE_PTRACE(PTRACE_POKEUSER, child_pid,
113 (void *)offsetof(struct user, u_debugreg[7]), (void *)1);
114
115 TEST(ptrace(PTRACE_POKEUSER, child_pid,
116 (void *)offsetof(struct user, u_debugreg[0]),
117 (void *)kern_addr));
118
119 if (deffered_check) {
120 TEST(ptrace(PTRACE_POKEUSER, child_pid,
121 (void *)offsetof(struct user, u_debugreg[7]), (void *)1));
122 }
123
124 if (TST_RET != -1) {
125 tst_res(TFAIL, "ptrace() breakpoint with kernel addr succeeded");
126 } else {
127 if (TST_ERR == EINVAL) {
128 tst_res(TPASS | TTERRNO,
129 "ptrace() breakpoint with kernel addr failed");
130 } else {
131 tst_res(TFAIL | TTERRNO,
132 "ptrace() breakpoint on kernel addr should return EINVAL, got");
133 }
134 }
135
136 addr = ptrace(PTRACE_PEEKUSER, child_pid,
137 (void*)offsetof(struct user, u_debugreg[0]), NULL);
138
139 if (!deffered_check && addr == kern_addr)
140 tst_res(TFAIL, "Was able to set breakpoint on kernel addr");
141
142 SAFE_PTRACE(PTRACE_DETACH, child_pid, NULL, NULL);
143 SAFE_KILL(child_pid, SIGCONT);
144 child_pid = 0;
145 tst_reap_children();
146 }
147
run(void)148 static void run(void)
149 {
150 ptrace_try_kern_addr(KERN_ADDR_MIN);
151 ptrace_try_kern_addr(KERN_ADDR_MAX);
152 ptrace_try_kern_addr(KERN_ADDR_MIN + (KERN_ADDR_MAX - KERN_ADDR_MIN)/2);
153 }
154
cleanup(void)155 static void cleanup(void)
156 {
157 /* Main process terminated by tst_brk() with child still paused */
158 if (child_pid)
159 SAFE_KILL(child_pid, SIGKILL);
160 }
161
162 static struct tst_test test = {
163 .test_all = run,
164 .setup = setup,
165 .cleanup = cleanup,
166 .forks_child = 1,
167 .tags = (const struct tst_tag[]) {
168 {"linux-git", "f67b15037a7a"},
169 {"CVE", "2018-1000199"},
170 {"linux-git", "27747f8bc355"},
171 {}
172 }
173 };
174 #else
175 TST_TEST_TCONF("This test is only supported on x86 systems");
176 #endif
177