The Pedigree Project 0.1
remap.c
1#define _GNU_SOURCE
2#include <pthread.h>
3#include <signal.h>
4#include <stdlib.h>
5#include <string.h>
6#include <unistd.h>
7
8#include "contract.h"
9#include <sys/mman.h>
10#include <sys/syscall.h>
11
12static int anonymous_resize(void) {
13 int failed = 0;
14 const size_t p = vm_page;
15 unsigned char *base = MAP_FAILED, *moved = MAP_FAILED, *tail = MAP_FAILED, *reuse = MAP_FAILED;
16 unsigned char resident[8];
17 CHECK((base = mmap(NULL, 8 * p, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)) !=
18 MAP_FAILED);
19 for (int n = 0; n < 8; ++n)
20 memset(base + n * p, 0x40 + n, p);
21 CHECK(mremap(base, 8 * p, 8 * p, 0) == base);
22 CHECK(mremap(base, 8 * p, 4 * p, 0) == base);
23 CHECK(mincore(base + 4 * p, p, resident) == -1 && errno == ENOMEM);
24 CHECK((tail = mmap(base + 4 * p, 4 * p, PROT_READ | PROT_WRITE,
25 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0)) == base + 4 * p);
26 memset(tail, 0xee, 4 * p);
27 CHECK(mremap(base, 4 * p, 6 * p, 0) == MAP_FAILED && errno == ENOMEM);
28 CHECK(vm_uniform(base, p, 0x40) && vm_uniform(base + 3 * p, p, 0x43) &&
29 vm_uniform(tail, 4 * p, 0xee));
30 CHECK((moved = mremap(base, 4 * p, 6 * p, MREMAP_MAYMOVE)) != MAP_FAILED && moved != base);
31 CHECK(mincore(moved, 6 * p, resident) == 0 && resident[4] == 0 && resident[5] == 0);
32 for (int n = 0; n < 4; ++n)
33 CHECK(vm_uniform(moved + n * p, p, 0x40 + n));
34 CHECK(vm_uniform(moved + 4 * p, 2 * p, 0));
35 CHECK(mincore(base, p, resident) == -1 && errno == ENOMEM);
36 CHECK((reuse = mmap(base, 4 * p, PROT_READ | PROT_WRITE,
37 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0)) == base);
38 CHECK(vm_uniform(reuse, p, 0) && vm_uniform(tail, 4 * p, 0xee));
39out:
40 if (moved != MAP_FAILED)
41 munmap(moved, 6 * p);
42 if (base != MAP_FAILED)
43 munmap(base, 8 * p);
44 return failed;
45}
46
47static int growth_and_rejection(void) {
48 int failed = 0;
49 const size_t p = vm_page;
50 unsigned char* base =
51 mmap(NULL, 4 * p, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
52 unsigned char resident[4];
53 volatile int stack_value = 17;
54 void* stack_page = (void*)((uintptr_t)&stack_value & ~(uintptr_t)(p - 1));
55 CHECK(base != MAP_FAILED);
56 memset(base, 0x51, 2 * p);
57 CHECK(munmap(base + 2 * p, 2 * p) == 0);
58 CHECK(mremap(base, 2 * p, 4 * p, 0) == base);
59 CHECK(mincore(base, 4 * p, resident) == 0 && resident[0] == 1 && resident[1] == 1 &&
60 resident[2] == 0 && resident[3] == 0);
61 CHECK(vm_uniform(base, 2 * p, 0x51) && vm_uniform(base + 2 * p, 2 * p, 0));
62 CHECK(mremap(base + 1, p, p, 0) == MAP_FAILED && errno == EINVAL);
63 CHECK(mremap(base, 4 * p, 0, 0) == MAP_FAILED && errno == EINVAL);
64 CHECK(mremap(base, 4 * p, 4 * p, 8) == MAP_FAILED && errno == EINVAL);
65 CHECK(mremap(base, 0, p, 0) == MAP_FAILED && errno == EINVAL);
66 CHECK(mremap(base, 0, p, MREMAP_MAYMOVE) == MAP_FAILED && errno == EOPNOTSUPP);
67 CHECK(mremap(base, 4 * p, 4 * p, MREMAP_MAYMOVE | MREMAP_DONTUNMAP) == MAP_FAILED &&
68 errno == EOPNOTSUPP);
69 CHECK(mremap(base, 4 * p, 4 * p, MREMAP_FIXED, base + 8 * p) == MAP_FAILED && errno == EINVAL);
70 CHECK(mremap(base, 4 * p, 2 * p, MREMAP_FIXED | MREMAP_MAYMOVE, base + p) == MAP_FAILED &&
71 errno == EINVAL);
72 CHECK(mremap(base, 4 * p, 2 * p, MREMAP_FIXED | MREMAP_MAYMOVE, base + 1) == MAP_FAILED &&
73 errno == EINVAL);
74 CHECK(mremap(base, 4 * p, 65537 * p, MREMAP_MAYMOVE) == MAP_FAILED && errno == ENOMEM);
75 CHECK((void*)syscall(SYS_mremap, base, 4 * p, SIZE_MAX, 0, NULL) == MAP_FAILED &&
76 errno == EINVAL);
77 CHECK(mremap(stack_page, p, p, 0) == MAP_FAILED && errno == EOPNOTSUPP && stack_value == 17);
78 CHECK(vm_uniform(base, 2 * p, 0x51) && vm_uniform(base + 2 * p, 2 * p, 0));
79out:
80 if (base != MAP_FAILED)
81 munmap(base, 4 * p);
82 return failed;
83}
84
85static int fixed_victims(void) {
86 int failed = 0;
87 const size_t p = vm_page;
88 unsigned char* source =
89 mmap(NULL, 3 * p, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
90 unsigned char* target =
91 mmap(NULL, 5 * p, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
92 unsigned char resident;
93 CHECK(source != MAP_FAILED && target != MAP_FAILED);
94 for (int n = 0; n < 3; ++n)
95 memset(source + n * p, 0x61 + n, p);
96 for (int n = 0; n < 5; ++n)
97 memset(target + n * p, 0x71 + n, p);
98 CHECK(mprotect(target + 4 * p, p, PROT_READ) == 0);
99 CHECK(mremap(source, 3 * p, 2 * p, MREMAP_FIXED | MREMAP_MAYMOVE, target + p) == target + p);
100 CHECK(vm_uniform(target, p, 0x71) && vm_uniform(target + p, p, 0x61) &&
101 vm_uniform(target + 2 * p, p, 0x62) && vm_uniform(target + 3 * p, p, 0x74) &&
102 vm_uniform(target + 4 * p, p, 0x75));
103 CHECK(vm_fault(target + 4 * p, 1, SIGSEGV) == 0);
104 CHECK(mincore(source, p, &resident) == -1 && errno == ENOMEM);
105 CHECK(mremap(source, p, p, 0) == MAP_FAILED && errno == EFAULT);
106 CHECK(mmap(source, 3 * p, PROT_READ | PROT_WRITE,
107 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0) == source);
108 CHECK(vm_uniform(source, 3 * p, 0));
109out:
110 if (source != MAP_FAILED)
111 munmap(source, 3 * p);
112 if (target != MAP_FAILED)
113 munmap(target, 5 * p);
114 return failed;
115}
116
117static int fork_cow(void) {
118 int failed = 0, gate[2] = {-1, -1};
119 const size_t p = vm_page;
120 pid_t child = -1;
121 unsigned char* areas[3] = {MAP_FAILED, MAP_FAILED, MAP_FAILED};
122 unsigned char* current;
123 for (int n = 0; n < 3; ++n) {
124 areas[n] = mmap(NULL, 2 * p, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
125 CHECK(areas[n] != MAP_FAILED);
126 }
127 current = areas[0];
128 memset(current, 0x31, 2 * p);
129 CHECK(pipe(gate) == 0 && (child = fork()) >= 0);
130 if (!child) {
131 alarm(5);
132 char byte;
133 if (read(gate[0], &byte, 1) != 1)
134 _exit(10);
135 for (int n = 0; n < 12; ++n) {
136 memset(areas[0], 0xc0 + n, 2 * p);
137 vm_pause(2);
138 if (!vm_uniform(areas[0], 2 * p, 0xc0 + n))
139 _exit(11);
140 }
141 _exit(0);
142 }
143 CHECK(write(gate[1], "g", 1) == 1);
144 for (int n = 0; n < 12; ++n) {
145 memset(current, 0x40 + n, 2 * p);
146 unsigned char* next = areas[1 + (n & 1)];
147 CHECK(mremap(current, 2 * p, 2 * p, MREMAP_FIXED | MREMAP_MAYMOVE, next) == next);
148 current = next;
149 vm_pause(2);
150 CHECK(vm_uniform(current, 2 * p, 0x40 + n));
151 }
152 int status = vm_reap(child, 3000);
153 child = -1;
154 CHECK(status == 0 && vm_uniform(current, 2 * p, 0x4b));
155out:
156 if (child > 0) {
157 kill(child, SIGKILL);
158 vm_reap(child, 1000);
159 }
160 for (int n = 0; n < 2; ++n)
161 if (gate[n] >= 0)
162 close(gate[n]);
163 for (int n = 0; n < 3; ++n)
164 if (areas[n] != MAP_FAILED)
165 munmap(areas[n], 2 * p);
166 return failed;
167}
168
169static __thread unsigned char tls_bytes[8192];
170static void* tls_probe(void* argument) {
171 unsigned char value = (uintptr_t)argument;
172 memset(tls_bytes, value, sizeof(tls_bytes));
173 vm_pause(2);
174 return vm_uniform(tls_bytes, sizeof(tls_bytes), value) ? NULL : (void*)1;
175}
176static void* thread_churn(void* ignored) {
177 (void)ignored;
178 for (uintptr_t n = 1; n <= 12; ++n) {
179 pthread_t thread;
180 void* result;
181 if (pthread_create(&thread, NULL, tls_probe, (void*)n) || pthread_join(thread, &result) ||
182 result)
183 return (void*)1;
184 }
185 return NULL;
186}
187static int allocation_and_tls(void) {
188 int failed = 0, active = 0;
189 const size_t p = vm_page;
190 pthread_t worker;
191 void* result;
192 unsigned char* heap = malloc(192 * 1024);
193 unsigned char *current = MAP_FAILED, *next = MAP_FAILED;
194 CHECK(heap != NULL);
195 memset(heap, 0x83, 192 * 1024);
196 unsigned char* replacement = realloc(heap, 384 * 1024);
197 CHECK(replacement != NULL);
198 heap = replacement;
199 CHECK(vm_uniform(heap, 192 * 1024, 0x83));
200 memset(heap + 192 * 1024, 0x84, 192 * 1024);
201 replacement = realloc(heap, 160 * 1024);
202 CHECK(replacement != NULL);
203 heap = replacement;
204 CHECK(vm_uniform(heap, 160 * 1024, 0x83));
205 CHECK((current = mmap(NULL, 2 * p, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)) !=
206 MAP_FAILED);
207 memset(current, 0x93, 2 * p);
208 CHECK(pthread_create(&worker, NULL, thread_churn, NULL) == 0);
209 active = 1;
210 for (int n = 0; n < 24; ++n) {
211 // A vacated address can become a thread stack; reserve a fresh victim.
212 CHECK((next = mmap(NULL, 2 * p, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)) != MAP_FAILED);
213 CHECK(mremap(current, 2 * p, 2 * p, MREMAP_FIXED | MREMAP_MAYMOVE, next) == next);
214 current = next;
215 next = MAP_FAILED;
216 vm_pause(2);
217 CHECK(vm_uniform(current, 2 * p, 0x93));
218 }
219 CHECK(pthread_join(worker, &result) == 0);
220 active = 0;
221 CHECK(result == NULL && vm_uniform(heap, 160 * 1024, 0x83));
222out:
223 if (active)
224 pthread_join(worker, NULL);
225 if (current != MAP_FAILED)
226 munmap(current, 2 * p);
227 if (next != MAP_FAILED)
228 munmap(next, 2 * p);
229 free(heap);
230 return failed;
231}
232
233static int fragmented_lookup(void) {
234 enum { Mappings = 64 };
235 const size_t p = vm_page, length = Mappings * 3 * p;
236 int failed = 0, fd = -1;
237 unsigned char* arena = MAP_FAILED;
238 CHECK((fd = vm_file(1, NULL)) >= 0);
239 CHECK((arena = mmap(NULL, length, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)) != MAP_FAILED);
240 // Publication order differs from address order, with a guard page between
241 // mappings. Exercise both user faults and kernel copies into untouched pages.
242 for (size_t n = 0; n < Mappings; ++n) {
243 const size_t i = (n * 37) % Mappings;
244 unsigned char* base = arena + i * 3 * p;
245 CHECK(mmap(base, 2 * p, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1,
246 0) == base);
247 }
248 for (size_t i = 0; i < Mappings; ++i) {
249 unsigned char* base = arena + i * 3 * p;
250 CHECK(base[0] == 0);
251 base[0] = (unsigned char)(i + 1);
252 CHECK(pread(fd, base + p - 1, 2, 0) == 2);
253 CHECK(base[p - 1] == 0x20 && base[p] == 0x20);
254 base[2 * p - 1] = (unsigned char)(i + 65);
255 if (!(i % 8))
256 CHECK(mprotect(base + p, p, PROT_READ) == 0);
257 }
258 CHECK(vm_fault(arena + 2 * p, 0, SIGSEGV) == 0);
259 CHECK(vm_fault(arena + 32 * 3 * p + p, 1, SIGSEGV) == 0);
260 CHECK(munmap(arena + 17 * 3 * p + 2 * p, p) == 0);
261 CHECK(vm_fault(arena + 17 * 3 * p + 2 * p, 0, SIGSEGV) == 0);
262
263 pid_t child = fork();
264 CHECK(child >= 0);
265 if (!child) {
266 for (size_t i = 0; i < Mappings; ++i) {
267 unsigned char* base = arena + i * 3 * p;
268 if (base[0] != i + 1 || base[2 * p - 1] != i + 65)
269 _exit(1);
270 base[0] = 0xee;
271 if (base[0] != 0xee)
272 _exit(2);
273 }
274 _exit(0);
275 }
276 CHECK(vm_reap(child, 4000) == 0);
277 for (size_t i = 0; i < Mappings; ++i) {
278 unsigned char* base = arena + i * 3 * p;
279 CHECK(base[0] == i + 1 && base[2 * p - 1] == i + 65);
280 }
281
282 unsigned char* source = arena + 32 * 3 * p;
283 unsigned char* destination = arena + 33 * 3 * p + 2 * p;
284 CHECK(mremap(source, p, p, MREMAP_FIXED | MREMAP_MAYMOVE, destination) == destination);
285 CHECK(destination[0] == 33 && destination[p - 1] == 0x20);
286 CHECK(source[p] == 0x20 && source[2 * p - 1] == 97);
287 CHECK(vm_fault(source, 0, SIGSEGV) == 0);
288 CHECK(mmap(source, p, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) ==
289 source);
290 CHECK(source[0] == 0 && source[p - 1] == 0);
291 CHECK(pread(fd, source, 1, 0) == 1 && source[0] == 0x20);
292 CHECK(destination[0] == 33);
293out:
294 if (arena != MAP_FAILED)
295 munmap(arena, length);
296 if (fd >= 0)
297 close(fd);
298 return failed;
299}
300
301int vm_test_remap(void) {
302 return anonymous_resize() || growth_and_rejection() || fixed_victims() || vm_test_remap_file() ||
303 vm_test_remap_shm() || fork_cow() || allocation_and_tls() || fragmented_lookup();
304}