xref: /linux/tools/testing/selftests/mm/memory-failure.c (revision 69567ac40ee3b29d95cccc721bb08aa211cc3292)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Memory-failure functional tests.
4  *
5  * Author(s): Miaohe Lin <linmiaohe@huawei.com>
6  */
7 
8 #include "../kselftest_harness.h"
9 
10 #include <sys/mman.h>
11 #include <linux/mman.h>
12 #include <linux/string.h>
13 #include <unistd.h>
14 #include <signal.h>
15 #include <setjmp.h>
16 #include <fcntl.h>
17 #include <sys/vfs.h>
18 #include <linux/magic.h>
19 #include <errno.h>
20 
21 #include "vm_util.h"
22 
23 enum inject_type {
24 	MADV_HARD,
25 	MADV_SOFT,
26 };
27 
28 enum result_type {
29 	MADV_HARD_ANON,
30 	MADV_HARD_CLEAN_PAGECACHE,
31 	MADV_HARD_DIRTY_PAGECACHE,
32 	MADV_HARD_CLEAN_SHMEM,
33 	MADV_HARD_DIRTY_SHMEM,
34 	MADV_SOFT_ANON,
35 	MADV_SOFT_CLEAN_PAGECACHE,
36 	MADV_SOFT_DIRTY_PAGECACHE,
37 	MADV_SOFT_CLEAN_SHMEM,
38 	MADV_SOFT_DIRTY_SHMEM,
39 	READ_ERROR,
40 };
41 
42 static jmp_buf signal_jmp_buf;
43 static siginfo_t siginfo;
44 const char *pagemap_proc = "/proc/self/pagemap";
45 const char *kpageflags_proc = "/proc/kpageflags";
46 
47 FIXTURE(memory_failure)
48 {
49 	unsigned long page_size;
50 	unsigned long corrupted_size;
51 	unsigned long pfn;
52 	int pagemap_fd;
53 	int kpageflags_fd;
54 	bool injection_attempted;
55 };
56 
57 FIXTURE_VARIANT(memory_failure)
58 {
59 	enum inject_type type;
60 	int (*inject)(FIXTURE_DATA(memory_failure) * self, void *vaddr);
61 };
62 
63 static int madv_hard_inject(FIXTURE_DATA(memory_failure) * self, void *vaddr)
64 {
65 	return madvise(vaddr, self->page_size, MADV_HWPOISON);
66 }
67 
68 FIXTURE_VARIANT_ADD(memory_failure, madv_hard)
69 {
70 	.type = MADV_HARD,
71 	.inject = madv_hard_inject,
72 };
73 
74 static int madv_soft_inject(FIXTURE_DATA(memory_failure) * self, void *vaddr)
75 {
76 	return madvise(vaddr, self->page_size, MADV_SOFT_OFFLINE);
77 }
78 
79 FIXTURE_VARIANT_ADD(memory_failure, madv_soft)
80 {
81 	.type = MADV_SOFT,
82 	.inject = madv_soft_inject,
83 };
84 
85 static void sigbus_action(int signo, siginfo_t *si, void *args)
86 {
87 	memcpy(&siginfo, si, sizeof(siginfo_t));
88 	siglongjmp(signal_jmp_buf, 1);
89 }
90 
91 static int setup_sighandler(void)
92 {
93 	struct sigaction sa = {
94 		.sa_sigaction = sigbus_action,
95 		.sa_flags = SA_SIGINFO,
96 	};
97 
98 	return sigaction(SIGBUS, &sa, NULL);
99 }
100 
101 FIXTURE_SETUP(memory_failure)
102 {
103 	memset(self, 0, sizeof(*self));
104 
105 	self->page_size = (unsigned long)sysconf(_SC_PAGESIZE);
106 
107 	memset(&siginfo, 0, sizeof(siginfo));
108 	if (setup_sighandler())
109 		SKIP(return, "setup sighandler failed.\n");
110 
111 	self->pagemap_fd = open(pagemap_proc, O_RDONLY);
112 	if (self->pagemap_fd == -1)
113 		SKIP(return, "open %s failed.\n", pagemap_proc);
114 
115 	self->kpageflags_fd = open(kpageflags_proc, O_RDONLY);
116 	if (self->kpageflags_fd == -1)
117 		SKIP(return, "open %s failed.\n", kpageflags_proc);
118 }
119 
120 static void teardown_sighandler(void)
121 {
122 	struct sigaction sa = {
123 		.sa_handler = SIG_DFL,
124 		.sa_flags = SA_SIGINFO,
125 	};
126 
127 	sigaction(SIGBUS, &sa, NULL);
128 }
129 
130 static void prepare(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self,
131 		    void *vaddr)
132 {
133 	self->pfn = pagemap_get_pfn(self->pagemap_fd, vaddr);
134 	ASSERT_NE(self->pfn, -1UL);
135 
136 	ASSERT_EQ(get_hardware_corrupted_size(&self->corrupted_size), 0);
137 }
138 
139 static bool check_memory(void *vaddr, unsigned long size)
140 {
141 	char buf[64];
142 
143 	memset(buf, 0xce, sizeof(buf));
144 	while (size >= sizeof(buf)) {
145 		if (memcmp(vaddr, buf, sizeof(buf)))
146 			return false;
147 		size -= sizeof(buf);
148 		vaddr += sizeof(buf);
149 	}
150 
151 	return true;
152 }
153 
154 static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self,
155 		  void *vaddr, enum result_type type, int setjmp)
156 {
157 	unsigned long size;
158 	uint64_t pfn_flags;
159 
160 	switch (type) {
161 	case MADV_SOFT_ANON:
162 	case MADV_HARD_CLEAN_PAGECACHE:
163 	case MADV_SOFT_CLEAN_PAGECACHE:
164 	case MADV_SOFT_DIRTY_PAGECACHE:
165 	case MADV_SOFT_DIRTY_SHMEM:
166 		/* The page content should remain unchanged. */
167 		ASSERT_TRUE(check_memory(vaddr, self->page_size));
168 		/* FALLTHROUGH */
169 	case MADV_HARD_CLEAN_SHMEM:
170 	case MADV_SOFT_CLEAN_SHMEM:
171 		/* It is not expected to receive a SIGBUS signal. */
172 		ASSERT_EQ(setjmp, 0);
173 
174 		/* The backing pfn of addr should have changed. */
175 		ASSERT_NE(pagemap_get_pfn(self->pagemap_fd, vaddr), self->pfn);
176 		break;
177 	case MADV_HARD_ANON:
178 	case MADV_HARD_DIRTY_PAGECACHE:
179 	case MADV_HARD_DIRTY_SHMEM:
180 	case READ_ERROR:
181 		/* The SIGBUS signal should have been received. */
182 		ASSERT_EQ(setjmp, 1);
183 
184 		/* Check if siginfo contains correct SIGBUS context. */
185 		ASSERT_EQ(siginfo.si_signo, SIGBUS);
186 		ASSERT_EQ(siginfo.si_code, BUS_MCEERR_AR);
187 		ASSERT_EQ(1UL << siginfo.si_addr_lsb, self->page_size);
188 		ASSERT_EQ(siginfo.si_addr, vaddr);
189 
190 		/* XXX Check backing pte is hwpoison entry when supported. */
191 		ASSERT_TRUE(pagemap_is_swapped(self->pagemap_fd, vaddr));
192 		break;
193 	default:
194 		SKIP(return, "unexpected inject type %d.\n", type);
195 	}
196 
197 	/* Check if the value of HardwareCorrupted has increased. */
198 	ASSERT_EQ(get_hardware_corrupted_size(&size), 0);
199 	ASSERT_EQ(size, self->corrupted_size + self->page_size / 1024);
200 
201 	/* Check if HWPoison flag is set. */
202 	ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0);
203 	ASSERT_EQ(pfn_flags & KPF_HWPOISON, KPF_HWPOISON);
204 }
205 
206 static void cleanup(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self)
207 {
208 	unsigned long size;
209 	uint64_t pfn_flags;
210 
211 	ASSERT_EQ(unpoison_memory(self->pfn), 0);
212 
213 	/* Check if HWPoison flag is cleared. */
214 	ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0);
215 	ASSERT_NE(pfn_flags & KPF_HWPOISON, KPF_HWPOISON);
216 
217 	/* Check if the value of HardwareCorrupted has decreased. */
218 	ASSERT_EQ(get_hardware_corrupted_size(&size), 0);
219 	ASSERT_EQ(size, self->corrupted_size);
220 }
221 
222 FIXTURE_TEARDOWN(memory_failure)
223 {
224 	/*
225 	 * Injection may poison the page before failing or delivering SIGBUS, so
226 	 * clean up after every injection attempt.
227 	 */
228 	if (self->injection_attempted)
229 		cleanup(_metadata, self);
230 
231 	close(self->kpageflags_fd);
232 	close(self->pagemap_fd);
233 	teardown_sighandler();
234 }
235 
236 TEST_F(memory_failure, anon)
237 {
238 	char *addr;
239 	int ret;
240 
241 	addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE,
242 		    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
243 	if (addr == MAP_FAILED)
244 		SKIP(return, "mmap failed, not enough memory.\n");
245 	memset(addr, 0xce, self->page_size);
246 
247 	prepare(_metadata, self, addr);
248 
249 	ret = sigsetjmp(signal_jmp_buf, 1);
250 	if (!self->injection_attempted) {
251 		self->injection_attempted = true;
252 		ASSERT_EQ(variant->inject(self, addr), 0);
253 		FORCE_READ(*addr);
254 	}
255 
256 	if (variant->type == MADV_HARD)
257 		check(_metadata, self, addr, MADV_HARD_ANON, ret);
258 	else
259 		check(_metadata, self, addr, MADV_SOFT_ANON, ret);
260 
261 	ASSERT_EQ(munmap(addr, self->page_size), 0);
262 }
263 
264 static int prepare_file(const char *fname, unsigned long size)
265 {
266 	int fd;
267 
268 	fd = open(fname, O_RDWR | O_CREAT, 0664);
269 	if (fd >= 0) {
270 		unlink(fname);
271 		ftruncate(fd, size);
272 	}
273 	return fd;
274 }
275 
276 static int prepare_shmem(const char *fname, unsigned long size)
277 {
278 	int fd;
279 
280 	fd = memfd_create(fname, 0);
281 	if (fd < 0)
282 		return -1;
283 	if (ftruncate(fd, size) < 0) {
284 		close(fd);
285 		return -1;
286 	}
287 	return fd;
288 }
289 
290 /* Borrowed from mm/gup_longterm.c. */
291 static int get_fs_type(int fd)
292 {
293 	struct statfs fs;
294 	int ret;
295 
296 	do {
297 		ret = fstatfs(fd, &fs);
298 	} while (ret && errno == EINTR);
299 
300 	return ret ? 0 : (int)fs.f_type;
301 }
302 
303 TEST_F(memory_failure, clean_pagecache)
304 {
305 	int fd;
306 	char *addr;
307 	int ret;
308 	int fs_type;
309 
310 	fd = prepare_file("./clean-page-cache-test-file", self->page_size);
311 	if (fd < 0)
312 		SKIP(return, "failed to open test file.\n");
313 	fs_type = get_fs_type(fd);
314 	if (!fs_type || fs_type == TMPFS_MAGIC) {
315 		close(fd);
316 		SKIP(return, "unsupported filesystem :%x\n", fs_type);
317 	}
318 
319 	addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE,
320 		    MAP_SHARED, fd, 0);
321 	if (addr == MAP_FAILED)
322 		SKIP(return, "mmap failed, not enough memory.\n");
323 	memset(addr, 0xce, self->page_size);
324 	fsync(fd);
325 
326 	prepare(_metadata, self, addr);
327 
328 	ret = sigsetjmp(signal_jmp_buf, 1);
329 	if (!self->injection_attempted) {
330 		self->injection_attempted = true;
331 		ASSERT_EQ(variant->inject(self, addr), 0);
332 		FORCE_READ(*addr);
333 	}
334 
335 	if (variant->type == MADV_HARD)
336 		check(_metadata, self, addr, MADV_HARD_CLEAN_PAGECACHE, ret);
337 	else
338 		check(_metadata, self, addr, MADV_SOFT_CLEAN_PAGECACHE, ret);
339 
340 	ASSERT_EQ(munmap(addr, self->page_size), 0);
341 
342 	ASSERT_EQ(close(fd), 0);
343 }
344 
345 TEST_F(memory_failure, dirty_pagecache)
346 {
347 	int fd;
348 	char *addr;
349 	int ret;
350 	int fs_type;
351 
352 	fd = prepare_file("./dirty-page-cache-test-file", self->page_size);
353 	if (fd < 0)
354 		SKIP(return, "failed to open test file.\n");
355 	fs_type = get_fs_type(fd);
356 	/*
357 	 * MADV_HARD poisoning of dirty page-cache data records an expected
358 	 * -EIO in the file mapping. NFS reports this error on close(), so
359 	 * skip this variant.
360 	 */
361 	if (!fs_type || fs_type == TMPFS_MAGIC ||
362 	    (fs_type == NFS_SUPER_MAGIC && variant->type == MADV_HARD)) {
363 		close(fd);
364 		SKIP(return, "unsupported filesystem :%x\n", fs_type);
365 	}
366 
367 	addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE,
368 		    MAP_SHARED, fd, 0);
369 	if (addr == MAP_FAILED)
370 		SKIP(return, "mmap failed, not enough memory.\n");
371 	memset(addr, 0xce, self->page_size);
372 
373 	prepare(_metadata, self, addr);
374 
375 	ret = sigsetjmp(signal_jmp_buf, 1);
376 	if (!self->injection_attempted) {
377 		self->injection_attempted = true;
378 		ASSERT_EQ(variant->inject(self, addr), 0);
379 		FORCE_READ(*addr);
380 	}
381 
382 	if (variant->type == MADV_HARD)
383 		check(_metadata, self, addr, MADV_HARD_DIRTY_PAGECACHE, ret);
384 	else
385 		check(_metadata, self, addr, MADV_SOFT_DIRTY_PAGECACHE, ret);
386 
387 	ASSERT_EQ(munmap(addr, self->page_size), 0);
388 
389 	ASSERT_EQ(close(fd), 0);
390 }
391 
392 TEST_F(memory_failure, dirty_shmem)
393 {
394 	int fd;
395 	char *addr;
396 	int ret;
397 
398 	fd = prepare_shmem("shmem-file", self->page_size);
399 	if (fd < 0)
400 		SKIP(return, "failed to open test shmem-file.\n");
401 
402 	addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE,
403 		    MAP_SHARED, fd, 0);
404 	if (addr == MAP_FAILED) {
405 		close(fd);
406 		SKIP(return, "mmap failed, not enough memory.\n");
407 	}
408 	memset(addr, 0xce, self->page_size);
409 
410 	prepare(_metadata, self, addr);
411 
412 	ret = sigsetjmp(signal_jmp_buf, 1);
413 	if (!ret && !self->injection_attempted) {
414 		self->injection_attempted = true;
415 		ASSERT_EQ(variant->inject(self, addr), 0);
416 	}
417 
418 	if (variant->type == MADV_HARD) {
419 		check(_metadata, self, addr, MADV_HARD_DIRTY_SHMEM, ret);
420 		ret = sigsetjmp(signal_jmp_buf, 1);
421 		if (ret == 0)
422 			FORCE_READ(*addr);
423 		check(_metadata, self, addr, READ_ERROR, ret);
424 	} else {
425 		check(_metadata, self, addr, MADV_SOFT_DIRTY_SHMEM, ret);
426 	}
427 
428 	ASSERT_EQ(munmap(addr, self->page_size), 0);
429 
430 	ASSERT_EQ(close(fd), 0);
431 }
432 
433 TEST_F(memory_failure, clean_shmem)
434 {
435 	int fd;
436 	char *addr;
437 	int ret;
438 
439 	fd = prepare_shmem("shmem-file", self->page_size);
440 	if (fd < 0)
441 		SKIP(return, "failed to open test shmem-file.\n");
442 
443 	addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE,
444 		    MAP_SHARED, fd, 0);
445 	if (addr == MAP_FAILED) {
446 		close(fd);
447 		SKIP(return, "mmap failed, not enough memory.\n");
448 	}
449 	FORCE_READ(*addr);
450 
451 	prepare(_metadata, self, addr);
452 
453 	ret = sigsetjmp(signal_jmp_buf, 1);
454 	if (!ret && !self->injection_attempted) {
455 		self->injection_attempted = true;
456 		ASSERT_EQ(variant->inject(self, addr), 0);
457 	}
458 
459 	if (variant->type == MADV_HARD) {
460 		check(_metadata, self, addr, MADV_HARD_CLEAN_SHMEM, ret);
461 		ret = sigsetjmp(signal_jmp_buf, 1);
462 		if (ret == 0)
463 			FORCE_READ(*addr);
464 		check(_metadata, self, addr, READ_ERROR, ret);
465 	} else {
466 		/* Test the address accessability without check_memory(). */
467 		FORCE_READ(*addr);
468 		check(_metadata, self, addr, MADV_SOFT_CLEAN_SHMEM, ret);
469 	}
470 
471 	ASSERT_EQ(munmap(addr, self->page_size), 0);
472 
473 	ASSERT_EQ(close(fd), 0);
474 }
475 
476 TEST_HARNESS_MAIN
477