The Pedigree Project 0.1
swap-contract-test/main.c
1#define _GNU_SOURCE
2#include <dirent.h>
3#include <errno.h>
4#include <fcntl.h>
5#include <stdint.h>
6#include <stdio.h>
7#include <string.h>
8#include <unistd.h>
9
10#include <sys/mman.h>
11#include <sys/stat.h>
12#include <sys/swap.h>
13#include <sys/syscall.h>
14#include <sys/sysmacros.h>
15#include <sys/wait.h>
16
17static int failure(const char* name) {
18 fprintf(stderr, "SWAP-TEST: FAIL %s errno=%d\n", name, errno);
19 return 1;
20}
21static int read_header(const char* path, unsigned char* header) {
22 int fd = open(path, O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
23 if (fd < 0)
24 return -1;
25 ssize_t received;
26 do {
27 received = pread(fd, header, 4096, 0);
28 } while (received < 0 && errno == EINTR);
29 int error = received == 4096 ? 0 : received < 0 ? errno : EIO;
30 if (close(fd) && !error)
31 error = errno;
32 errno = error;
33 return error ? -1 : 0;
34}
35static uint32_t header_word(const unsigned char* data, size_t offset) {
36 return (uint32_t)data[offset] | ((uint32_t)data[offset + 1] << 8) |
37 ((uint32_t)data[offset + 2] << 16) | ((uint32_t)data[offset + 3] << 24);
38}
39static int discover_device(char* selected, size_t capacity) {
40 DIR* directory = opendir("/dev/block");
41 if (!directory)
42 return -1;
43 size_t matches = 0;
44 int error = 0;
45 for (;;) {
46 errno = 0;
47 struct dirent* entry = readdir(directory);
48 if (!entry) {
49 error = errno;
50 break;
51 }
52 if (strncmp(entry->d_name, "disk", 4) || !entry->d_name[4])
53 continue;
54 const char* digit = entry->d_name + 4;
55 while (*digit >= '0' && *digit <= '9')
56 ++digit;
57 if (*digit)
58 continue;
59 char candidate[512];
60 int length = snprintf(candidate, sizeof(candidate), "/dev/block/%s", entry->d_name);
61 if (length < 0 || (size_t)length >= sizeof(candidate)) {
62 error = ENAMETOOLONG;
63 break;
64 }
65 struct stat attributes;
66 if (stat(candidate, &attributes)) {
67 error = errno;
68 break;
69 }
70 if (!S_ISBLK(attributes.st_mode) || major(attributes.st_rdev) != 241 ||
71 attributes.st_size != (off_t)4097 * 4096)
72 continue;
73 int fd = open(candidate, O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
74 if (fd < 0) {
75 error = errno;
76 break;
77 }
78 struct stat opened;
79 unsigned char header[4096];
80 ssize_t received = -1;
81 if (fstat(fd, &opened))
82 error = errno;
83 else if (!S_ISBLK(opened.st_mode) || opened.st_rdev != attributes.st_rdev ||
84 opened.st_size != attributes.st_size)
85 error = EIO;
86 else {
87 do {
88 received = pread(fd, header, sizeof(header), 0);
89 } while (received < 0 && errno == EINTR);
90 if (received != (ssize_t)sizeof(header))
91 error = received < 0 ? errno : EIO;
92 }
93 if (close(fd) && !error)
94 error = errno;
95 if (error)
96 break;
97 if (memcmp(header + 4086, "SWAPSPACE2", 10) || header_word(header, 1024) != 1 ||
98 header_word(header, 1028) != 4096 || header_word(header, 1032))
99 continue;
100 if (++matches > 1) {
101 error = EEXIST;
102 break;
103 }
104 if ((size_t)length >= capacity) {
105 error = ENAMETOOLONG;
106 break;
107 }
108 memcpy(selected, candidate, (size_t)length + 1);
109 }
110 if (closedir(directory) && !error)
111 error = errno;
112 if (!error && !matches)
113 error = ENOENT;
114 errno = error;
115 return error ? -1 : 0;
116}
117static unsigned char value(size_t page, size_t byte) {
118 return (unsigned char)(page * 29 + byte * 7 + 0x35);
119}
120static int contents(const unsigned char* p, size_t pages, size_t bytes) {
121 for (size_t n = 0; n < pages; ++n)
122 for (size_t i = 0; i < bytes; ++i) {
123 const unsigned char actual = p[n * bytes + i], expected = value(n, i);
124 if (actual != expected) {
125 errno = 0;
126 fprintf(stderr, "SWAP-TEST: CONTENT page=%zu byte=%zu expected=%u actual=%u\n", n, i,
127 (unsigned)expected, (unsigned)actual);
128 return 0;
129 }
130 }
131 return 1;
132}
133static int resident(unsigned char* p, size_t pages, size_t bytes, int expected) {
134 unsigned char vector[16];
135 if (pages > sizeof(vector)) {
136 errno = EINVAL;
137 return 0;
138 }
139 errno = 0;
140 if (mincore(p, pages * bytes, vector)) {
141 fprintf(stderr, "SWAP-TEST: MINCORE error=%d\n", errno);
142 return 0;
143 }
144 for (size_t i = 0; i < pages; ++i)
145 if (!!(vector[i] & 1) != expected) {
146 fprintf(stderr, "SWAP-TEST: RESIDENCY page=%zu expected=%d actual=%u\n", i, expected,
147 (unsigned)vector[i]);
148 return 0;
149 }
150 return 1;
151}
152int main(int argc, char** argv) {
153 setvbuf(stdout, NULL, _IONBF, 0);
154 alarm(60);
155 if (argc != 2 || geteuid() || sysconf(_SC_PAGESIZE) != 4096)
156 return failure("requires root and a prepared disposable device or --discover");
157 const size_t page = 4096, pages = 8, bytes = pages * page;
158 const char* device = argv[1];
159 char discovered[512];
160 if (!strcmp(device, "--discover")) {
161 if (discover_device(discovered, sizeof(discovered)))
162 return failure("discovery requires exactly one prepared disposable disk");
163 device = discovered;
164 }
165 struct stat st;
166 if (stat(device, &st) || !S_ISBLK(st.st_mode))
167 return failure("physical device selection");
168 printf("SWAP-TEST: DEVICE path=%s size=%llu\n", device, (unsigned long long)st.st_size);
169 unsigned char originalHeader[4096];
170 if (read_header(device, originalHeader))
171 return failure("initial header snapshot");
172 errno = 0;
173 if (swapon((const char*)1, 0) != -1 || errno != EFAULT)
174 return failure("path copy");
175 errno = 0;
176 if (swapon(device, 0x80000000U) != -1 || errno != EINVAL)
177 return failure("unknown flags");
178 errno = 0;
179 if (swapon(device, SWAP_FLAG_DISCARD) != -1 || errno != EOPNOTSUPP)
180 return failure("unsupported flags");
181 pid_t child = fork();
182 if (child < 0)
183 return failure("privilege fork");
184 if (!child) {
185 if (setuid(1000))
186 _exit(2);
187 errno = 0;
188 if (swapon(device, 0) != -1 || errno != EPERM)
189 _exit(3);
190 errno = 0;
191 _exit(swapoff(device) == -1 && errno == EPERM ? 0 : 4);
192 }
193 int status;
194 if (waitpid(child, &status, 0) != child || !WIFEXITED(status) || WEXITSTATUS(status))
195 return failure("privilege gate");
196 puts("SWAP-TEST: PASS inputs-permissions");
197 unsigned char* p = mmap(0, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
198 if (p == MAP_FAILED)
199 return failure("anonymous mapping");
200 for (size_t n = 0; n < pages; ++n)
201 for (size_t i = 0; i < page; ++i)
202 p[n * page + i] = value(n, i);
203 int result = 1, active = 0;
204 if (swapon(device, 0)) {
205 failure("activate");
206 goto done;
207 }
208 active = 1;
209 errno = 0;
210 if (swapon(device, 0) != -1 || errno != EBUSY) {
211 failure("duplicate activation");
212 goto done;
213 }
214 if (madvise(p, bytes, MADV_PAGEOUT) || !resident(p, pages, page, 0)) {
215 failure("pageout residency");
216 goto done;
217 }
218 int nullfd = open("/tmp/swap-contract-copy", O_WRONLY | O_CREAT | O_TRUNC, 0600);
219 if (nullfd < 0 || write(nullfd, p, bytes) != (ssize_t)bytes) {
220 if (nullfd >= 0)
221 close(nullfd);
222 failure("usercopy fault-in");
223 goto done;
224 }
225 close(nullfd);
226 unlink("/tmp/swap-contract-copy");
227 if (!resident(p, pages, page, 1)) {
228 failure("usercopy residency");
229 goto done;
230 }
231 if (!contents(p, pages, page)) {
232 failure("restored contents");
233 goto done;
234 }
235 puts("SWAP-TEST: PASS eviction-usercopy-faultin");
236 if (madvise(p, bytes, MADV_PAGEOUT)) {
237 failure("fork preparation");
238 goto done;
239 }
240 child = fork();
241 if (child < 0) {
242 failure("swapped fork");
243 goto done;
244 }
245 if (!child) {
246 printf("SWAP-TEST: CHILD stage=read-swapped address=%p pages=%zu\n", (void*)p, pages);
247 if (!contents(p, pages, page))
248 _exit(5);
249 puts("SWAP-TEST: CHILD stage=private-write");
250 p[0] ^= 0xff;
251 puts("SWAP-TEST: CHILD stage=unmap");
252 _exit(munmap(p, bytes) ? 6 : 0);
253 }
254 errno = 0;
255 pid_t reaped = waitpid(child, &status, 0);
256 if (reaped != child) {
257 fprintf(stderr, "SWAP-TEST: CHILD wait expected=%ld actual=%ld errno=%d\n", (long)child,
258 (long)reaped, errno);
259 failure("fork wait");
260 goto done;
261 }
262 if (!WIFEXITED(status) || WEXITSTATUS(status)) {
263 fprintf(stderr, "SWAP-TEST: CHILD status=%#x exited=%d code=%d signalled=%d signal=%d\n",
264 status, WIFEXITED(status), WIFEXITED(status) ? WEXITSTATUS(status) : -1,
265 WIFSIGNALED(status), WIFSIGNALED(status) ? WTERMSIG(status) : 0);
266 failure("fork child completion");
267 goto done;
268 }
269 if (!contents(p, pages, page)) {
270 failure("fork parent contents");
271 goto done;
272 }
273 if (madvise(p, bytes, MADV_PAGEOUT)) {
274 failure("remap preparation");
275 goto done;
276 }
277 unsigned char* destination = mmap(0, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
278 if (destination == MAP_FAILED) {
279 failure("remap destination");
280 goto done;
281 }
282 void* moved = mremap(p, bytes, bytes, MREMAP_MAYMOVE | MREMAP_FIXED, destination);
283 if (moved == MAP_FAILED) {
284 munmap(destination, bytes);
285 failure("swapped remap");
286 goto done;
287 }
288 p = moved;
289 if (!resident(p, pages, page, 0) || !contents(p, pages, page)) {
290 failure("remap contents");
291 goto done;
292 }
293 puts("SWAP-TEST: PASS fork-remap");
294 if (madvise(p, bytes, MADV_PAGEOUT) || mlock(p, page) || !resident(p, 1, page, 1)) {
295 failure("mlock fault-in");
296 goto done;
297 }
298 errno = 0;
299 if (madvise(p, bytes, MADV_PAGEOUT) != -1 || errno != EOPNOTSUPP || !resident(p, 1, page, 1) ||
300 munlock(p, page)) {
301 failure("locked range admission");
302 goto done;
303 }
304 if (swapoff(device)) {
305 failure("drain");
306 goto done;
307 }
308 active = 0;
309 if (!resident(p, pages, page, 1) || !contents(p, pages, page)) {
310 failure("drain contents");
311 goto done;
312 }
313 errno = 0;
314 if (swapoff(device) != -1 || errno != EINVAL || swapon(device, 0)) {
315 failure("retirement reuse");
316 goto done;
317 }
318 active = 1;
319 if (madvise(p, bytes, MADV_PAGEOUT) || madvise(p, bytes, MADV_DONTNEED) || swapoff(device)) {
320 failure("discard releases slots");
321 goto done;
322 }
323 active = 0;
324 for (size_t i = 0; i < bytes; ++i)
325 if (p[i]) {
326 failure("discard zero-fill");
327 goto done;
328 }
329 puts("SWAP-TEST: PASS mlock-swapoff-discard-reuse");
330 result = 0;
331done:
332 munmap(p, bytes);
333 if (active) {
334 if (swapoff(device))
335 result = failure("cleanup swapoff");
336 else
337 active = 0;
338 }
339 if (!active) {
340 unsigned char currentHeader[4096];
341 if (read_header(device, currentHeader))
342 result = failure("final header snapshot");
343 else {
344 for (size_t i = 0; i < sizeof(originalHeader); ++i)
345 if (currentHeader[i] != originalHeader[i]) {
346 errno = 0;
347 fprintf(stderr, "SWAP-TEST: HEADER byte=%zu expected=%u actual=%u\n", i,
348 (unsigned)originalHeader[i], (unsigned)currentHeader[i]);
349 result = failure("swap header changed");
350 break;
351 }
352 }
353 }
354 if (!result)
355 puts("SWAP-TEST: END PASS");
356 return result;
357}