• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (c) 2019 SUSE LLC <mdoucha@suse.cz>
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 <kirill.shutemov@linux.intel.com>
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 <sys/mman.h>
30 
31 #include "tst_test.h"
32 #include "lapi/mmap.h"
33 #include "tst_fuzzy_sync.h"
34 
35 static char *write_thp, *read_thp;
36 static int *write_ptr, *read_ptr;
37 static size_t thp_size;
38 static int writefd = -1, readfd = -1;
39 static struct tst_fzsync_pair fzsync_pair;
40 
alloc_zero_page(void * baseaddr)41 static void *alloc_zero_page(void *baseaddr)
42 {
43 	int i;
44 	void *ret;
45 
46 	/* Find aligned chunk of address space. MAP_HUGETLB doesn't work. */
47 	for (i = 0; i < 16; i++, baseaddr += thp_size) {
48 		ret = mmap(baseaddr, thp_size, PROT_READ,
49 			MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
50 
51 		if (ret == baseaddr) {
52 			TEST(madvise(ret, thp_size, MADV_HUGEPAGE));
53 
54 			if (TST_RET == -1 && TST_ERR == EINVAL) {
55 				tst_brk(TCONF | TTERRNO,
56 					"madvise(MADV_HUGEPAGE) not supported");
57 			}
58 
59 			if (TST_RET) {
60 				tst_brk(TBROK | TTERRNO,
61 					"madvise(MADV_HUGEPAGE) failed");
62 			}
63 
64 			return ret;
65 		}
66 
67 		if (ret != MAP_FAILED)
68 			SAFE_MUNMAP(ret, thp_size);
69 	}
70 
71 	tst_brk(TBROK, "Cannot map huge zero page near the specified address");
72 	return NULL;	/* Silence compiler warning */
73 }
74 
setup(void)75 static void setup(void)
76 {
77 	size_t i;
78 
79 	thp_size = tst_get_hugepage_size();
80 
81 	if (!thp_size)
82 		tst_brk(TCONF, "Kernel does not support huge pages");
83 
84 	write_thp = alloc_zero_page((void *)thp_size);
85 
86 	for (i = 0; i < thp_size; i++) {
87 		if (write_thp[i])
88 			tst_brk(TCONF, "Huge zero page is pre-polluted");
89 	}
90 
91 	/* leave a hole between read and write THP to prevent merge */
92 	read_thp = alloc_zero_page(write_thp + 2 * thp_size);
93 	write_ptr = (int *)(write_thp + thp_size - sizeof(int));
94 	read_ptr = (int *)(read_thp + thp_size - sizeof(int));
95 	writefd = SAFE_OPEN("/proc/self/mem", O_RDWR);
96 	readfd = SAFE_OPEN("/proc/self/mem", O_RDWR);
97 
98 	fzsync_pair.exec_loops = 100000;
99 	tst_fzsync_pair_init(&fzsync_pair);
100 }
101 
thread_run(void * arg)102 static void *thread_run(void *arg)
103 {
104 	int c;
105 
106 	while (tst_fzsync_run_b(&fzsync_pair)) {
107 		tst_fzsync_start_race_b(&fzsync_pair);
108 		madvise(write_thp, thp_size, MADV_DONTNEED);
109 		memcpy(&c, write_ptr, sizeof(c));
110 		SAFE_LSEEK(readfd, (off_t)write_ptr, SEEK_SET);
111 		SAFE_READ(1, readfd, &c, sizeof(int));
112 		tst_fzsync_end_race_b(&fzsync_pair);
113 		/* Wait for dirty page handling before next madvise() */
114 		usleep(10);
115 	}
116 
117 	return arg;
118 }
119 
run(void)120 static void run(void)
121 {
122 	int c = 0xdeadbeef;
123 
124 	tst_fzsync_pair_reset(&fzsync_pair, thread_run);
125 
126 	while (tst_fzsync_run_a(&fzsync_pair)) {
127 		/* Write into the main huge page */
128 		tst_fzsync_start_race_a(&fzsync_pair);
129 		SAFE_LSEEK(writefd, (off_t)write_ptr, SEEK_SET);
130 		madvise(write_thp, thp_size, MADV_DONTNEED);
131 		SAFE_WRITE(1, writefd, &c, sizeof(int));
132 		tst_fzsync_end_race_a(&fzsync_pair);
133 
134 		/* Check the other huge zero page for pollution */
135 		madvise(read_thp, thp_size, MADV_DONTNEED);
136 
137 		if (*read_ptr != 0) {
138 			tst_res(TFAIL, "Huge zero page was polluted");
139 			return;
140 		}
141 	}
142 
143 	tst_res(TPASS, "Huge zero page is still clean");
144 }
145 
cleanup(void)146 static void cleanup(void)
147 {
148 	tst_fzsync_pair_cleanup(&fzsync_pair);
149 
150 	if (readfd >= 0)
151 		SAFE_CLOSE(readfd);
152 
153 	if (writefd >= 0)
154 		SAFE_CLOSE(writefd);
155 
156 	if (read_thp)
157 		SAFE_MUNMAP(read_thp, thp_size);
158 	if (write_thp)
159 		SAFE_MUNMAP(write_thp, thp_size);
160 }
161 
162 static struct tst_test test = {
163 	.test_all = run,
164 	.setup = setup,
165 	.cleanup = cleanup,
166 	.tags = (const struct tst_tag[]) {
167 		{"linux-git", "a8f97366452e"},
168 		{"linux-git", "8310d48b125d"},
169 		{"CVE", "2017-1000405"},
170 		{}
171 	}
172 };
173