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 static pid_t child_pid;
47
48 #if defined(__i386__)
49 # define KERN_ADDR_MIN 0xc0000000
50 # define KERN_ADDR_MAX 0xffffffff
51 # define KERN_ADDR_BITS 32
52 #else
53 # define KERN_ADDR_MIN 0xffff800000000000
54 # define KERN_ADDR_MAX 0xffffffffffffffff
55 # define KERN_ADDR_BITS 64
56 #endif
57
58 static int deffered_check;
59
60 static struct tst_kern_exv kvers[] = {
61 {"RHEL8", "4.18.0-49"},
62 {NULL, NULL},
63 };
64
setup(void)65 static void setup(void)
66 {
67 /*
68 * The original fix for the kernel haven't rejected the kernel address
69 * right away when breakpoint was modified from userspace it was
70 * disabled instead and the EINVAL was returned when dr7 was written to
71 * enable it again. On RHEL8, it has introduced the right fix since
72 * 4.18.0-49.
73 */
74 if (tst_kvercmp2(4, 19, 0, kvers) < 0)
75 deffered_check = 1;
76 }
77
child_main(void)78 static void child_main(void)
79 {
80 raise(SIGSTOP);
81 exit(0);
82 }
83
ptrace_try_kern_addr(unsigned long kern_addr)84 static void ptrace_try_kern_addr(unsigned long kern_addr)
85 {
86 int status;
87 unsigned long addr;
88
89 tst_res(TINFO, "Trying address 0x%lx", kern_addr);
90
91 child_pid = SAFE_FORK();
92
93 if (!child_pid)
94 child_main();
95
96 if (SAFE_WAITPID(child_pid, &status, WUNTRACED) != child_pid)
97 tst_brk(TBROK, "Received event from unexpected PID");
98
99 #if defined(__i386__) || defined(__x86_64__)
100 SAFE_PTRACE(PTRACE_ATTACH, child_pid, NULL, NULL);
101 SAFE_PTRACE(PTRACE_POKEUSER, child_pid,
102 (void *)offsetof(struct user, u_debugreg[0]), (void *)1);
103 SAFE_PTRACE(PTRACE_POKEUSER, child_pid,
104 (void *)offsetof(struct user, u_debugreg[7]), (void *)1);
105
106 TEST(ptrace(PTRACE_POKEUSER, child_pid,
107 (void *)offsetof(struct user, u_debugreg[0]),
108 (void *)kern_addr));
109
110 if (deffered_check) {
111 TEST(ptrace(PTRACE_POKEUSER, child_pid,
112 (void *)offsetof(struct user, u_debugreg[7]), (void *)1));
113 }
114
115 if (TST_RET != -1) {
116 tst_res(TFAIL, "ptrace() breakpoint with kernel addr succeeded");
117 } else {
118 if (TST_ERR == EINVAL) {
119 tst_res(TPASS | TTERRNO,
120 "ptrace() breakpoint with kernel addr failed");
121 } else {
122 tst_res(TFAIL | TTERRNO,
123 "ptrace() breakpoint on kernel addr should return EINVAL, got");
124 }
125 }
126
127 addr = ptrace(PTRACE_PEEKUSER, child_pid,
128 (void*)offsetof(struct user, u_debugreg[0]), NULL);
129 #endif
130
131 if (!deffered_check && addr == kern_addr)
132 tst_res(TFAIL, "Was able to set breakpoint on kernel addr");
133
134 SAFE_PTRACE(PTRACE_DETACH, child_pid, NULL, NULL);
135 SAFE_KILL(child_pid, SIGCONT);
136 child_pid = 0;
137 tst_reap_children();
138 }
139
run(void)140 static void run(void)
141 {
142 ptrace_try_kern_addr(KERN_ADDR_MIN);
143 ptrace_try_kern_addr(KERN_ADDR_MAX);
144 ptrace_try_kern_addr(KERN_ADDR_MIN + (KERN_ADDR_MAX - KERN_ADDR_MIN)/2);
145 }
146
cleanup(void)147 static void cleanup(void)
148 {
149 /* Main process terminated by tst_brk() with child still paused */
150 if (child_pid)
151 SAFE_KILL(child_pid, SIGKILL);
152 }
153
154 static struct tst_test test = {
155 .test_all = run,
156 .setup = setup,
157 .cleanup = cleanup,
158 .forks_child = 1,
159 /*
160 * When running in compat mode we can't pass 64 address to ptrace so we
161 * have to skip the test.
162 */
163 .skip_in_compat = 1,
164 .supported_archs = (const char *const []) {
165 "x86",
166 "x86_64",
167 NULL
168 },
169 .tags = (const struct tst_tag[]) {
170 {"linux-git", "f67b15037a7a"},
171 {"CVE", "2018-1000199"},
172 {"linux-git", "27747f8bc355"},
173 {}
174 }
175 };
176