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