20#include <sys/syscall.h>
25static void require(
int condition,
const char* operation) {
27 printf(
"VM-CONTRACT: FAIL %s errno=%d\n", operation, errno);
32static void anonymous_ranges(
size_t page) {
33 unsigned char* memory =
34 mmap(NULL, page * 4, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
35 require(memory != MAP_FAILED,
"anonymous mapping");
36 for (
size_t i = 4; i; --i) {
37 memory[(i - 1) * page] = (
unsigned char)(0x40 + i);
38 memory[i * page - 1] = (
unsigned char)(0x60 + i);
40 require(munmap(memory, page) == 0,
"remove reverse-faulted prefix");
41 unsigned char* prefix = mmap(memory, page, PROT_READ | PROT_WRITE,
42 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
43 require(prefix == memory && prefix[0] == 0 && prefix[page - 1] == 0,
44 "prefix removed every resident page");
46 memory[page - 1] = 0x61;
47 require(mprotect(memory + page, page * 2, PROT_NONE) == 0,
"protect middle none");
48 require(mprotect(memory + page, page * 2, PROT_READ | PROT_WRITE) == 0,
49 "restore middle protections");
50 for (
size_t i = 1; i <= 4; ++i) {
51 require(memory[(i - 1) * page] == 0x40 + i && memory[i * page - 1] == 0x60 + i,
52 "anonymous protection preserves contents");
54 require(munmap(memory + page, page) == 0,
"create range hole");
56 require(mprotect(memory, page * 4, PROT_READ) == -1 && errno == ENOMEM,
57 "protection rejects uncovered range");
59 memory[page * 2] = 0xA2;
61 require(msync(memory, page * 4, MS_SYNC) == -1 && errno == ENOMEM,
62 "sync rejects uncovered range");
64 require(mprotect(memory, page, 0x40000000) == -1 && errno == EINVAL,
65 "protection rejects unknown flags");
68 require(syscall(SYS_mprotect, memory, SIZE_MAX, PROT_NONE) == -1 && errno == ENOMEM &&
70 "syscall length-rounding overflow preserves mapping");
72 require(mprotect(memory, SIZE_MAX & ~(page - 1), PROT_NONE) == -1 && errno == ENOMEM &&
74 "address-range overflow preserves mapping");
75 require(munmap(memory, page * 4) == 0,
"unmap range with hole");
76 puts(
"VM-CONTRACT: PASS anonymous-ranges");
79static void protected_fork(
size_t page) {
80 unsigned char* memory =
81 mmap(NULL, page * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
82 require(memory != MAP_FAILED,
"fork mapping");
85 require(mprotect(memory, page, PROT_READ) == 0 && mprotect(memory + page, page, PROT_NONE) == 0,
86 "protect before fork");
87 int ready[2], proceed[2];
88 require(pipe(ready) == 0 && pipe(proceed) == 0,
"fork barriers");
90 require(child >= 0,
"fork protected mapping");
94 if (mprotect(memory, page * 2, PROT_READ | PROT_WRITE) != 0 || memory[0] != 0x31 ||
100 if (write(ready[1], &
byte, 1) != 1 || read(proceed[0], &
byte, 1) != 1 || memory[0] != 0x51 ||
101 memory[page] != 0x52)
108 require(read(ready[0], &
byte, 1) == 1,
"child restored protected pages");
109 require(mprotect(memory, page * 2, PROT_READ | PROT_WRITE) == 0 && memory[0] == 0x31 &&
110 memory[page] == 0x32,
111 "child writes preserve parent protected contents");
114 require(write(proceed[1], &
byte, 1) == 1,
"release protected child");
116 require(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0,
117 "parent writes preserve child contents");
120 require(munmap(memory, page * 2) == 0,
"unmap protected fork mapping");
121 puts(
"VM-CONTRACT: PASS protected-fork");
124static void require_sparse(
int condition,
const char* operation,
unsigned scenario,
125 const void* address) {
127 printf(
"VM-CONTRACT: FAIL sparse case=%u stage=%s address=%p errno=%d\n", scenario, operation,
133static void sparse_file_splits(
int file,
size_t page,
const unsigned char* pattern) {
134 const unsigned resident_masks[] = {4, 2, 5, 7};
135 for (
unsigned scenario = 0; scenario < 24; ++scenario) {
136 unsigned mask = resident_masks[scenario / 6];
137 unsigned reverse_split = (scenario / 3) % 2;
138 unsigned removal = scenario % 3;
139 unsigned char* arena =
140 mmap(NULL, page * 7, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
141 require_sparse(arena != MAP_FAILED,
"guarded reservation", scenario, arena);
143 arena[page * 4] = 0x92;
144 arena[page * 6] = 0x93;
145 unsigned char* memory = arena + page;
146 require_sparse(munmap(memory, page * 3) == 0,
"file reservation hole", scenario, memory);
148 mmap(memory, page * 3, PROT_READ, MAP_SHARED | MAP_FIXED_NOREPLACE, file, 0) == memory,
149 "file placement", scenario, memory);
150 for (
size_t i = 3; i; --i) {
151 size_t offset = (i - 1) * page;
152 if (mask & (1U << (i - 1))) {
153 require_sparse(memory[offset] == pattern[offset] &&
154 memory[offset + page - 1] == pattern[offset + page - 1],
155 "resident file contents", scenario, memory);
158 require_sparse(mprotect(memory + (reverse_split ? 2 : 1) * page, (reverse_split ? 1 : 2) * page,
160 mprotect(memory + (reverse_split ? 1 : 2) * page, page, PROT_READ) == 0,
161 "protection split", scenario, memory);
164 size_t first = removal == 1 ? 0 : page * 2;
165 require_sparse(munmap(memory + first, page) == 0,
"first fragment removal", scenario, memory);
168 require_sparse(munmap(arena + page * 5, page) == 0,
"unrelated hole", scenario, memory);
170 require_sparse(munmap(memory, page * 3) == 0,
"whole split removal", scenario, memory);
172 size_t last = removal == 1 ? page * 2 : 0;
173 require_sparse(munmap(memory + last, page) == 0 && munmap(memory + page, page) == 0,
174 "remaining fragment removal", scenario, memory);
178 unsigned char* reclaimed = mmap(memory, page * 3, PROT_READ | PROT_WRITE,
179 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
180 if (reclaimed != memory) {
181 printf(
"VM-CONTRACT: sparse case=%u requested=%p returned=%p errno=%d\n", scenario,
182 (
void*)memory, (
void*)reclaimed, errno);
183 require_sparse(0,
"released reservation", scenario, memory);
185 for (
size_t offset = 0; offset < page * 3; ++offset) {
186 unsigned char value = reclaimed[offset];
188 printf(
"VM-CONTRACT: sparse case=%u offset=%zu value=%u\n", scenario, offset, value);
189 require_sparse(0,
"reclaimed zero contents", scenario, memory);
192 require_sparse(arena[0] == 0x91 && arena[page * 4] == 0x92 && arena[page * 6] == 0x93,
193 "neighbor contents", scenario, memory);
194 require_sparse(munmap(arena, page * 7) == 0,
"guarded cleanup", scenario, arena);
196 puts(
"VM-CONTRACT: PASS sparse-splits cases=24");
199static void file_mappings(
size_t page) {
201 snprintf(path,
sizeof(path),
"/tmp/vm-contract-%d", getpid());
202 int file = open(path, O_CREAT | O_TRUNC | O_RDWR, 0600);
203 require(file >= 0,
"create mapped file");
204 unsigned char* pattern = malloc(page * 3);
205 require(pattern != NULL,
"file pattern allocation");
206 for (
size_t i = 0; i < page * 3; ++i)
207 pattern[i] = (
unsigned char)(1 + i % 251);
208 require(write(file, pattern, page * 3) == (ssize_t)(page * 3),
"populate mapped file");
210 unsigned char*
private = mmap(NULL, page + 17, PROT_READ | PROT_WRITE, MAP_PRIVATE, file, 0);
211 require(
private != MAP_FAILED,
"private partial-length mapping");
212 require(
private[page + 25] == pattern[page + 25],
"mapping covers final complete page");
214 private[page + 25] = 0xA2;
215 require(mprotect(
private, page * 2, PROT_NONE) == 0 &&
216 mprotect(
private, page * 2, PROT_READ | PROT_WRITE) == 0 &&
private[0] == 0xA1 &&
217 private[page + 25] == 0xA2,
218 "private file protection preserves modifications");
219 require(msync(
private, page * 2, MS_SYNC | MS_INVALIDATE) == 0 &&
private[0] == 0xA1 &&
220 private[page + 25] == 0xA2,
221 "invalidation preserves private modifications");
223 require(pread(file, &
byte, 1, 0) == 1 &&
byte == pattern[0],
"private writes preserve file");
224 require(munmap(
private, page * 2) == 0,
"unmap private file");
226 unsigned char* sparse = mmap(NULL, page * 3, PROT_READ, MAP_SHARED, file, 0);
227 require(sparse != MAP_FAILED && sparse[page * 2] == pattern[page * 2],
228 "fault last sparse file page");
229 require(mprotect(sparse + page, page * 2, PROT_READ) == 0 && munmap(sparse, page * 3) == 0,
230 "unmap split with untouched prefix page");
232 unsigned char* reclaimed = mmap(sparse, page * 3, PROT_READ | PROT_WRITE,
233 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
234 if (reclaimed != sparse)
235 printf(
"VM-CONTRACT: original sparse requested=%p returned=%p errno=%d\n", (
void*)sparse,
236 (
void*)reclaimed, errno);
237 require(reclaimed == sparse,
"sparse split released address reservation");
238 unsigned char reclaimed_value = reclaimed[page * 2];
240 printf(
"VM-CONTRACT: original sparse address=%p offset=%zu value=%u\n", (
void*)sparse, page * 2,
242 require(reclaimed_value == 0,
"sparse split released every resident file page");
243 require(munmap(reclaimed, page * 3) == 0,
"unmap reclaimed sparse range");
244 sparse_file_splits(file, page, pattern);
246 unsigned char* shared = mmap(NULL, page * 3, PROT_READ | PROT_WRITE, MAP_SHARED, file, 0);
247 require(shared != MAP_FAILED,
"shared file mapping");
248 shared[page * 2] = 0xD1;
249 require(mprotect(shared + page, page, PROT_READ) == 0,
"split with untouched file page");
250 require(mprotect(shared + page * 2, page, PROT_NONE) == 0 &&
251 msync(shared + page * 2, page, MS_SYNC) == 0 &&
252 mprotect(shared + page * 2, page, PROT_READ) == 0 && shared[page * 2] == 0xD1,
253 "sync after removing shared write access");
254 require(pread(file, &
byte, 1, (off_t)(page * 2)) == 1 &&
byte == 0xD1,
255 "shared modifications reach backing view");
257 require(msync(shared, page, MS_SYNC | MS_ASYNC) == -1 && errno == EINVAL,
258 "sync rejects conflicting flags");
259 require(munmap(shared, page * 3) == 0,
"unmap sparse split file mapping");
261 int readonly = open(path, O_RDONLY);
262 require(readonly >= 0,
"open read-only mapping descriptor");
263 shared = mmap(NULL, page, PROT_READ, MAP_SHARED, readonly, 0);
264 require(shared != MAP_FAILED && shared[0] == pattern[0],
"read-only shared mapping");
266 require(mprotect(shared, page, PROT_READ | PROT_WRITE) == -1 && errno == EACCES,
267 "shared mapping remembers descriptor permissions");
268 require(munmap(shared, page) == 0,
"unmap read-only shared mapping");
270 require(mmap(NULL, page, PROT_READ | PROT_WRITE, MAP_SHARED, readonly, 0) == MAP_FAILED &&
272 "shared mapping rejects read-only descriptor writes");
273 private = mmap(NULL, page, PROT_READ | PROT_WRITE, MAP_PRIVATE, readonly, 0);
274 require(
private != MAP_FAILED,
"read-only descriptor allows private copy");
276 require(pread(readonly, &
byte, 1, 0) == 1 &&
byte == pattern[0],
"private copy remains isolated");
277 require(munmap(
private, page) == 0,
"unmap read-only descriptor private copy");
279 int writeonly = open(path, O_WRONLY);
280 require(writeonly >= 0,
"open write-only mapping descriptor");
282 require(mmap(NULL, page, PROT_NONE, MAP_PRIVATE, writeonly, 0) == MAP_FAILED && errno == EACCES,
283 "file mapping requires readable descriptor");
286 require(unlink(path) == 0,
"remove mapped file");
288 puts(
"VM-CONTRACT: PASS file-mappings");
291static volatile unsigned char* bus_address;
293static void mapped_bus_handler(
int signal, siginfo_t* info,
void* context) {
295 _exit(signal == SIGBUS && info->si_code == BUS_ADRERR && info->si_addr == (
void*)bus_address
305static struct shrink_child wait_for_shrink(unsigned char* address,
int check_info) {
306 int ready[2], proceed[2];
307 require(pipe(ready) == 0 && pipe(proceed) == 0,
"shrink barriers");
308 pid_t child = fork();
309 require(child >= 0,
"fork mapped shrink observer");
313 bus_address = address;
315 struct sigaction action = {0};
316 action.sa_sigaction = mapped_bus_handler;
317 action.sa_flags = SA_SIGINFO;
318 sigemptyset(&action.sa_mask);
319 if (sigaction(SIGBUS, &action, NULL))
322 signal(SIGBUS, SIG_DFL);
324 volatile unsigned char before = *bus_address;
327 if (write(ready[1], &
byte, 1) != 1 || read(proceed[0], &
byte, 1) != 1)
329 volatile unsigned char after = *bus_address;
336 require(read(ready[0], &
byte, 1) == 1,
"child has resident EOF page");
341static void reap_shrink_child(
struct shrink_child child,
int check_info) {
343 require(write(child.proceed, &
byte, 1) == 1,
"release mapped shrink observer");
344 close(child.proceed);
346 require(waitpid(child.pid, &status, 0) == child.pid,
"wait for mapped EOF signal");
347 require(check_info ? (WIFEXITED(status) && WEXITSTATUS(status) == 0)
348 : (WIFSIGNALED(status) && WTERMSIG(status) == SIGBUS),
349 check_info ?
"shared EOF signal address and BUS_ADRERR" :
"private EOF terminal SIGBUS");
352static void mapped_shrink(
size_t page) {
354 snprintf(path,
sizeof(path),
"/tmp/vm-shrink-%d", getpid());
355 int file = open(path, O_CREAT | O_EXCL | O_RDWR, 0600);
356 require(file >= 0,
"create shrink file");
357 unsigned char* bytes = malloc(page * 3);
358 require(bytes != NULL,
"shrink pattern");
359 memset(bytes, 0x49, page * 3);
360 require(write(file, bytes, page * 3) == (ssize_t)(page * 3),
"populate shrink file");
362 unsigned char* shared = mmap(NULL, page * 3, PROT_READ | PROT_WRITE, MAP_SHARED, file, 0);
363 unsigned char*
private = mmap(NULL, page * 3, PROT_READ | PROT_WRITE, MAP_PRIVATE, file, 0);
364 unsigned char* offset = mmap(NULL, page, PROT_READ, MAP_SHARED, file, (off_t)page);
365 require(shared != MAP_FAILED &&
private != MAP_FAILED && offset != MAP_FAILED,
"shrink mappings");
367 shared[page + 37] = 0x52;
368 private[page + 37] = 0xA5;
369 private[page * 2] = 0xA6;
370 require(offset[37] == 0x52,
"offset alias before shrink");
371 struct shrink_child shared_child = wait_for_shrink(shared + page * 2 + 11, 1);
372 struct shrink_child private_child = wait_for_shrink(
private + page * 2 + 13, 0);
373 require(mprotect(shared + page, page, PROT_NONE) == 0,
"inaccessible borrowed shrink page");
374 require(ftruncate(file, (off_t)(page + 17)) == 0,
375 "shrink with shared and private resident pages");
376 reap_shrink_child(shared_child, 1);
377 reap_shrink_child(private_child, 0);
378 require(mprotect(shared + page, page, PROT_READ | PROT_WRITE) == 0,
"restore shrunken tail");
379 require(shared[0] == 0x51 && shared[page + 16] == 0x49 && offset[16] == 0x49,
380 "shrink preserves preceding shared contents");
381 for (
size_t i = 17; i < page; ++i)
382 require(shared[page + i] == 0 && offset[i] == 0,
"shrink zeroes partial borrowed page");
383 require(
private[page + 37] == 0xA5 &&
private[page + 16] == 0x49,
384 "shrink preserves partial private COW page");
386 require(pread(file, shared + page * 2, 1, 0) == -1 && errno == EFAULT,
387 "usercopy into mapped EOF returns EFAULT");
388 require(ftruncate(file, (off_t)(page * 3)) == 0,
"regrow mapped file");
389 require(shared[page * 2] == 0 &&
private[page * 2] == 0,
390 "regrowth cannot resurrect discarded private or shared whole pages");
392 munmap(shared, page * 3) == 0 && munmap(
private, page * 3) == 0 && munmap(offset, page) == 0,
393 "unmap shrunken file aliases");
395 require(unlink(path) == 0,
"remove shrink file");
396 puts(
"VM-CONTRACT: PASS mapped-shrink");
399void test_vm_contracts(
void) {
400 long configured_page_size = sysconf(_SC_PAGESIZE);
401 require(configured_page_size > 0,
"page size");
402 size_t page = (size_t)configured_page_size;
403 anonymous_ranges(page);
404 protected_fork(page);
407 puts(
"VM-CONTRACT: PASS all");