The Pedigree Project 0.1
file-resize-contract-test/main.c
1#define _GNU_SOURCE
2#include <fcntl.h>
3#include <poll.h>
4#include <signal.h>
5#include <stdlib.h>
6#include <string.h>
7#include <time.h>
8#include <unistd.h>
9
10#include "contract.h"
11#include <sys/mman.h>
12#include <sys/stat.h>
13#include <sys/wait.h>
14
15size_t fr_page;
16
17static int64_t now(void) {
18 struct timespec time;
19 return clock_gettime(CLOCK_MONOTONIC, &time) ? -1
20 : (int64_t)time.tv_sec * 1000000000 + time.tv_nsec;
21}
22int fr_reap(pid_t child, int milliseconds) {
23 int64_t deadline = now() + (int64_t)milliseconds * 1000000;
24 while (now() < deadline) {
25 int status;
26 pid_t result = waitpid(child, &status, WNOHANG);
27 if (result == child)
28 return WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status);
29 if (result < 0 && errno != EINTR)
30 return -1;
31 struct timespec pause = {0, 5000000};
32 nanosleep(&pause, NULL);
33 }
34 kill(child, SIGKILL);
35 while (waitpid(child, NULL, 0) < 0 && errno == EINTR) {
36 }
37 return -1;
38}
39static void expected_bus(int signal) {
40 (void)signal;
41 _exit(0);
42}
43int fr_fault(void* address) {
44 pid_t child = fork();
45 if (child < 0)
46 return -1;
47 if (!child) {
48 alarm(3);
49 struct sigaction action = {.sa_handler = expected_bus};
50 sigemptyset(&action.sa_mask);
51 if (sigaction(SIGBUS, &action, NULL))
52 _exit(2);
53 volatile unsigned char value = *(volatile unsigned char*)address;
54 (void)value;
55 _exit(1);
56 }
57 int result = fr_reap(child, 4000);
58 if (result)
59 fprintf(stderr, "FILE-RESIZE-CONTRACT: expected SIGBUS at %p, child status=%d\n", address,
60 result);
61 return result;
62}
63int fr_send(int fd, char byte) {
64 ssize_t result;
65 do {
66 result = write(fd, &byte, 1);
67 } while (result < 0 && errno == EINTR);
68 return result == 1 ? 0 : -1;
69}
70int fr_receive(int fd, char expected) {
71 int64_t deadline = now() + 5000000000;
72 while (now() < deadline) {
73 struct pollfd watch = {.fd = fd, .events = POLLIN};
74 int result = poll(&watch, 1, 100);
75 if (result < 0 && errno == EINTR)
76 continue;
77 if (result < 0)
78 return -1;
79 if (!result)
80 continue;
81 char byte;
82 ssize_t amount = read(fd, &byte, 1);
83 if (amount < 0 && errno == EINTR)
84 continue;
85 if (amount == 1 && byte == expected)
86 return 0;
87 errno = EIO;
88 return -1;
89 }
90 errno = ETIMEDOUT;
91 return -1;
92}
93unsigned char fr_pattern(size_t offset) {
94 return (unsigned char)(0x31 + offset * 37 + (offset >> 8) * 11);
95}
96int fr_matches(const volatile unsigned char* bytes, size_t offset, size_t length, int zero) {
97 for (size_t n = 0; n < length; ++n) {
98 unsigned char expected = zero ? 0 : fr_pattern(offset + n);
99 unsigned char actual = bytes[n];
100 if (actual != expected) {
101 fprintf(stderr, "FILE-RESIZE-CONTRACT: byte offset=%zu got=%u expected=%u\n", offset + n,
102 (unsigned)actual, (unsigned)expected);
103 return 0;
104 }
105 }
106 return 1;
107}
108int fr_contents(int fd, size_t offset, size_t length, int zero) {
109 unsigned char bytes[512];
110 while (length) {
111 size_t amount = length < sizeof(bytes) ? length : sizeof(bytes);
112 if (pread(fd, bytes, amount, offset) != (ssize_t)amount ||
113 !fr_matches(bytes, offset, amount, zero))
114 return -1;
115 offset += amount;
116 length -= amount;
117 }
118 return 0;
119}
120int fr_size(int fd, size_t size) {
121 struct stat metadata;
122 if (fstat(fd, &metadata))
123 return -1;
124 if (metadata.st_size == (off_t)size)
125 return 0;
126 fprintf(stderr, "FILE-RESIZE-CONTRACT: size=%lld expected=%zu\n", (long long)metadata.st_size,
127 size);
128 return -1;
129}
130int fr_resident(void* address, size_t pages, unsigned bits, const char* stage) {
131 unsigned char vector[4] = {0xa5, 0xa5, 0xa5, 0xa5};
132 if (pages > sizeof(vector) || mincore(address, pages * fr_page, vector)) {
133 fprintf(stderr, "FILE-RESIZE-CONTRACT: %s mincore failed errno=%d\n", stage, errno);
134 return -1;
135 }
136 for (size_t n = 0; n < pages; ++n) {
137 if ((vector[n] & 1) != ((bits >> n) & 1)) {
138 fprintf(stderr, "FILE-RESIZE-CONTRACT: %s residency=%u,%u,%u,%u pages=%zu expected bits=%x\n",
139 stage, vector[0], vector[1], vector[2], vector[3], pages, bits);
140 return -1;
141 }
142 }
143 return 0;
144}
145int fr_create(struct fr_file* file, int backend) {
146 static unsigned sequence;
147 file->fd = -1;
148 file->backend = backend;
149 file->path[0] = 0;
150 if (backend == FR_MEMFD)
151 file->fd = memfd_create("file-resize", MFD_CLOEXEC | MFD_ALLOW_SEALING);
152 else {
153 snprintf(file->path, sizeof(file->path), "%s/file-resize-%ld-%u",
154 backend == FR_RAMFS ? "/tmp" : "", (long)getpid(), ++sequence);
155 file->fd = open(file->path, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, 0600);
156 if (file->fd < 0) {
157 fprintf(stderr, "FILE-RESIZE-CONTRACT: create %s failed errno=%d\n", file->path, errno);
158 file->path[0] = 0;
159 }
160 }
161 if (file->fd < 0)
162 return -1;
163 unsigned char* bytes = malloc(fr_page);
164 int error = !bytes || ftruncate(file->fd, 4 * fr_page);
165 for (size_t offset = 0; !error && offset < 4 * fr_page; offset += fr_page) {
166 for (size_t n = 0; n < fr_page; ++n)
167 bytes[n] = fr_pattern(offset + n);
168 error = pwrite(file->fd, bytes, fr_page, offset) != (ssize_t)fr_page;
169 }
170 free(bytes);
171 if (error) {
172 int saved = errno;
173 fr_close(file);
174 errno = saved;
175 return -1;
176 }
177 return 0;
178}
179int fr_open_alias(const struct fr_file* file) {
180 if (file->backend == FR_EXT2) {
181 char alias[160];
182 snprintf(alias, sizeof(alias), "%s.alias", file->path);
183 if (link(file->path, alias))
184 return -1;
185 int fd = open(alias, O_RDWR | O_CLOEXEC);
186 int saved = errno;
187 if (unlink(alias)) {
188 saved = errno;
189 if (fd >= 0)
190 close(fd);
191 fd = -1;
192 }
193 errno = saved;
194 return fd;
195 }
196 return file->path[0] ? open(file->path, O_RDWR | O_CLOEXEC) : dup(file->fd);
197}
198void fr_close(struct fr_file* file) {
199 if (file->fd >= 0)
200 close(file->fd);
201 if (file->path[0])
202 unlink(file->path);
203 file->fd = -1;
204 file->path[0] = 0;
205}
206static int run(const char* name, int backend) {
207 printf("FILE-RESIZE-CONTRACT: BEGIN %s\n", name);
208 fflush(stdout);
209 pid_t child = fork();
210 if (child < 0)
211 return -1;
212 if (!child) {
213 alarm(40);
214 int result = backend < 0 ? fr_seals() : fr_shared(backend) || fr_private(backend);
215 fflush(stdout);
216 fflush(stderr);
217 _exit(result ? 1 : 0);
218 }
219 int result = fr_reap(child, 45000);
220 printf("FILE-RESIZE-CONTRACT: %s %s status=%d\n", result ? "FAIL" : "PASS", name, result);
221 fflush(stdout);
222 return result;
223}
224int main(int argc, char** argv) {
225 fr_page = (size_t)sysconf(_SC_PAGESIZE);
226 if (fr_page < 512 || fr_page > 65536 || (fr_page & (fr_page - 1)))
227 return 2;
228 signal(SIGPIPE, SIG_IGN);
229 static const char* names[] = {"memfd", "ramfs", "ext2", "sealed"};
230 int selected = 0;
231 for (int n = 0; n < 4; ++n) {
232 if (argc > 1 && strcmp(argv[1], names[n]))
233 continue;
234 selected = 1;
235 if (run(names[n], n == 3 ? -1 : n))
236 return 1;
237 }
238 if (!selected)
239 return 2;
240 puts("FILE-RESIZE-CONTRACT: END PASS");
241 return 0;
242}