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