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 <fcntl.h> 16 #include <signal.h> 17 #include <setjmp.h> 18 #include <linux/mman.h> 19 #include <sys/mman.h> 20 #include <sys/wait.h> 21 22 #include "../kselftest_harness.h" 23 #include "vm_util.h" 24 25 static sigjmp_buf sigjmp_buf_env; 26 27 static void signal_handler(int sig) 28 { 29 siglongjmp(sigjmp_buf_env, -EFAULT); 30 } 31 32 static int test_read_access(char *addr, size_t size, size_t pagesize) 33 { 34 size_t offs; 35 int ret; 36 37 if (signal(SIGSEGV, signal_handler) == SIG_ERR) 38 return -EINVAL; 39 40 ret = sigsetjmp(sigjmp_buf_env, 1); 41 if (!ret) { 42 for (offs = 0; offs < size; offs += pagesize) 43 /* Force a read that the compiler cannot optimize out. */ 44 *((volatile char *)(addr + offs)); 45 } 46 if (signal(SIGSEGV, signal_handler) == SIG_ERR) 47 return -EINVAL; 48 49 return ret; 50 } 51 52 FIXTURE(pfnmap) 53 { 54 size_t pagesize; 55 int dev_mem_fd; 56 char *addr1; 57 size_t size1; 58 char *addr2; 59 size_t size2; 60 }; 61 62 FIXTURE_SETUP(pfnmap) 63 { 64 self->pagesize = getpagesize(); 65 66 self->dev_mem_fd = open("/dev/mem", O_RDONLY); 67 if (self->dev_mem_fd < 0) 68 SKIP(return, "Cannot open '/dev/mem'\n"); 69 70 /* We'll require the first two pages throughout our tests ... */ 71 self->size1 = self->pagesize * 2; 72 self->addr1 = mmap(NULL, self->size1, PROT_READ, MAP_SHARED, 73 self->dev_mem_fd, 0); 74 if (self->addr1 == MAP_FAILED) 75 SKIP(return, "Cannot mmap '/dev/mem'\n"); 76 77 /* ... and want to be able to read from them. */ 78 if (test_read_access(self->addr1, self->size1, self->pagesize)) 79 SKIP(return, "Cannot read-access mmap'ed '/dev/mem'\n"); 80 81 self->size2 = 0; 82 self->addr2 = MAP_FAILED; 83 } 84 85 FIXTURE_TEARDOWN(pfnmap) 86 { 87 if (self->addr2 != MAP_FAILED) 88 munmap(self->addr2, self->size2); 89 if (self->addr1 != MAP_FAILED) 90 munmap(self->addr1, self->size1); 91 if (self->dev_mem_fd >= 0) 92 close(self->dev_mem_fd); 93 } 94 95 TEST_F(pfnmap, madvise_disallowed) 96 { 97 int advices[] = { 98 MADV_DONTNEED, 99 MADV_DONTNEED_LOCKED, 100 MADV_FREE, 101 MADV_WIPEONFORK, 102 MADV_COLD, 103 MADV_PAGEOUT, 104 MADV_POPULATE_READ, 105 MADV_POPULATE_WRITE, 106 }; 107 int i; 108 109 /* All these advices must be rejected. */ 110 for (i = 0; i < ARRAY_SIZE(advices); i++) { 111 EXPECT_LT(madvise(self->addr1, self->pagesize, advices[i]), 0); 112 EXPECT_EQ(errno, EINVAL); 113 } 114 } 115 116 TEST_F(pfnmap, munmap_split) 117 { 118 /* 119 * Unmap the first page. This munmap() call is not really expected to 120 * fail, but we might be able to trigger other internal issues. 121 */ 122 ASSERT_EQ(munmap(self->addr1, self->pagesize), 0); 123 124 /* 125 * Remap the first page while the second page is still mapped. This 126 * makes sure that any PAT tracking on x86 will allow for mmap()'ing 127 * a page again while some parts of the first mmap() are still 128 * around. 129 */ 130 self->size2 = self->pagesize; 131 self->addr2 = mmap(NULL, self->pagesize, PROT_READ, MAP_SHARED, 132 self->dev_mem_fd, 0); 133 ASSERT_NE(self->addr2, MAP_FAILED); 134 } 135 136 TEST_F(pfnmap, mremap_fixed) 137 { 138 char *ret; 139 140 /* Reserve a destination area. */ 141 self->size2 = self->size1; 142 self->addr2 = mmap(NULL, self->size2, PROT_READ, MAP_ANON | MAP_PRIVATE, 143 -1, 0); 144 ASSERT_NE(self->addr2, MAP_FAILED); 145 146 /* mremap() over our destination. */ 147 ret = mremap(self->addr1, self->size1, self->size2, 148 MREMAP_FIXED | MREMAP_MAYMOVE, self->addr2); 149 ASSERT_NE(ret, MAP_FAILED); 150 } 151 152 TEST_F(pfnmap, mremap_shrink) 153 { 154 char *ret; 155 156 /* Shrinking is expected to work. */ 157 ret = mremap(self->addr1, self->size1, self->size1 - self->pagesize, 0); 158 ASSERT_NE(ret, MAP_FAILED); 159 } 160 161 TEST_F(pfnmap, mremap_expand) 162 { 163 /* 164 * Growing is not expected to work, and getting it right would 165 * be challenging. So this test primarily serves as an early warning 166 * that something that probably should never work suddenly works. 167 */ 168 self->size2 = self->size1 + self->pagesize; 169 self->addr2 = mremap(self->addr1, self->size1, self->size2, MREMAP_MAYMOVE); 170 ASSERT_EQ(self->addr2, MAP_FAILED); 171 } 172 173 TEST_F(pfnmap, fork) 174 { 175 pid_t pid; 176 int ret; 177 178 /* fork() a child and test if the child can access the pages. */ 179 pid = fork(); 180 ASSERT_GE(pid, 0); 181 182 if (!pid) { 183 EXPECT_EQ(test_read_access(self->addr1, self->size1, 184 self->pagesize), 0); 185 exit(0); 186 } 187 188 wait(&ret); 189 if (WIFEXITED(ret)) 190 ret = WEXITSTATUS(ret); 191 else 192 ret = -EINVAL; 193 ASSERT_EQ(ret, 0); 194 } 195 196 TEST_HARNESS_MAIN 197