• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 /*
3  * Copyright (C) 2005-2006 David Gibson & Adam Litke, IBM Corporation.
4  * Copyright (c) Linux Test Project, 2022-2023
5  * Author: David Gibson & Adam Litke
6  */
7 
8 /*\
9  * [Description]
10  *
11  * Older ppc64 kernels don't properly flush dcache to icache before
12  * giving a cleared page to userspace.  With some exceedingly
13  * hairy code, this attempts to test for this bug.
14  *
15  * This test will never trigger (obviously) on machines with coherent
16  * icache and dcache (including x86 and POWER5).  On any given run,
17  * even on a buggy kernel there's a chance the bug won't trigger -
18  * either because we don't get the same physical page back when we
19  * remap, or because the icache happens to get flushed in the interim.
20  */
21 
22 #if defined(__clang__)
23 	#pragma clang optimize off
24 #endif
25 
26 #define _GNU_SOURCE
27 #include "hugetlb.h"
28 
29 #if defined(__powerpc__) || defined(__powerpc64__) || defined(__ia64__) || \
30 	defined(__s390__) || defined(__s390x__) || defined(__sparc__) || \
31 	defined(__aarch64__) || (defined(__riscv) && __riscv_xlen == 64) || \
32 	defined(__i386__) || defined(__x86_64__) || defined(__arm__)
33 
34 #include <setjmp.h>
35 
36 #define SUCC_JMP 1
37 #define FAIL_JMP 2
38 #define COPY_SIZE	128
39 
40 /* Seems to be enough to trigger reliably */
41 #define NUM_REPETITIONS	64
42 #define MNTPOINT "hugetlbfs/"
43 static long hpage_size;
44 static int  fd = -1;
45 
cacheflush(void * p)46 static void cacheflush(void *p)
47 {
48 #if defined(__powerpc__)
49 	asm volatile("dcbst 0,%0; sync; icbi 0,%0; isync" : : "r"(p));
50 #elif defined(__arm__) || defined(__aarch64__)
51 	__clear_cache(p, p + COPY_SIZE);
52 #else
53 	(void)p;
54 #endif
55 }
56 
jumpfunc(int copy,void * p)57 static void jumpfunc(int copy, void *p)
58 {
59 	/*
60 	 * gcc bug workaround: if there is exactly one &&label
61 	 * construct in the function, gcc assumes the computed goto
62 	 * goes there, leading to the complete elision of the goto in
63 	 * this case
64 	 */
65 	void *l = &&dummy;
66 
67 	l = &&jumplabel;
68 
69 	if (copy) {
70 		memcpy(p, l, COPY_SIZE);
71 		cacheflush(p);
72 	}
73 
74 	goto *p;
75  dummy:
76 	tst_res(TWARN, "unreachable?");
77 
78  jumplabel:
79 	return;
80 }
81 
82 static sigjmp_buf sig_escape;
83 static void *sig_expected;
84 
sig_handler(int signum,siginfo_t * si,void * uc)85 static void sig_handler(int signum, siginfo_t *si, void *uc)
86 {
87 #if defined(__powerpc__) || defined(__powerpc64__) || defined(__ia64__) || \
88 	defined(__s390__) || defined(__s390x__) || defined(__sparc__) || \
89 	defined(__aarch64__) || (defined(__riscv) && __riscv_xlen == 64)
90 	/* On powerpc, ia64, s390 and Aarch64, 0 bytes are an illegal
91 	 * instruction, so, if the icache is cleared properly, we SIGILL
92 	 * as soon as we jump into the cleared page
93 	 */
94 	if (signum == SIGILL) {
95 		tst_res(TINFO, "SIGILL at %p (sig_expected=%p)", si->si_addr,
96 				sig_expected);
97 		if (si->si_addr == sig_expected)
98 			siglongjmp(sig_escape, SUCC_JMP);
99 		siglongjmp(sig_escape, FAIL_JMP + SIGILL);
100 	}
101 #elif defined(__i386__) || defined(__x86_64__) || defined(__arm__)
102 	/* On x86, zero bytes form a valid instruction:
103 	 *	add %al,(%eax)		(i386)
104 	 * or	add %al,(%rax)		(x86_64)
105 	 *
106 	 * So, behaviour depends on the contents of [ER]AX, which in
107 	 * turn depends on the details of code generation.  If [ER]AX
108 	 * contains a valid pointer, we will execute the instruction
109 	 * repeatedly until we run off that hugepage and get a SIGBUS
110 	 * on the second, truncated page.  If [ER]AX does not contain
111 	 * a valid pointer, we will SEGV on the first instruction in
112 	 * the cleared page.  We check for both possibilities
113 	 * below.
114 	 *
115 	 * On 32 bit ARM, zero bytes are interpreted as follows:
116 	 *  andeq	r0, r0, r0	(ARM state, 4 bytes)
117 	 *  movs	r0, r0		(Thumb state, 2 bytes)
118 	 *
119 	 * So, we only expect to run off the end of the huge page and
120 	 * generate a SIGBUS.
121 	 */
122 	if (signum == SIGBUS) {
123 		tst_res(TINFO, "SIGBUS at %p (sig_expected=%p)", si->si_addr,
124 				sig_expected);
125 		if (sig_expected
126 		    && (PALIGN(sig_expected, hpage_size)
127 			== si->si_addr)) {
128 			siglongjmp(sig_escape, SUCC_JMP);
129 		}
130 		siglongjmp(sig_escape, FAIL_JMP + SIGBUS);
131 	}
132 #if defined(__x86_64__) || defined(__i386__)
133 	if (signum == SIGSEGV) {
134 #ifdef __x86_64__
135 		void *pc = (void *)((ucontext_t *)uc)->uc_mcontext.gregs[REG_RIP];
136 #else
137 		void *pc = (void *)((ucontext_t *)uc)->uc_mcontext.gregs[REG_EIP];
138 #endif
139 		tst_res(TINFO, "SIGSEGV at %p, PC=%p (sig_expected=%p)",
140 				si->si_addr, pc, sig_expected);
141 		if (sig_expected == pc)
142 			siglongjmp(sig_escape, SUCC_JMP);
143 		siglongjmp(sig_escape, FAIL_JMP + SIGSEGV);
144 	}
145 #endif
146 #endif
147 }
148 
test_once(int fd)149 static int test_once(int fd)
150 {
151 	void *p, *q;
152 
153 	SAFE_FTRUNCATE(fd, 0);
154 
155 	switch (sigsetjmp(sig_escape, 1)) {
156 	case SUCC_JMP:
157 		sig_expected = NULL;
158 		SAFE_FTRUNCATE(fd, 0);
159 		return 0;
160 	case FAIL_JMP + SIGILL:
161 		tst_res(TFAIL, "SIGILL somewhere unexpected");
162 		return -1;
163 	case FAIL_JMP + SIGBUS:
164 		tst_res(TFAIL, "SIGBUS somewhere unexpected");
165 		return -1;
166 	case FAIL_JMP + SIGSEGV:
167 		tst_res(TFAIL, "SIGSEGV somewhere unexpected");
168 		return -1;
169 	default:
170 		break;
171 	}
172 	p = SAFE_MMAP(NULL, 2*hpage_size, PROT_READ|PROT_WRITE|PROT_EXEC,
173 		 MAP_SHARED, fd, 0);
174 
175 	SAFE_FTRUNCATE(fd, hpage_size);
176 
177 	q = p + hpage_size - COPY_SIZE;
178 
179 	jumpfunc(1, q);
180 
181 	SAFE_FTRUNCATE(fd, 0);
182 	p = SAFE_MMAP(p, hpage_size, PROT_READ|PROT_WRITE|PROT_EXEC,
183 		 MAP_SHARED|MAP_FIXED, fd, 0);
184 
185 	q = p + hpage_size - COPY_SIZE;
186 	sig_expected = q;
187 
188 	jumpfunc(0, q); /* This should blow up */
189 
190 	tst_res(TFAIL, "icache unclean");
191 	return -1;
192 }
193 
run_test(void)194 static void run_test(void)
195 {
196 	int i;
197 
198 	struct sigaction sa = {
199 		.sa_sigaction = sig_handler,
200 		.sa_flags = SA_SIGINFO,
201 	};
202 
203 	SAFE_SIGACTION(SIGILL, &sa, NULL);
204 	SAFE_SIGACTION(SIGBUS, &sa, NULL);
205 	SAFE_SIGACTION(SIGSEGV, &sa, NULL);
206 
207 	fd = tst_creat_unlinked(MNTPOINT, 0);
208 
209 	for (i = 0; i < NUM_REPETITIONS; i++)
210 		if (test_once(fd))
211 			goto cleanup;
212 
213 	tst_res(TPASS, "Successfully tested dcache to icache flush");
214 cleanup:
215 	SAFE_CLOSE(fd);
216 }
217 
setup(void)218 static void setup(void)
219 {
220 	hpage_size = SAFE_READ_MEMINFO("Hugepagesize:")*1024;
221 }
222 
cleanup(void)223 static void cleanup(void)
224 {
225 	if (fd > 0)
226 		SAFE_CLOSE(fd);
227 }
228 
229 static struct tst_test test = {
230 	.tags = (struct tst_tag[]) {
231 		{"linux-git", "cbf52afdc0eb"},
232 		{}
233 	},
234 	.needs_root = 1,
235 	.mntpoint = MNTPOINT,
236 	.needs_hugetlbfs = 1,
237 	.needs_tmpdir = 1,
238 	.setup = setup,
239 	.cleanup = cleanup,
240 	.test_all = run_test,
241 	.hugepages = {3, TST_NEEDS},
242 };
243 #else
244 	TST_TEST_TCONF("Signal handler for this architecture hasn't been written");
245 #endif
246