• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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