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 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 52 FIXTURE_VARIANT(memory_failure) 53 { 54 enum inject_type type; 55 int (*inject)(FIXTURE_DATA(memory_failure) * self, void *vaddr); 56 }; 57 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 63 FIXTURE_VARIANT_ADD(memory_failure, madv_hard) 64 { 65 .type = MADV_HARD, 66 .inject = madv_hard_inject, 67 }; 68 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 74 FIXTURE_VARIANT_ADD(memory_failure, madv_soft) 75 { 76 .type = MADV_SOFT, 77 .inject = madv_soft_inject, 78 }; 79 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 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 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 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 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 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 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 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 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 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 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. */ 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 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 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