The Pedigree Project 0.1
locks.c
1#define _GNU_SOURCE
2#include <signal.h>
3#include <unistd.h>
4
5#include "contract.h"
6#include <sys/mman.h>
7
8static int replacement_charge(void) {
9 int failed = 0;
10 struct rp_file file = {.fd = -1};
11 unsigned char* mapping = MAP_FAILED;
12 void* extra = MAP_FAILED;
13 CHECK(rp_create(&file, RP_MEMFD) == 0);
14 mapping = mmap(NULL, 2 * rp_page, PROT_READ, MAP_SHARED, file.fd, 0);
15 extra = mmap(NULL, 2 * rp_page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
16 CHECK(mapping != MAP_FAILED && extra != MAP_FAILED);
17 CHECK(rp_limit(2 * rp_page) == 0 && mlock(mapping, 2 * rp_page) == 0);
18 errno = 0;
19 CHECK(remap_file_pages(mapping, 2 * rp_page, 0, 4, MAP_NONBLOCK) == -1 && errno == EAGAIN);
20 CHECK(rp_matches(mapping, 0, 2 * rp_page, 0));
21 CHECK(rp_limit(4 * rp_page) == 0);
22 CHECK(remap_file_pages(mapping, 2 * rp_page, 0, 4, MAP_NONBLOCK) == 0);
23 CHECK(rp_matches(mapping, 4 * rp_page, 2 * rp_page, 0));
24 // The replacement consumes two pages after commit, despite admitting four before it.
25 CHECK(mlock(extra, 2 * rp_page) == 0);
26 errno = 0;
27 CHECK(msync(mapping, 2 * rp_page, MS_SYNC | MS_INVALIDATE) == -1 && errno == EBUSY);
28 CHECK(munlock(extra, 2 * rp_page) == 0 && rp_limit(0) == 0);
29 errno = 0;
30 CHECK(remap_file_pages(mapping, 2 * rp_page, 0, 0, 0) == -1 && errno == EPERM);
31 CHECK(rp_matches(mapping, 4 * rp_page, 2 * rp_page, 0));
32 CHECK(munlock(mapping, 2 * rp_page) == 0);
33 CHECK(msync(mapping, 2 * rp_page, MS_SYNC | MS_INVALIDATE) == 0);
34 CHECK(rp_limit(2 * rp_page) == 0 && mlock(extra, 2 * rp_page) == 0);
35out:
36 munlockall();
37 if (extra != MAP_FAILED)
38 munmap(extra, 2 * rp_page);
39 if (mapping != MAP_FAILED)
40 munmap(mapping, 2 * rp_page);
41 rp_close(&file);
42 return failed;
43}
44
45static int future_zero_limit(void) {
46 int failed = 0;
47 struct rp_file file = {.fd = -1};
48 unsigned char* mapping = MAP_FAILED;
49 CHECK(rp_create(&file, RP_MEMFD) == 0);
50 mapping = mmap(NULL, rp_page, PROT_READ, MAP_SHARED, file.fd, 0);
51 CHECK(mapping != MAP_FAILED && rp_matches(mapping, 0, rp_page, 0));
52 CHECK(rp_limit(2 * rp_page) == 0 && mlockall(MCL_FUTURE | MCL_ONFAULT) == 0);
53 CHECK(rp_limit(0) == 0);
54 errno = 0;
55 CHECK(remap_file_pages(mapping, rp_page, 0, 4, 0) == -1 && errno == EAGAIN);
56 CHECK(rp_matches(mapping, 0, rp_page, 0));
57 CHECK(munlockall() == 0);
58 CHECK(remap_file_pages(mapping, rp_page, 0, 4, MAP_NONBLOCK) == 0);
59 CHECK(rp_matches(mapping, 4 * rp_page, rp_page, 0));
60out:
61 munlockall();
62 if (mapping != MAP_FAILED)
63 munmap(mapping, rp_page);
64 rp_close(&file);
65 return failed;
66}
67
68static int lock_mode_conversion(void) {
69 int failed = 0;
70 struct rp_file file = {.fd = -1};
71 unsigned char* mapping = MAP_FAILED;
72 CHECK(rp_create(&file, RP_MEMFD) == 0 && rp_limit(8 * rp_page) == 0);
73 mapping = mmap(NULL, 2 * rp_page, PROT_READ, MAP_SHARED, file.fd, 0);
74 CHECK(mapping != MAP_FAILED);
75 CHECK(mlock2(mapping, rp_page, MLOCK_ONFAULT) == 0);
76 CHECK(mlock(mapping + rp_page, rp_page) == 0);
77 errno = 0;
78 CHECK(remap_file_pages(mapping, 2 * rp_page, 0, 4, 0) == -1 && errno == EINVAL);
79 CHECK(rp_matches(mapping, 0, 2 * rp_page, 0));
80 CHECK(remap_file_pages(mapping, rp_page, 0, 4, MAP_NONBLOCK) == 0);
81 CHECK(rp_matches(mapping, 4 * rp_page, rp_page, 0));
82 // Equal adjacent lock modes admit a spanning remap; this observes ONFAULT becoming eager.
83 CHECK(remap_file_pages(mapping, 2 * rp_page, 0, 2, 0) == 0);
84 CHECK(rp_matches(mapping, 2 * rp_page, 2 * rp_page, 0));
85 errno = 0;
86 CHECK(msync(mapping, 2 * rp_page, MS_SYNC | MS_INVALIDATE) == -1 && errno == EBUSY);
87 CHECK(rp_fault(mapping, SIGSEGV, 1) == 0);
88 CHECK(mprotect(mapping, rp_page, PROT_NONE) == 0);
89 CHECK(rp_fault(mapping, SIGSEGV, 0) == 0);
90 CHECK(mprotect(mapping, rp_page, PROT_READ) == 0);
91 CHECK(rp_matches(mapping, 2 * rp_page, rp_page, 0));
92 CHECK(munlock(mapping, rp_page) == 0);
93 CHECK(msync(mapping, rp_page, MS_SYNC | MS_INVALIDATE) == 0);
94 errno = 0;
95 CHECK(msync(mapping + rp_page, rp_page, MS_SYNC | MS_INVALIDATE) == -1 && errno == EBUSY);
96 CHECK(munlock(mapping + rp_page, rp_page) == 0);
97 CHECK(msync(mapping + rp_page, rp_page, MS_SYNC | MS_INVALIDATE) == 0);
98out:
99 munlockall();
100 if (mapping != MAP_FAILED)
101 munmap(mapping, 2 * rp_page);
102 rp_close(&file);
103 return failed;
104}
105
106static int future_onfault_mode(void) {
107 int failed = 0;
108 struct rp_file file = {.fd = -1};
109 unsigned char* mapping = MAP_FAILED;
110 CHECK(rp_create(&file, RP_MEMFD) == 0 && rp_limit(8 * rp_page) == 0);
111 mapping = mmap(NULL, 2 * rp_page, PROT_READ, MAP_SHARED, file.fd, 0);
112 CHECK(mapping != MAP_FAILED && mlock2(mapping, 2 * rp_page, MLOCK_ONFAULT) == 0);
113 CHECK(mlockall(MCL_FUTURE | MCL_ONFAULT) == 0);
114 CHECK(remap_file_pages(mapping, rp_page, 0, 4, 0) == 0);
115 // FUTURE ONFAULT keeps the changed fragment compatible with its untouched neighbor.
116 CHECK(remap_file_pages(mapping, 2 * rp_page, 0, 2, 0) == 0);
117 CHECK(rp_matches(mapping, 2 * rp_page, 2 * rp_page, 0));
118 errno = 0;
119 CHECK(msync(mapping, 2 * rp_page, MS_SYNC | MS_INVALIDATE) == -1 && errno == EBUSY);
120 CHECK(munlockall() == 0);
121 CHECK(msync(mapping, 2 * rp_page, MS_SYNC | MS_INVALIDATE) == 0);
122out:
123 munlockall();
124 if (mapping != MAP_FAILED)
125 munmap(mapping, 2 * rp_page);
126 rp_close(&file);
127 return failed;
128}
129
130int rp_locks(void) {
131 if (rp_unprivileged())
132 return 1;
133 return replacement_charge() || future_zero_limit() || lock_mode_conversion() ||
134 future_onfault_mode();
135}