xref: /linux/tools/testing/selftests/mm/pfnmap.c (revision b509c16e1d7cba8d0fd3843f6641fcafb3761432)
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