1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Basic VM_PFNMAP tests relying on mmap() of input file provided. 4 * Use '/dev/mem' as default. 5 * 6 * Copyright 2025, Red Hat, Inc. 7 * 8 * Author(s): David Hildenbrand <david@redhat.com> 9 */ 10 #define _GNU_SOURCE 11 #include <stdlib.h> 12 #include <string.h> 13 #include <stdint.h> 14 #include <unistd.h> 15 #include <errno.h> 16 #include <stdio.h> 17 #include <ctype.h> 18 #include <fcntl.h> 19 #include <signal.h> 20 #include <setjmp.h> 21 #include <linux/mman.h> 22 #include <sys/mman.h> 23 #include <sys/wait.h> 24 25 #include "../kselftest_harness.h" 26 #include "vm_util.h" 27 28 static sigjmp_buf sigjmp_buf_env; 29 static char *file = "/dev/mem"; 30 31 static void signal_handler(int sig) 32 { 33 siglongjmp(sigjmp_buf_env, -EFAULT); 34 } 35 36 static int test_read_access(char *addr, size_t size, size_t pagesize) 37 { 38 size_t offs; 39 int ret; 40 41 if (signal(SIGSEGV, signal_handler) == SIG_ERR) 42 return -EINVAL; 43 44 ret = sigsetjmp(sigjmp_buf_env, 1); 45 if (!ret) { 46 for (offs = 0; offs < size; offs += pagesize) 47 /* Force a read that the compiler cannot optimize out. */ 48 *((volatile char *)(addr + offs)); 49 } 50 if (signal(SIGSEGV, SIG_DFL) == SIG_ERR) 51 return -EINVAL; 52 53 return ret; 54 } 55 56 static int find_ram_target(off_t *offset, 57 unsigned long long pagesize) 58 { 59 unsigned long long start, end; 60 char line[80], *end_ptr; 61 FILE *file; 62 63 /* Search /proc/iomem for the first suitable "System RAM" range. */ 64 file = fopen("/proc/iomem", "r"); 65 if (!file) 66 return -errno; 67 68 while (fgets(line, sizeof(line), file)) { 69 /* Ignore any child nodes. */ 70 if (!isalnum(line[0])) 71 continue; 72 73 if (!strstr(line, "System RAM\n")) 74 continue; 75 76 start = strtoull(line, &end_ptr, 16); 77 /* Skip over the "-" */ 78 end_ptr++; 79 /* Make end "exclusive". */ 80 end = strtoull(end_ptr, NULL, 16) + 1; 81 82 /* Actual addresses are not exported */ 83 if (!start && !end) 84 break; 85 86 /* We need full pages. */ 87 start = (start + pagesize - 1) & ~(pagesize - 1); 88 end &= ~(pagesize - 1); 89 90 if (start != (off_t)start) 91 break; 92 93 /* We need two pages. */ 94 if (end > start + 2 * pagesize) { 95 fclose(file); 96 *offset = start; 97 return 0; 98 } 99 } 100 return -ENOENT; 101 } 102 103 FIXTURE(pfnmap) 104 { 105 off_t offset; 106 size_t pagesize; 107 int dev_mem_fd; 108 char *addr1; 109 size_t size1; 110 char *addr2; 111 size_t size2; 112 }; 113 114 FIXTURE_SETUP(pfnmap) 115 { 116 self->pagesize = getpagesize(); 117 118 if (strncmp(file, "/dev/mem", strlen("/dev/mem")) == 0) { 119 /* We'll require two physical pages throughout our tests ... */ 120 if (find_ram_target(&self->offset, self->pagesize)) 121 SKIP(return, 122 "Cannot find ram target in '/proc/iomem'\n"); 123 } else { 124 self->offset = 0; 125 } 126 127 self->dev_mem_fd = open(file, O_RDONLY); 128 if (self->dev_mem_fd < 0) 129 SKIP(return, "Cannot open '%s'\n", file); 130 131 self->size1 = self->pagesize * 2; 132 self->addr1 = mmap(NULL, self->size1, PROT_READ, MAP_SHARED, 133 self->dev_mem_fd, self->offset); 134 if (self->addr1 == MAP_FAILED) 135 SKIP(return, "Cannot mmap '%s'\n", file); 136 137 if (!check_vmflag_pfnmap(self->addr1)) 138 SKIP(return, "Invalid file: '%s'. Not pfnmap'ed\n", file); 139 140 /* ... and want to be able to read from them. */ 141 if (test_read_access(self->addr1, self->size1, self->pagesize)) 142 SKIP(return, "Cannot read-access mmap'ed '%s'\n", file); 143 144 self->size2 = 0; 145 self->addr2 = MAP_FAILED; 146 } 147 148 FIXTURE_TEARDOWN(pfnmap) 149 { 150 if (self->addr2 != MAP_FAILED) 151 munmap(self->addr2, self->size2); 152 if (self->addr1 != MAP_FAILED) 153 munmap(self->addr1, self->size1); 154 if (self->dev_mem_fd >= 0) 155 close(self->dev_mem_fd); 156 } 157 158 TEST_F(pfnmap, madvise_disallowed) 159 { 160 int advices[] = { 161 MADV_DONTNEED, 162 MADV_DONTNEED_LOCKED, 163 MADV_FREE, 164 MADV_WIPEONFORK, 165 MADV_COLD, 166 MADV_PAGEOUT, 167 MADV_POPULATE_READ, 168 MADV_POPULATE_WRITE, 169 }; 170 int i; 171 172 /* All these advices must be rejected. */ 173 for (i = 0; i < ARRAY_SIZE(advices); i++) { 174 EXPECT_LT(madvise(self->addr1, self->pagesize, advices[i]), 0); 175 EXPECT_EQ(errno, EINVAL); 176 } 177 } 178 179 TEST_F(pfnmap, munmap_split) 180 { 181 /* 182 * Unmap the first page. This munmap() call is not really expected to 183 * fail, but we might be able to trigger other internal issues. 184 */ 185 ASSERT_EQ(munmap(self->addr1, self->pagesize), 0); 186 187 /* 188 * Remap the first page while the second page is still mapped. This 189 * makes sure that any PAT tracking on x86 will allow for mmap()'ing 190 * a page again while some parts of the first mmap() are still 191 * around. 192 */ 193 self->size2 = self->pagesize; 194 self->addr2 = mmap(NULL, self->pagesize, PROT_READ, MAP_SHARED, 195 self->dev_mem_fd, self->offset); 196 ASSERT_NE(self->addr2, MAP_FAILED); 197 } 198 199 TEST_F(pfnmap, mremap_fixed) 200 { 201 char *ret; 202 203 /* Reserve a destination area. */ 204 self->size2 = self->size1; 205 self->addr2 = mmap(NULL, self->size2, PROT_READ, MAP_ANON | MAP_PRIVATE, 206 -1, 0); 207 ASSERT_NE(self->addr2, MAP_FAILED); 208 209 /* mremap() over our destination. */ 210 ret = mremap(self->addr1, self->size1, self->size2, 211 MREMAP_FIXED | MREMAP_MAYMOVE, self->addr2); 212 ASSERT_NE(ret, MAP_FAILED); 213 } 214 215 TEST_F(pfnmap, mremap_shrink) 216 { 217 char *ret; 218 219 /* Shrinking is expected to work. */ 220 ret = mremap(self->addr1, self->size1, self->size1 - self->pagesize, 0); 221 ASSERT_NE(ret, MAP_FAILED); 222 } 223 224 TEST_F(pfnmap, mremap_expand) 225 { 226 /* 227 * Growing is not expected to work, and getting it right would 228 * be challenging. So this test primarily serves as an early warning 229 * that something that probably should never work suddenly works. 230 */ 231 self->size2 = self->size1 + self->pagesize; 232 self->addr2 = mremap(self->addr1, self->size1, self->size2, MREMAP_MAYMOVE); 233 ASSERT_EQ(self->addr2, MAP_FAILED); 234 } 235 236 TEST_F(pfnmap, fork) 237 { 238 pid_t pid; 239 int ret; 240 241 /* fork() a child and test if the child can access the pages. */ 242 pid = fork(); 243 ASSERT_GE(pid, 0); 244 245 if (!pid) { 246 EXPECT_EQ(test_read_access(self->addr1, self->size1, 247 self->pagesize), 0); 248 exit(0); 249 } 250 251 wait(&ret); 252 if (WIFEXITED(ret)) 253 ret = WEXITSTATUS(ret); 254 else 255 ret = -EINVAL; 256 ASSERT_EQ(ret, 0); 257 } 258 259 int main(int argc, char **argv) 260 { 261 for (int i = 1; i < argc; i++) { 262 if (strcmp(argv[i], "--") == 0) { 263 if (i + 1 < argc && strlen(argv[i + 1]) > 0) 264 file = argv[i + 1]; 265 return test_harness_run(i, argv); 266 } 267 } 268 return test_harness_run(argc, argv); 269 } 270