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_SOFT_ANON,
33 MADV_SOFT_CLEAN_PAGECACHE,
34 MADV_SOFT_DIRTY_PAGECACHE,
35 };
36
37 static jmp_buf signal_jmp_buf;
38 static siginfo_t siginfo;
39 const char *pagemap_proc = "/proc/self/pagemap";
40 const char *kpageflags_proc = "/proc/kpageflags";
41
FIXTURE(memory_failure)42 FIXTURE(memory_failure)
43 {
44 unsigned long page_size;
45 unsigned long corrupted_size;
46 unsigned long pfn;
47 int pagemap_fd;
48 int kpageflags_fd;
49 bool injection_attempted;
50 };
51
FIXTURE_VARIANT(memory_failure)52 FIXTURE_VARIANT(memory_failure)
53 {
54 enum inject_type type;
55 int (*inject)(FIXTURE_DATA(memory_failure) * self, void *vaddr);
56 };
57
madv_hard_inject(FIXTURE_DATA (memory_failure)* self,void * vaddr)58 static int madv_hard_inject(FIXTURE_DATA(memory_failure) * self, void *vaddr)
59 {
60 return madvise(vaddr, self->page_size, MADV_HWPOISON);
61 }
62
FIXTURE_VARIANT_ADD(memory_failure,madv_hard)63 FIXTURE_VARIANT_ADD(memory_failure, madv_hard)
64 {
65 .type = MADV_HARD,
66 .inject = madv_hard_inject,
67 };
68
madv_soft_inject(FIXTURE_DATA (memory_failure)* self,void * vaddr)69 static int madv_soft_inject(FIXTURE_DATA(memory_failure) * self, void *vaddr)
70 {
71 return madvise(vaddr, self->page_size, MADV_SOFT_OFFLINE);
72 }
73
FIXTURE_VARIANT_ADD(memory_failure,madv_soft)74 FIXTURE_VARIANT_ADD(memory_failure, madv_soft)
75 {
76 .type = MADV_SOFT,
77 .inject = madv_soft_inject,
78 };
79
sigbus_action(int signo,siginfo_t * si,void * args)80 static void sigbus_action(int signo, siginfo_t *si, void *args)
81 {
82 memcpy(&siginfo, si, sizeof(siginfo_t));
83 siglongjmp(signal_jmp_buf, 1);
84 }
85
setup_sighandler(void)86 static int setup_sighandler(void)
87 {
88 struct sigaction sa = {
89 .sa_sigaction = sigbus_action,
90 .sa_flags = SA_SIGINFO,
91 };
92
93 return sigaction(SIGBUS, &sa, NULL);
94 }
95
FIXTURE_SETUP(memory_failure)96 FIXTURE_SETUP(memory_failure)
97 {
98 memset(self, 0, sizeof(*self));
99
100 self->page_size = (unsigned long)sysconf(_SC_PAGESIZE);
101
102 memset(&siginfo, 0, sizeof(siginfo));
103 if (setup_sighandler())
104 SKIP(return, "setup sighandler failed.\n");
105
106 self->pagemap_fd = open(pagemap_proc, O_RDONLY);
107 if (self->pagemap_fd == -1)
108 SKIP(return, "open %s failed.\n", pagemap_proc);
109
110 self->kpageflags_fd = open(kpageflags_proc, O_RDONLY);
111 if (self->kpageflags_fd == -1)
112 SKIP(return, "open %s failed.\n", kpageflags_proc);
113 }
114
teardown_sighandler(void)115 static void teardown_sighandler(void)
116 {
117 struct sigaction sa = {
118 .sa_handler = SIG_DFL,
119 .sa_flags = SA_SIGINFO,
120 };
121
122 sigaction(SIGBUS, &sa, NULL);
123 }
124
prepare(struct __test_metadata * _metadata,FIXTURE_DATA (memory_failure)* self,void * vaddr)125 static void prepare(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self,
126 void *vaddr)
127 {
128 self->pfn = pagemap_get_pfn(self->pagemap_fd, vaddr);
129 ASSERT_NE(self->pfn, -1UL);
130
131 ASSERT_EQ(get_hardware_corrupted_size(&self->corrupted_size), 0);
132 }
133
check_memory(void * vaddr,unsigned long size)134 static bool check_memory(void *vaddr, unsigned long size)
135 {
136 char buf[64];
137
138 memset(buf, 0xce, sizeof(buf));
139 while (size >= sizeof(buf)) {
140 if (memcmp(vaddr, buf, sizeof(buf)))
141 return false;
142 size -= sizeof(buf);
143 vaddr += sizeof(buf);
144 }
145
146 return true;
147 }
148
check(struct __test_metadata * _metadata,FIXTURE_DATA (memory_failure)* self,void * vaddr,enum result_type type,int setjmp)149 static void check(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self,
150 void *vaddr, enum result_type type, int setjmp)
151 {
152 unsigned long size;
153 uint64_t pfn_flags;
154
155 switch (type) {
156 case MADV_SOFT_ANON:
157 case MADV_HARD_CLEAN_PAGECACHE:
158 case MADV_SOFT_CLEAN_PAGECACHE:
159 case MADV_SOFT_DIRTY_PAGECACHE:
160 /* It is not expected to receive a SIGBUS signal. */
161 ASSERT_EQ(setjmp, 0);
162
163 /* The page content should remain unchanged. */
164 ASSERT_TRUE(check_memory(vaddr, self->page_size));
165
166 /* The backing pfn of addr should have changed. */
167 ASSERT_NE(pagemap_get_pfn(self->pagemap_fd, vaddr), self->pfn);
168 break;
169 case MADV_HARD_ANON:
170 case MADV_HARD_DIRTY_PAGECACHE:
171 /* The SIGBUS signal should have been received. */
172 ASSERT_EQ(setjmp, 1);
173
174 /* Check if siginfo contains correct SIGBUS context. */
175 ASSERT_EQ(siginfo.si_signo, SIGBUS);
176 ASSERT_EQ(siginfo.si_code, BUS_MCEERR_AR);
177 ASSERT_EQ(1UL << siginfo.si_addr_lsb, self->page_size);
178 ASSERT_EQ(siginfo.si_addr, vaddr);
179
180 /* XXX Check backing pte is hwpoison entry when supported. */
181 ASSERT_TRUE(pagemap_is_swapped(self->pagemap_fd, vaddr));
182 break;
183 default:
184 SKIP(return, "unexpected inject type %d.\n", type);
185 }
186
187 /* Check if the value of HardwareCorrupted has increased. */
188 ASSERT_EQ(get_hardware_corrupted_size(&size), 0);
189 ASSERT_EQ(size, self->corrupted_size + self->page_size / 1024);
190
191 /* Check if HWPoison flag is set. */
192 ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0);
193 ASSERT_EQ(pfn_flags & KPF_HWPOISON, KPF_HWPOISON);
194 }
195
cleanup(struct __test_metadata * _metadata,FIXTURE_DATA (memory_failure)* self)196 static void cleanup(struct __test_metadata *_metadata, FIXTURE_DATA(memory_failure) * self)
197 {
198 unsigned long size;
199 uint64_t pfn_flags;
200
201 ASSERT_EQ(unpoison_memory(self->pfn), 0);
202
203 /* Check if HWPoison flag is cleared. */
204 ASSERT_EQ(pageflags_get(self->pfn, self->kpageflags_fd, &pfn_flags), 0);
205 ASSERT_NE(pfn_flags & KPF_HWPOISON, KPF_HWPOISON);
206
207 /* Check if the value of HardwareCorrupted has decreased. */
208 ASSERT_EQ(get_hardware_corrupted_size(&size), 0);
209 ASSERT_EQ(size, self->corrupted_size);
210 }
211
FIXTURE_TEARDOWN(memory_failure)212 FIXTURE_TEARDOWN(memory_failure)
213 {
214 /*
215 * Injection may poison the page before failing or delivering SIGBUS, so
216 * clean up after every injection attempt.
217 */
218 if (self->injection_attempted)
219 cleanup(_metadata, self);
220
221 close(self->kpageflags_fd);
222 close(self->pagemap_fd);
223 teardown_sighandler();
224 }
225
TEST_F(memory_failure,anon)226 TEST_F(memory_failure, anon)
227 {
228 char *addr;
229 int ret;
230
231 addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE,
232 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
233 if (addr == MAP_FAILED)
234 SKIP(return, "mmap failed, not enough memory.\n");
235 memset(addr, 0xce, self->page_size);
236
237 prepare(_metadata, self, addr);
238
239 ret = sigsetjmp(signal_jmp_buf, 1);
240 if (!self->injection_attempted) {
241 self->injection_attempted = true;
242 ASSERT_EQ(variant->inject(self, addr), 0);
243 FORCE_READ(*addr);
244 }
245
246 if (variant->type == MADV_HARD)
247 check(_metadata, self, addr, MADV_HARD_ANON, ret);
248 else
249 check(_metadata, self, addr, MADV_SOFT_ANON, ret);
250
251 ASSERT_EQ(munmap(addr, self->page_size), 0);
252 }
253
prepare_file(const char * fname,unsigned long size)254 static int prepare_file(const char *fname, unsigned long size)
255 {
256 int fd;
257
258 fd = open(fname, O_RDWR | O_CREAT, 0664);
259 if (fd >= 0) {
260 unlink(fname);
261 ftruncate(fd, size);
262 }
263 return fd;
264 }
265
266 /* Borrowed from mm/gup_longterm.c. */
get_fs_type(int fd)267 static int get_fs_type(int fd)
268 {
269 struct statfs fs;
270 int ret;
271
272 do {
273 ret = fstatfs(fd, &fs);
274 } while (ret && errno == EINTR);
275
276 return ret ? 0 : (int)fs.f_type;
277 }
278
TEST_F(memory_failure,clean_pagecache)279 TEST_F(memory_failure, clean_pagecache)
280 {
281 int fd;
282 char *addr;
283 int ret;
284 int fs_type;
285
286 fd = prepare_file("./clean-page-cache-test-file", self->page_size);
287 if (fd < 0)
288 SKIP(return, "failed to open test file.\n");
289 fs_type = get_fs_type(fd);
290 if (!fs_type || fs_type == TMPFS_MAGIC) {
291 close(fd);
292 SKIP(return, "unsupported filesystem :%x\n", fs_type);
293 }
294
295 addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE,
296 MAP_SHARED, fd, 0);
297 if (addr == MAP_FAILED)
298 SKIP(return, "mmap failed, not enough memory.\n");
299 memset(addr, 0xce, self->page_size);
300 fsync(fd);
301
302 prepare(_metadata, self, addr);
303
304 ret = sigsetjmp(signal_jmp_buf, 1);
305 if (!self->injection_attempted) {
306 self->injection_attempted = true;
307 ASSERT_EQ(variant->inject(self, addr), 0);
308 FORCE_READ(*addr);
309 }
310
311 if (variant->type == MADV_HARD)
312 check(_metadata, self, addr, MADV_HARD_CLEAN_PAGECACHE, ret);
313 else
314 check(_metadata, self, addr, MADV_SOFT_CLEAN_PAGECACHE, ret);
315
316 ASSERT_EQ(munmap(addr, self->page_size), 0);
317
318 ASSERT_EQ(close(fd), 0);
319 }
320
TEST_F(memory_failure,dirty_pagecache)321 TEST_F(memory_failure, dirty_pagecache)
322 {
323 int fd;
324 char *addr;
325 int ret;
326 int fs_type;
327
328 fd = prepare_file("./dirty-page-cache-test-file", self->page_size);
329 if (fd < 0)
330 SKIP(return, "failed to open test file.\n");
331 fs_type = get_fs_type(fd);
332 /*
333 * MADV_HARD poisoning of dirty page-cache data records an expected
334 * -EIO in the file mapping. NFS reports this error on close(), so
335 * skip this variant.
336 */
337 if (!fs_type || fs_type == TMPFS_MAGIC ||
338 (fs_type == NFS_SUPER_MAGIC && variant->type == MADV_HARD)) {
339 close(fd);
340 SKIP(return, "unsupported filesystem :%x\n", fs_type);
341 }
342
343 addr = mmap(0, self->page_size, PROT_READ | PROT_WRITE,
344 MAP_SHARED, fd, 0);
345 if (addr == MAP_FAILED)
346 SKIP(return, "mmap failed, not enough memory.\n");
347 memset(addr, 0xce, self->page_size);
348
349 prepare(_metadata, self, addr);
350
351 ret = sigsetjmp(signal_jmp_buf, 1);
352 if (!self->injection_attempted) {
353 self->injection_attempted = true;
354 ASSERT_EQ(variant->inject(self, addr), 0);
355 FORCE_READ(*addr);
356 }
357
358 if (variant->type == MADV_HARD)
359 check(_metadata, self, addr, MADV_HARD_DIRTY_PAGECACHE, ret);
360 else
361 check(_metadata, self, addr, MADV_SOFT_DIRTY_PAGECACHE, ret);
362
363 ASSERT_EQ(munmap(addr, self->page_size), 0);
364
365 ASSERT_EQ(close(fd), 0);
366 }
367
368 TEST_HARNESS_MAIN
369