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) modifies debug registers.
9 * Even if the call returns error, it may create breakpoint in kernel code.
10 * Kernel crash partially fixed in:
11 *
12 * commit f67b15037a7a50c57f72e69a6d59941ad90a0f0f
13 * Author: Linus Torvalds <torvalds@linux-foundation.org>
14 * Date: Mon Mar 26 15:39:07 2018 -1000
15 *
16 * perf/hwbp: Simplify the perf-hwbp code, fix documentation
17 */
18
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <stddef.h>
22 #include <sys/ptrace.h>
23 #include <sys/user.h>
24 #include <signal.h>
25 #include "tst_test.h"
26 #include "tst_safe_stdio.h"
27
28 #if defined(__i386__) || defined(__x86_64__)
29 #define SYMNAME_SIZE 256
30 #define KERNEL_SYM "do_debug"
31
32 static unsigned long break_addr;
33 static pid_t child_pid;
34
setup(void)35 static void setup(void)
36 {
37 int fcount;
38 char endl, symname[256];
39 FILE *fr = SAFE_FOPEN("/proc/kallsyms", "r");
40
41 /* Find address of do_debug() in /proc/kallsyms */
42 do {
43 fcount = fscanf(fr, "%lx %*c %255s%c", &break_addr, symname,
44 &endl);
45
46 if (fcount <= 0 && feof(fr))
47 break;
48
49 if (fcount < 2) {
50 fclose(fr);
51 tst_brk(TBROK, "Unexpected data in /proc/kallsyms %d", fcount);
52 }
53
54 if (fcount >= 3 && endl != '\n')
55 while (!feof(fr) && fgetc(fr) != '\n');
56 } while (!feof(fr) && strcmp(symname, KERNEL_SYM));
57
58 SAFE_FCLOSE(fr);
59
60 if (strcmp(symname, KERNEL_SYM))
61 tst_brk(TBROK, "Cannot find address of kernel symbol \"%s\"",
62 KERNEL_SYM);
63
64 if (!break_addr)
65 tst_brk(TCONF, "Addresses in /proc/kallsyms are hidden");
66
67 tst_res(TINFO, "Kernel symbol \"%s\" found at 0x%lx", KERNEL_SYM,
68 break_addr);
69 }
70
debug_trap(void)71 static void debug_trap(void)
72 {
73 /* x86 instruction INT1 */
74 asm volatile (".byte 0xf1");
75 }
76
child_main(void)77 static void child_main(void)
78 {
79 raise(SIGSTOP);
80 /* wait for SIGCONT from parent */
81 debug_trap();
82 exit(0);
83 }
84
run(void)85 static void run(void)
86 {
87 int status;
88 pid_t child;
89
90 child = child_pid = SAFE_FORK();
91
92 if (!child_pid) {
93 child_main();
94 }
95
96 if (SAFE_WAITPID(child_pid, &status, WUNTRACED) != child_pid)
97 tst_brk(TBROK, "Received event from unexpected PID");
98
99 SAFE_PTRACE(PTRACE_ATTACH, child_pid, NULL, NULL);
100 SAFE_PTRACE(PTRACE_POKEUSER, child_pid,
101 (void *)offsetof(struct user, u_debugreg[0]), (void *)1);
102 SAFE_PTRACE(PTRACE_POKEUSER, child_pid,
103 (void *)offsetof(struct user, u_debugreg[7]), (void *)1);
104
105 /* Return value intentionally ignored here */
106 ptrace(PTRACE_POKEUSER, child_pid,
107 (void *)offsetof(struct user, u_debugreg[0]),
108 (void *)break_addr);
109
110 SAFE_PTRACE(PTRACE_DETACH, child_pid, NULL, NULL);
111 SAFE_KILL(child_pid, SIGCONT);
112 child_pid = 0;
113
114 if (SAFE_WAITPID(child, &status, 0) != child)
115 tst_brk(TBROK, "Received event from unexpected PID");
116
117 if (!WIFSIGNALED(status))
118 tst_brk(TBROK, "Received unexpected event from child");
119
120 tst_res(TPASS, "Child killed by %s", tst_strsig(WTERMSIG(status)));
121 tst_res(TPASS, "We're still here. Nothing bad happened, probably.");
122 }
123
cleanup(void)124 static void cleanup(void)
125 {
126 /* Main process terminated by tst_brk() with child still paused */
127 if (child_pid)
128 SAFE_KILL(child_pid, SIGKILL);
129 }
130
131 static struct tst_test test = {
132 .test_all = run,
133 .setup = setup,
134 .cleanup = cleanup,
135 .forks_child = 1,
136 .tags = (const struct tst_tag[]) {
137 {"linux-git", "f67b15037a7a"},
138 {"CVE", "2018-1000199"},
139 {}
140 }
141 };
142 #else
143 TST_TEST_TCONF("This test is only supported on x86 systems");
144 #endif
145