1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2019 SUSE LLC <[email protected]>
4 */
5
6 /*
7 * CVE-2017-1000405
8 *
9 * Check for the Huge Dirty Cow vulnerability which allows a userspace process
10 * to overwrite the huge zero page. Race fixed in:
11 *
12 * commit a8f97366452ed491d13cf1e44241bc0b5740b1f0
13 * Author: Kirill A. Shutemov <[email protected]>
14 * Date: Mon Nov 27 06:21:25 2017 +0300
15 *
16 * mm, thp: Do not make page table dirty unconditionally in touch_p[mu]d()
17 *
18 * More details see the following URL
19 * https://medium.com/bindecy/huge-dirty-cow-cve-2017-1000405-110eca132de0
20 *
21 * On old kernel such as 4.9, it has fixed the Dirty Cow bug but a similar check
22 * in huge_memory.c was forgotten. As a result, remote memory writes to ro regions
23 * of memory backed by transparent huge pages cause an infinite loop in the kernel.
24 * While in this state the process is stil SIGKILLable, but little else works.
25 * It is also a regression test about kernel
26 * commit 8310d48b125d("huge_memory.c: respect FOLL_FORCE/FOLL_COW for thp").
27 */
28
29 #include "tst_test.h"
30 #include "lapi/mmap.h"
31 #include "tst_fuzzy_sync.h"
32
33 static char *write_thp, *read_thp;
34 static int *write_ptr, *read_ptr;
35 static size_t thp_size;
36 static int writefd = -1, readfd = -1;
37 static struct tst_fzsync_pair fzsync_pair;
38
alloc_zero_page(void * baseaddr)39 static void *alloc_zero_page(void *baseaddr)
40 {
41 int i;
42 void *ret;
43
44 /* Find aligned chunk of address space. MAP_HUGETLB doesn't work. */
45 for (i = 0; i < 16; i++, baseaddr += thp_size) {
46 ret = mmap(baseaddr, thp_size, PROT_READ,
47 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
48
49 if (ret == baseaddr) {
50 TEST(madvise(ret, thp_size, MADV_HUGEPAGE));
51
52 if (TST_RET == -1 && TST_ERR == EINVAL) {
53 tst_brk(TCONF | TTERRNO,
54 "madvise(MADV_HUGEPAGE) not supported");
55 }
56
57 if (TST_RET) {
58 tst_brk(TBROK | TTERRNO,
59 "madvise(MADV_HUGEPAGE) failed");
60 }
61
62 return ret;
63 }
64
65 if (ret != MAP_FAILED)
66 SAFE_MUNMAP(ret, thp_size);
67 }
68
69 tst_brk(TBROK, "Cannot map huge zero page near the specified address");
70 return NULL; /* Silence compiler warning */
71 }
72
setup(void)73 static void setup(void)
74 {
75 size_t i;
76
77 thp_size = tst_get_hugepage_size();
78
79 if (!thp_size)
80 tst_brk(TCONF, "Kernel does not support huge pages");
81
82 write_thp = alloc_zero_page((void *)thp_size);
83
84 for (i = 0; i < thp_size; i++) {
85 if (write_thp[i])
86 tst_brk(TCONF, "Huge zero page is pre-polluted");
87 }
88
89 /* leave a hole between read and write THP to prevent merge */
90 read_thp = alloc_zero_page(write_thp + 2 * thp_size);
91 write_ptr = (int *)(write_thp + thp_size - sizeof(int));
92 read_ptr = (int *)(read_thp + thp_size - sizeof(int));
93 writefd = SAFE_OPEN("/proc/self/mem", O_RDWR);
94 readfd = SAFE_OPEN("/proc/self/mem", O_RDWR);
95
96 fzsync_pair.exec_loops = 100000;
97 tst_fzsync_pair_init(&fzsync_pair);
98 }
99
thread_run(void * arg)100 static void *thread_run(void *arg)
101 {
102 int c;
103
104 while (tst_fzsync_run_b(&fzsync_pair)) {
105 tst_fzsync_start_race_b(&fzsync_pair);
106 madvise(write_thp, thp_size, MADV_DONTNEED);
107 memcpy(&c, write_ptr, sizeof(c));
108 SAFE_LSEEK(readfd, (off_t)write_ptr, SEEK_SET);
109 SAFE_READ(1, readfd, &c, sizeof(int));
110 tst_fzsync_end_race_b(&fzsync_pair);
111 /* Wait for dirty page handling before next madvise() */
112 usleep(10);
113 }
114
115 return arg;
116 }
117
run(void)118 static void run(void)
119 {
120 int c = 0xdeadbeef;
121
122 tst_fzsync_pair_reset(&fzsync_pair, thread_run);
123
124 while (tst_fzsync_run_a(&fzsync_pair)) {
125 /* Write into the main huge page */
126 tst_fzsync_start_race_a(&fzsync_pair);
127 SAFE_LSEEK(writefd, (off_t)write_ptr, SEEK_SET);
128 madvise(write_thp, thp_size, MADV_DONTNEED);
129 SAFE_WRITE(SAFE_WRITE_ALL, writefd, &c, sizeof(int));
130 tst_fzsync_end_race_a(&fzsync_pair);
131
132 /* Check the other huge zero page for pollution */
133 madvise(read_thp, thp_size, MADV_DONTNEED);
134
135 if (*read_ptr != 0) {
136 tst_res(TFAIL, "Huge zero page was polluted");
137 return;
138 }
139 }
140
141 tst_res(TPASS, "Huge zero page is still clean");
142 }
143
cleanup(void)144 static void cleanup(void)
145 {
146 tst_fzsync_pair_cleanup(&fzsync_pair);
147
148 if (readfd >= 0)
149 SAFE_CLOSE(readfd);
150
151 if (writefd >= 0)
152 SAFE_CLOSE(writefd);
153
154 if (read_thp)
155 SAFE_MUNMAP(read_thp, thp_size);
156 if (write_thp)
157 SAFE_MUNMAP(write_thp, thp_size);
158 }
159
160 static struct tst_test test = {
161 .test_all = run,
162 .setup = setup,
163 .cleanup = cleanup,
164 .max_runtime = 150,
165 .tags = (const struct tst_tag[]) {
166 {"linux-git", "a8f97366452e"},
167 {"linux-git", "8310d48b125d"},
168 {"CVE", "2017-1000405"},
169 {}
170 }
171 };
172