• 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 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