The Pedigree Project 0.1
large-mapping-contract-test/main.c
1#define _GNU_SOURCE
2#include <errno.h>
3#include <fcntl.h>
4#include <signal.h>
5#include <stddef.h>
6#include <stdint.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <time.h>
11#include <unistd.h>
12
13#include <sys/mman.h>
14#include <sys/wait.h>
15
16#define CHECK(expression) \
17 do { \
18 if (!(expression)) { \
19 fprintf(stderr, "LARGE-MAPPING-CONTRACT: line=%d %s errno=%d\n", __LINE__, #expression, \
20 errno); \
21 failed = 1; \
22 goto out; \
23 } \
24 } while (0)
25
26static size_t page_size;
27static const size_t gib = (size_t)1 << 30;
28
29static int64_t milliseconds(void) {
30 struct timespec now;
31 if (clock_gettime(CLOCK_MONOTONIC, &now))
32 return -1;
33 return (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000;
34}
35
36static int reap(pid_t child, int timeout) {
37 int64_t deadline = milliseconds() + timeout;
38 while (milliseconds() < deadline) {
39 int status;
40 pid_t result = waitpid(child, &status, WNOHANG);
41 if (result == child)
42 return WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status);
43 if (result < 0 && errno != EINTR)
44 return -1;
45 struct timespec delay = {0, 5000000};
46 nanosleep(&delay, NULL);
47 }
48 kill(child, SIGKILL);
49 while (waitpid(child, NULL, 0) < 0 && errno == EINTR) {
50 }
51 return -1;
52}
53
54static int release_and_reuse(unsigned char* base, size_t length) {
55 if (munmap(base, length))
56 return -1;
57 if (mmap(base, length, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0) !=
58 base)
59 return -1;
60 return munmap(base, length);
61}
62
63static int whole_unmap(void) {
64 int failed = 0;
65 unsigned char* base = mmap(NULL, gib, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
66 CHECK(base != MAP_FAILED);
67 CHECK(release_and_reuse(base, gib) == 0);
68 base = MAP_FAILED;
69out:
70 if (base != MAP_FAILED)
71 munmap(base, gib);
72 return failed;
73}
74
75static int sparse_reservation(size_t length) {
76 int failed = 0;
77 unsigned char* base = mmap(NULL, length, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
78 CHECK(base != MAP_FAILED);
79 unsigned char* middle = base + length / 2;
80 CHECK(mmap(middle, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1,
81 0) == middle);
82 CHECK(middle[0] == 0);
83 middle[0] = 0x51;
84 CHECK(mprotect(middle - 2 * page_size, page_size, PROT_READ | PROT_WRITE) == 0);
85 CHECK(mprotect(middle + 2 * page_size, page_size, PROT_READ | PROT_WRITE) == 0);
86 CHECK(middle[-2 * (ptrdiff_t)page_size] == 0 && middle[2 * page_size] == 0);
87 middle[-2 * (ptrdiff_t)page_size] = 0x52;
88 middle[2 * page_size] = 0x53;
89#if defined(__x86_64__)
90 unsigned char* code = middle + page_size;
91 const unsigned char return_42[] = {0xb8, 42, 0, 0, 0, 0xc3};
92 CHECK(mprotect(code, page_size, PROT_READ | PROT_WRITE) == 0);
93 memcpy(code, return_42, sizeof(return_42));
94 __builtin___clear_cache((char*)code, (char*)code + sizeof(return_42));
95 CHECK(mprotect(code, page_size, PROT_READ | PROT_EXEC) == 0);
96 CHECK(((int (*)(void))code)() == 42);
97 CHECK(mprotect(code, page_size, PROT_NONE) == 0);
98#endif
99 unsigned char* hole = base + length / 4;
100 CHECK(munmap(hole, page_size) == 0);
101 CHECK(mmap(hole, page_size, PROT_READ | PROT_WRITE,
102 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0) == hole);
103 CHECK(hole[0] == 0);
104 hole[0] = 0x54;
105 errno = 0;
106 CHECK(mmap(middle, page_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1,
107 0) == MAP_FAILED &&
108 errno == EEXIST);
109 CHECK(middle[0] == 0x51 && middle[-2 * (ptrdiff_t)page_size] == 0x52 &&
110 middle[2 * page_size] == 0x53 && hole[0] == 0x54);
111 CHECK(release_and_reuse(base, length) == 0);
112 base = MAP_FAILED;
113out:
114 if (base != MAP_FAILED)
115 munmap(base, length);
116 return failed;
117}
118
119static int reservation(void) {
120 return sparse_reservation(gib);
121}
122
123static int sparse(void) {
124 return sparse_reservation(64 * gib);
125}
126
127static int anonymous(void) {
128 int failed = 0;
129 pid_t child = -1;
130 unsigned char* base = mmap(NULL, gib, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
131 CHECK(base != MAP_FAILED);
132 unsigned char* middle = base + gib / 2;
133 base[0] = 0x61;
134 base[gib - page_size] = 0x62;
135 CHECK(mmap(middle, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1,
136 0) == middle);
137 CHECK(base[0] == 0x61 && base[gib - page_size] == 0x62 && middle[0] == 0);
138 CHECK(base[2 * page_size] == 0);
139 middle[0] = 0x63;
140 CHECK(munmap(base + gib / 4, 2 * page_size) == 0);
141 CHECK(mmap(base + gib / 4, 2 * page_size, PROT_READ | PROT_WRITE,
142 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0) == base + gib / 4);
143 CHECK(base[gib / 4] == 0 && base[0] == 0x61 && base[gib - page_size] == 0x62);
144 CHECK((child = fork()) >= 0);
145 if (!child) {
146 alarm(15);
147 if (base[0] != 0x61 || base[gib - page_size] != 0x62 || base[2 * page_size] != 0)
148 _exit(2);
149 base[0] = 0x71;
150 base[gib - page_size] = 0x72;
151 base[2 * page_size] = 0x73;
152 _exit(release_and_reuse(base, gib) ? 3 : 0);
153 }
154 int child_status = reap(child, 20000);
155 child = -1;
156 CHECK(child_status == 0);
157 CHECK(base[0] == 0x61 && base[gib - page_size] == 0x62 && base[2 * page_size] == 0 &&
158 middle[0] == 0x63);
159 CHECK(release_and_reuse(base, gib) == 0);
160 base = MAP_FAILED;
161out:
162 if (child > 0) {
163 kill(child, SIGKILL);
164 reap(child, 2000);
165 }
166 if (base != MAP_FAILED)
167 munmap(base, gib);
168 return failed;
169}
170
171static int backing_file(int memory, int* readonly) {
172 char path[96];
173 snprintf(path, sizeof(path), "/tmp/large-mapping-contract-%ld", (long)getpid());
174 int fd = memory ? memfd_create("large-mapping-contract", MFD_CLOEXEC)
175 : open(path, O_CREAT | O_EXCL | O_RDWR, 0600);
176 if (fd < 0)
177 return -1;
178 unsigned char* bytes = malloc(page_size);
179 int error = bytes == NULL;
180 if (readonly)
181 *readonly = -1;
182 for (size_t n = 0; !error && n < 4; ++n) {
183 memset(bytes, 0x40 + n, page_size);
184 error = write(fd, bytes, page_size) != (ssize_t)page_size;
185 }
186 free(bytes);
187 if (!error && readonly) {
188 *readonly = open(path, O_RDONLY);
189 error = *readonly < 0;
190 }
191 if (!memory && unlink(path))
192 error = 1;
193 if (error) {
194 if (readonly && *readonly >= 0) {
195 close(*readonly);
196 *readonly = -1;
197 }
198 close(fd);
199 return -1;
200 }
201 return fd;
202}
203
204static int file_mapping(int shared) {
205 int failed = 0, fd = -1;
206 unsigned char *base = MAP_FAILED, *alias = MAP_FAILED;
207 CHECK((fd = backing_file(shared, NULL)) >= 0);
208 CHECK((base = mmap(NULL, gib, PROT_READ | PROT_WRITE, shared ? MAP_SHARED : MAP_PRIVATE, fd,
209 page_size)) != MAP_FAILED);
210 CHECK((alias = mmap(NULL, 3 * page_size, PROT_READ, MAP_SHARED, fd, page_size)) != MAP_FAILED);
211 CHECK(base[0] == 0x41 && base[page_size] == 0x42 && base[2 * page_size] == 0x43);
212 base[0] = 0x81;
213 base[page_size] = 0x82;
214 CHECK(alias[0] == (shared ? 0x81 : 0x41) && alias[page_size] == (shared ? 0x82 : 0x42));
215 CHECK(close(fd) == 0);
216 fd = -1;
217 CHECK(mmap(base + page_size, page_size, PROT_READ | PROT_WRITE,
218 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) == base + page_size);
219 CHECK(base[0] == 0x81 && base[page_size] == 0 && base[2 * page_size] == 0x43);
220 base[page_size] = 0x83;
221 CHECK(munmap(base + 4 * page_size, page_size) == 0);
222 CHECK(mmap(base + 4 * page_size, page_size, PROT_NONE,
223 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0) == base + 4 * page_size);
224 CHECK(base[0] == 0x81 && base[2 * page_size] == 0x43 &&
225 alias[page_size] == (shared ? 0x82 : 0x42));
226 CHECK(release_and_reuse(base, gib) == 0);
227 base = MAP_FAILED;
228 CHECK(alias[0] == (shared ? 0x81 : 0x41) && alias[page_size] == (shared ? 0x82 : 0x42) &&
229 alias[2 * page_size] == 0x43);
230 CHECK(munmap(alias, 3 * page_size) == 0);
231 alias = MAP_FAILED;
232out:
233 if (base != MAP_FAILED)
234 munmap(base, gib);
235 if (alias != MAP_FAILED)
236 munmap(alias, 3 * page_size);
237 if (fd >= 0)
238 close(fd);
239 return failed;
240}
241
242static int file(void) {
243 return file_mapping(0);
244}
245
246static int shared(void) {
247 return file_mapping(1);
248}
249
250static int failure(void) {
251 int failed = 0, fd = -1, readonly = -1;
252 unsigned char* base = MAP_FAILED;
253 CHECK((fd = backing_file(0, &readonly)) >= 0);
254 CHECK((base = mmap(NULL, gib, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)) !=
255 MAP_FAILED);
256 unsigned char* middle = base + gib / 2;
257 base[0] = 0x91;
258 middle[0] = 0x92;
259 base[gib - page_size] = 0x93;
260 errno = 0;
261 CHECK(mmap(middle, page_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, -1, 0) == MAP_FAILED &&
262 errno == EBADF);
263 CHECK(base[0] == 0x91 && middle[0] == 0x92 && base[gib - page_size] == 0x93);
264 errno = 0;
265 CHECK(mmap(middle, page_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, readonly, 0) ==
266 MAP_FAILED &&
267 errno == EACCES);
268 CHECK(base[0] == 0x91 && middle[0] == 0x92 && base[gib - page_size] == 0x93);
269 CHECK(release_and_reuse(base, gib) == 0);
270 base = MAP_FAILED;
271out:
272 if (base != MAP_FAILED)
273 munmap(base, gib);
274 if (readonly >= 0)
275 close(readonly);
276 if (fd >= 0)
277 close(fd);
278 return failed;
279}
280
281static int run(const char* name, int (*test)(void)) {
282 printf("LARGE-MAPPING-CONTRACT: BEGIN %s\n", name);
283 fflush(stdout);
284 pid_t child = fork();
285 int result = -1;
286 if (!child) {
287 alarm(40);
288 result = test();
289 fflush(stdout);
290 fflush(stderr);
291 _exit(result ? 1 : 0);
292 }
293 if (child > 0)
294 result = reap(child, 45000);
295 printf("LARGE-MAPPING-CONTRACT: %s %s status=%d\n", result ? "FAIL" : "PASS", name, result);
296 fflush(stdout);
297 return result;
298}
299
300int main(int argc, char** argv) {
301 page_size = (size_t)sysconf(_SC_PAGESIZE);
302 if (sizeof(size_t) < 8 || page_size < 512 || page_size > 65536 || (page_size & (page_size - 1)))
303 return 2;
304 static const struct {
305 const char* name;
306 int (*test)(void);
307 } suites[] = {{"whole-unmap", whole_unmap},
308 {"reservation", reservation},
309 {"sparse", sparse},
310 {"anonymous", anonymous},
311 {"file", file},
312 {"shared", shared},
313 {"failure", failure}};
314 int selected = 0;
315 for (size_t n = 0; n < sizeof(suites) / sizeof(suites[0]); ++n) {
316 if (argc > 1 && strcmp(argv[1], suites[n].name))
317 continue;
318 selected = 1;
319 if (run(suites[n].name, suites[n].test))
320 return 1;
321 }
322 if (!selected)
323 return 2;
324 puts("LARGE-MAPPING-CONTRACT: END PASS");
325 return 0;
326}