1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright (c) 2018 Andrew Lutomirski
4 * Copyright (C) 2020 SUSE LLC <[email protected]>
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 <[email protected]>
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
child_main(void)59 static void child_main(void)
60 {
61 raise(SIGSTOP);
62 exit(0);
63 }
64
ptrace_try_kern_addr(unsigned long kern_addr)65 static void ptrace_try_kern_addr(unsigned long kern_addr)
66 {
67 int status;
68 unsigned long addr;
69
70 tst_res(TINFO, "Trying address 0x%lx", kern_addr);
71
72 child_pid = SAFE_FORK();
73
74 if (!child_pid)
75 child_main();
76
77 if (SAFE_WAITPID(child_pid, &status, WUNTRACED) != child_pid)
78 tst_brk(TBROK, "Received event from unexpected PID");
79
80 #if defined(__i386__) || defined(__x86_64__)
81 SAFE_PTRACE(PTRACE_ATTACH, child_pid, NULL, NULL);
82 SAFE_PTRACE(PTRACE_POKEUSER, child_pid,
83 (void *)offsetof(struct user, u_debugreg[0]), (void *)1);
84 SAFE_PTRACE(PTRACE_POKEUSER, child_pid,
85 (void *)offsetof(struct user, u_debugreg[7]), (void *)1);
86
87 TEST(ptrace(PTRACE_POKEUSER, child_pid,
88 (void *)offsetof(struct user, u_debugreg[0]),
89 (void *)kern_addr));
90
91 if (TST_RET == -1) {
92 addr = ptrace(PTRACE_PEEKUSER, child_pid,
93 (void *)offsetof(struct user, u_debugreg[0]), NULL);
94 if (addr == kern_addr) {
95 TEST(ptrace(PTRACE_POKEUSER, child_pid,
96 (void *)offsetof(struct user, u_debugreg[7]), (void *)1));
97 }
98 }
99
100 if (TST_RET != -1) {
101 tst_res(TFAIL, "ptrace() breakpoint with kernel addr succeeded");
102 } else {
103 if (TST_ERR == EINVAL) {
104 tst_res(TPASS | TTERRNO,
105 "ptrace() breakpoint with kernel addr failed");
106 } else {
107 tst_res(TFAIL | TTERRNO,
108 "ptrace() breakpoint on kernel addr should return EINVAL, got");
109 }
110 }
111
112 #endif
113
114 SAFE_PTRACE(PTRACE_DETACH, child_pid, NULL, NULL);
115 SAFE_KILL(child_pid, SIGCONT);
116 child_pid = 0;
117 tst_reap_children();
118 }
119
run(void)120 static void run(void)
121 {
122 ptrace_try_kern_addr(KERN_ADDR_MIN);
123 ptrace_try_kern_addr(KERN_ADDR_MAX);
124 ptrace_try_kern_addr(KERN_ADDR_MIN + (KERN_ADDR_MAX - KERN_ADDR_MIN)/2);
125 }
126
cleanup(void)127 static void cleanup(void)
128 {
129 /* Main process terminated by tst_brk() with child still paused */
130 if (child_pid)
131 SAFE_KILL(child_pid, SIGKILL);
132 }
133
134 static struct tst_test test = {
135 .test_all = run,
136 .cleanup = cleanup,
137 .forks_child = 1,
138 /*
139 * When running in compat mode we can't pass 64 address to ptrace so we
140 * have to skip the test.
141 */
142 .skip_in_compat = 1,
143 .supported_archs = (const char *const []) {
144 "x86",
145 "x86_64",
146 NULL
147 },
148 .tags = (const struct tst_tag[]) {
149 {"linux-git", "f67b15037a7a"},
150 {"CVE", "2018-1000199"},
151 {"linux-git", "27747f8bc355"},
152 {}
153 }
154 };
155