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