14#include <netinet/in.h>
20#include <sys/statfs.h>
21#include <sys/statvfs.h>
22#include <sys/syscall.h>
29extern void test_socket_path_contracts(
void);
31static void require(
int condition,
const char* operation) {
33 printf(
"USERCOPY-CONTRACT: FAIL %s errno=%d\n", operation, errno);
38static void socket_buffers(
void* inaccessible,
size_t page) {
40 require(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0,
"stream socketpair");
42 require(setsockopt(sockets[0], SOL_SOCKET, SO_REUSEADDR, &enabled,
sizeof(enabled)) == 0,
43 "snapshot socket option input");
45 require(setsockopt(sockets[0], SOL_SOCKET, SO_REUSEADDR, inaccessible,
sizeof(
int)) == -1 &&
47 "socket option input protection");
48 unsigned char option[
sizeof(int) + 2];
49 memset(option, 0xA7,
sizeof(option));
50 socklen_t option_length = 1;
51 require(getsockopt(sockets[0], SOL_SOCKET, SO_TYPE, option + 1, &option_length) == 0 &&
52 option_length == 1 && option[0] == 0xA7 && option[2] == 0xA7 &&
53 option[1] == SOCK_STREAM,
54 "truncated socket option output");
57 getsockopt(sockets[0], SOL_SOCKET, SO_TYPE, NULL, &option_length) == 0 && option_length == 0,
58 "zero capacity socket option");
61 getsockopt(sockets[0], SOL_SOCKET, SO_TYPE, option, inaccessible) == -1 && errno == EFAULT,
62 "socket option length protection");
64 char first[] =
"ab", second[] =
"cd";
65 struct iovec send_vectors[] = {{NULL, 0}, {first, 2}, {NULL, 0}, {second, 2}};
66 struct msghdr
message = {.msg_iov = send_vectors, .msg_iovlen = 4};
67 require(sendmsg(sockets[0], &
message, 0) == 4,
"gather stream payload");
68 char* output = mmap(NULL, page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
69 require(output != MAP_FAILED,
"demand receive buffer");
70 struct iovec receive_vectors[] = {{output, 1}, {NULL, 0}, {output + 1, 3}};
71 message = (
struct msghdr){.msg_iov = receive_vectors, .msg_iovlen = 3};
72 require(recvmsg(sockets[1], &
message, 0) == 4 && !memcmp(output,
"abcd", 4),
73 "scatter into demand pages");
75 require(send(sockets[0], inaccessible, 4, 0) == -1 && errno == EFAULT,
"stream send protection");
76 require(send(sockets[0],
"x", 1, 0) == 1,
"queue protected receive");
78 require(recv(sockets[1], inaccessible, 1, 0) == -1 && errno == EFAULT,
79 "stream receive protection");
80 require(munmap(output, page) == 0,
"release receive buffer");
84 struct sockaddr_un destination = {.sun_family = AF_UNIX};
85 snprintf(destination.sun_path,
sizeof(destination.sun_path),
"/tmp/usercopy-dgram-%ld.sock",
87 const socklen_t destination_length =
88 offsetof(
struct sockaddr_un, sun_path) + strlen(destination.sun_path) + 1;
89 unlink(destination.sun_path);
90 sockets[0] = socket(AF_UNIX, SOCK_DGRAM, 0);
91 require(sockets[0] >= 0,
"create datagram sender");
92 sockets[1] = socket(AF_UNIX, SOCK_DGRAM, 0);
93 require(sockets[1] >= 0,
"create datagram receiver");
94 require(bind(sockets[1], (
struct sockaddr*)&destination, destination_length) == 0,
95 "bind datagram pathname");
96 require(connect(sockets[0], (
struct sockaddr*)&destination, destination_length) == 0,
97 "connect datagram pathname");
98 require(send(sockets[0],
"packet", 6, 0) == 6,
"datagram send");
99 char truncated[4] = {0, 0, 0, 0x6A};
100 require(recv(sockets[1], truncated, 3, MSG_TRUNC) == 6 && !memcmp(truncated,
"pac", 3) &&
101 truncated[3] == 0x6A,
102 "datagram truncation copy extent");
103 require(unlink(destination.sun_path) == 0,
"remove datagram pathname");
106 puts(
"USERCOPY-CONTRACT: PASS socket-buffers");
109void test_tcp_receive_buffers(
void) {
112 int listener = socket(AF_INET, SOCK_STREAM, 0);
113 require(listener >= 0,
"TCP receive listener");
114 struct sockaddr_in address = {.sin_family = AF_INET, .sin_addr.s_addr = htonl(
INADDR_LOOPBACK)};
116 bind(listener, (
struct sockaddr*)&address,
sizeof(address)) == 0 && listen(listener, 1) == 0,
117 "TCP receive listen");
118 socklen_t address_length =
sizeof(address);
119 require(getsockname(listener, (
struct sockaddr*)&address, &address_length) == 0,
120 "TCP receive listener address");
121 int sender = socket(AF_INET, SOCK_STREAM, 0);
122 require(sender >= 0 && connect(sender, (
struct sockaddr*)&address, address_length) == 0,
123 "TCP receive connect");
124 int receiver = accept(listener, (
struct sockaddr*)&address, &address_length);
125 require(receiver >= 0,
"TCP receive accept");
126 int alias = dup(receiver);
127 require(alias >= 0,
"TCP receive duplicate");
129 int epoll = epoll_create1(0);
130 struct epoll_event interest = {.events = EPOLLIN | EPOLLRDHUP | EPOLLET, .data.fd = receiver};
131 require(epoll >= 0 && epoll_ctl(epoll, EPOLL_CTL_ADD, receiver, &interest) == 0,
132 "TCP receive edge watch");
134 require(recv(receiver, output, 0, 0) == 0,
"empty TCP receive capacity");
136 require(recv(receiver, output,
sizeof(output), MSG_DONTWAIT) == -1 && errno == EAGAIN &&
137 !(fcntl(receiver, F_GETFL) & O_NONBLOCK),
138 "empty TCP per-call nonblocking receive");
139 require(send(sender,
"x", 1, 0) == 1,
"TCP short receive payload");
140 struct epoll_event event = {};
141 require(epoll_wait(epoll, &event, 1, 5000) == 1 && (event.events & EPOLLIN),
142 "TCP receive first edge");
143 require(recv(receiver, output,
sizeof(output), 0) == 1 && output[0] ==
'x',
144 "TCP short receive does not wait for more data");
145 require(epoll_wait(epoll, &event, 1, 0) == 0,
"TCP drain clears receive edge");
148 for (
size_t i = 0; i <
sizeof(payload); ++i) {
149 payload[i] = (char)(i * 37);
152 while (sent <
sizeof(payload)) {
153 ssize_t result = send(sender, payload + sent,
sizeof(payload) - sent, 0);
154 require(result > 0,
"TCP queue receive payload");
155 sent += (size_t)result;
157 require(shutdown(sender, SHUT_WR) == 0,
"TCP finish receive payload");
162 require(epoll_wait(epoll, &event, 1, 5000) == 1,
"TCP receive refill edge");
163 refilled |= !!(
event.events & EPOLLIN);
164 }
while (!(event.events & EPOLLRDHUP));
165 require(refilled,
"TCP refill remains readable before EOF");
167 require(recv(receiver, output, 257, 0) == 257 && !memcmp(output, payload, 257),
168 "TCP retain partial receive packet");
169 struct iovec vectors[] = {
170 {NULL, 0}, {output + 257, 3}, {NULL, 0}, {output + 260,
sizeof(output) - 260 - 31}};
171 struct msghdr
message = {.msg_iov = vectors, .msg_iovlen = 4};
172 require(recvmsg(alias, &
message, MSG_DONTWAIT) == (ssize_t)(
sizeof(output) - 257 - 31) &&
173 !memcmp(output, payload,
sizeof(output) - 31),
174 "TCP scatter receive drains queued packets through duplicate");
176 require(recv(receiver, tail,
sizeof(tail), 0) == 31 &&
177 !memcmp(tail, payload +
sizeof(payload) - 31, 31),
178 "TCP partial progress is returned before EOF");
179 require(recv(alias, output,
sizeof(output), 0) == 0 &&
180 recv(receiver, output,
sizeof(output), MSG_DONTWAIT) == 0,
181 "TCP EOF follows queued payload");
182 require(close(alias) == 0 && close(receiver) == 0 && close(sender) == 0 && close(listener) == 0 &&
184 "TCP receive fixture close");
186 puts(
"USERCOPY-CONTRACT: PASS tcp-receive-buffers");
189static void socket_addresses(
void* inaccessible) {
190 struct sockaddr_un address = {.sun_family = AF_UNIX};
191 snprintf(address.sun_path,
sizeof(address.sun_path),
"/tmp/usercopy-%ld.sock", (
long)getpid());
192 unlink(address.sun_path);
193 const socklen_t length = offsetof(
struct sockaddr_un, sun_path) + strlen(address.sun_path) + 1;
194 int listener = socket(AF_UNIX, SOCK_STREAM, 0);
195 require(listener >= 0,
"pathname socket");
197 require(bind(listener, inaccessible, length) == -1 && errno == EFAULT,
"bind address protection");
198 require(bind(listener, (
struct sockaddr*)&address, length) == 0 && listen(listener, 1) == 0,
199 "bind and listen from address snapshot");
200 int regular = open(
"/dev/null", O_RDWR);
201 require(regular >= 0,
"non-socket descriptor");
203 require(connect(regular, (
struct sockaddr*)&address, length) == -1 && errno == ENOTSOCK,
204 "non-socket connect rejected");
207 pid_t child = fork();
208 require(child >= 0,
"accept peer fork");
211 int peer = socket(AF_UNIX, SOCK_STREAM, 0);
212 if (peer < 0 || connect(peer, (
struct sockaddr*)&address, length) != 0 ||
213 send(peer,
"k", 1, 0) != 1)
218 unsigned char peer_address[3] = {0xA5, 0xA5, 0xA5};
219 socklen_t peer_length = 1;
220 int accepted = accept(listener, (
struct sockaddr*)(peer_address + 1), &peer_length);
221 require(accepted >= 0 && peer_length >=
sizeof(sa_family_t) && peer_address[0] == 0xA5 &&
222 peer_address[2] == 0xA5,
223 "bounded accepted address output");
225 require(recv(accepted, &
byte, 1, 0) == 1 &&
byte ==
'k',
"accepted stream usable");
227 require(unlink(address.sun_path) == 0,
"remove socket pathname");
230 require(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0,
231 "pathname peer completed");
232 puts(
"USERCOPY-CONTRACT: PASS socket-addresses");
235static void console_buffers(
void* inaccessible) {
236 int master = posix_openpt(O_RDWR | O_NOCTTY);
237 require(master >= 0,
"open console pair");
238 struct winsize requested = {.ws_row = 31, .ws_col = 93}, actual = {0};
239 require(ioctl(master, TIOCSWINSZ, &requested) == 0 && ioctl(master, TIOCGWINSZ, &actual) == 0 &&
240 actual.ws_row == 31 && actual.ws_col == 93,
241 "console window snapshot roundtrip");
243 require(ioctl(master, TIOCSWINSZ, inaccessible) == -1 && errno == EFAULT,
244 "console input protection");
246 require(ioctl(master, TIOCGWINSZ, inaccessible) == -1 && errno == EFAULT,
247 "console output protection");
249 require(ioctl(master, FIONBIO, &enabled) == 0 && (fcntl(master, F_GETFL) & O_NONBLOCK),
250 "nonblocking input snapshot");
252 require(ioctl(master, FIONBIO, NULL) == -1 && errno == EFAULT &&
253 (fcntl(master, F_GETFL) & O_NONBLOCK),
254 "invalid nonblocking input preserves flags");
256 puts(
"USERCOPY-CONTRACT: PASS console-buffers");
262 unsigned short length;
267static void directory_buffers(
void* inaccessible) {
268 int directory = open(
"/tmp", O_RDONLY | O_DIRECTORY);
269 require(directory >= 0,
"open directory");
271 require(syscall(SYS_getdents64, directory, inaccessible, 256) == -1 && errno == EFAULT,
272 "directory output protection");
273 unsigned char records[512];
274 ssize_t count = syscall(SYS_getdents64, directory, records,
sizeof(records));
275 require(count > 0,
"directory output snapshot");
276 for (
size_t offset = 0; offset < (size_t)count;) {
279 record->length % 8 == 0 && record->length <= count - offset &&
280 memchr(record->name, 0, record->length - offsetof(
struct directory_record, name)),
281 "aligned bounded directory record");
282 offset += record->length;
285 puts(
"USERCOPY-CONTRACT: PASS directory-buffers");
288static void mapped_write_buffers(
size_t page) {
290 snprintf(path,
sizeof(path),
"/tmp/usercopy-mapped-%ld", (
long)getpid());
291 int file = open(path, O_CREAT | O_TRUNC | O_RDWR, 0600);
292 require(file >= 0 && ftruncate(file, page) == 0 && pwrite(file,
"mapped", 6, 0) == 6,
293 "prepare file-backed write source");
294 for (
int operation = 0; operation < 4; ++operation) {
295 char* source = mmap(NULL, page, PROT_READ, MAP_PRIVATE, file, 0);
296 require(source != MAP_FAILED && lseek(file, 0, SEEK_SET) == 0,
297 "cold mapping for same-file write");
298 struct iovec vectors[] = {{source, 2}, {source + 2, 4}};
302 written = write(file, source, 6);
305 written = pwrite(file, source, 6, 0);
308 written = writev(file, vectors, 2);
311 written = pwritev(file, vectors, 2, 0);
314 require(written == 6,
"write materializes source outside backing mutation lock");
315 require(munmap(source, page) == 0,
"release cold write source");
318 require(pread(file, actual,
sizeof(actual), 0) ==
sizeof(actual) &&
319 !memcmp(actual,
"mapped",
sizeof(actual)),
320 "mapped source contents preserved");
322 require(unlink(path) == 0,
"remove mapped write fixture");
323 puts(
"USERCOPY-CONTRACT: PASS mapped-write-buffers");
326static void metadata_buffers(
void* inaccessible) {
327 char path[80], link[88];
329 snprintf(path,
sizeof(path),
"/usercopy-metadata-%ld", (
long)getpid());
330 snprintf(link,
sizeof(link),
"%s.link", path);
331 int file = open(path, O_CREAT | O_EXCL | O_RDWR, 0600);
332 require(file >= 0 && write(file,
"metadata", 8) == 8,
"prepare metadata file");
333 struct stat by_path, by_descriptor;
334 require(stat(path, &by_path) == 0 && fstat(file, &by_descriptor) == 0 &&
335 S_ISREG(by_path.st_mode) && by_path.st_size == 8 &&
336 by_path.st_ino == by_descriptor.st_ino && by_descriptor.st_size == 8,
337 "public stat snapshots agree");
338 struct statfs filesystem;
339 struct statvfs capacity;
340 require(statfs(path, &filesystem) == 0 && filesystem.f_bsize > 0 &&
341 fstatfs(file, &filesystem) == 0 && filesystem.f_bsize > 0 &&
342 statvfs(path, &capacity) == 0 && capacity.f_bsize > 0 &&
343 fstatvfs(file, &capacity) == 0 && capacity.f_bsize > 0,
344 "public filesystem metadata snapshots");
349 require(syscall(SYS_stat, path, inaccessible) == -1 && errno == EFAULT,
"stat output protection");
351 require(syscall(SYS_fstat, file, inaccessible) == -1 && errno == EFAULT,
352 "fstat output protection");
354 require(syscall(SYS_newfstatat, AT_FDCWD, path, inaccessible, 0) == -1 && errno == EFAULT,
355 "fstatat output protection");
357 require(syscall(SYS_statfs, path, inaccessible) == -1 && errno == EFAULT,
358 "statfs output protection");
360 require(syscall(SYS_fstatfs, file, inaccessible) == -1 && errno == EFAULT,
361 "fstatfs output protection");
363 require(symlink(path, link) == 0,
"prepare readlink target");
364 unsigned char target[96];
365 memset(target, 0xA7,
sizeof(target));
366 require(readlink(link, (
char*)target + 1, 3) == 3 && !memcmp(target + 1, path, 3) &&
367 target[0] == 0xA7 && target[4] == 0xA7,
368 "readlink truncates without a terminator");
369 memset(target, 0xA7,
sizeof(target));
370 size_t target_length = strlen(path);
371 require(readlinkat(AT_FDCWD, link, (
char*)target + 1, target_length) == (ssize_t)target_length &&
372 !memcmp(target + 1, path, target_length) && target[0] == 0xA7 &&
373 target[target_length + 1] == 0xA7,
374 "readlinkat exact target extent");
376 require(readlink(link, inaccessible, 3) == -1 && errno == EFAULT,
"readlink output protection");
380 struct utimbuf seconds = {.actime = 11, .modtime = 22};
381 struct timeval fractions[2] = {{33, 100}, {44, 200}};
382 require(syscall(SYS_utime, path, &seconds) == 0 && syscall(SYS_utimes, path, fractions) == 0 &&
383 syscall(SYS_futimesat, AT_FDCWD, path, fractions) == 0 &&
384 fstat(file, &by_descriptor) == 0,
385 "legacy timestamp inputs snapshotted");
387 require(syscall(SYS_utime, path, inaccessible) == -1 && errno == EFAULT,
388 "legacy utime input protection");
390 require(syscall(SYS_utimes, path, inaccessible) == -1 && errno == EFAULT,
391 "legacy utimes input protection");
393 require(syscall(SYS_futimesat, AT_FDCWD, path, inaccessible) == -1 && errno == EFAULT,
394 "legacy futimesat input protection");
395 require(fstat(file, &by_path) == 0 && by_path.st_atim.tv_sec == by_descriptor.st_atim.tv_sec &&
396 by_path.st_atim.tv_nsec == by_descriptor.st_atim.tv_nsec &&
397 by_path.st_mtim.tv_sec == by_descriptor.st_mtim.tv_sec &&
398 by_path.st_mtim.tv_nsec == by_descriptor.st_mtim.tv_nsec,
399 "rejected timestamp inputs preserve metadata");
401 require(unlink(link) == 0 && unlink(path) == 0,
"remove metadata fixtures");
402 puts(
"USERCOPY-CONTRACT: PASS metadata-buffers");
405static void working_directory_buffer(
void* inaccessible) {
407 snprintf(path,
sizeof(path),
"/tmp/usercopy-cwd-%ld", (
long)getpid());
408 int original = open(
".", O_RDONLY | O_DIRECTORY);
409 require(original >= 0 && mkdir(path, 0700) == 0 && chdir(path) == 0,
"prepare working directory");
411 require(getcwd(expected,
sizeof(expected)) == expected,
"working directory baseline");
412 const size_t length = strlen(expected);
413 unsigned char output[
sizeof(expected) + 2];
414 memset(output, 0xA7,
sizeof(output));
415 require(getcwd((
char*)output + 1, length + 1) == (
char*)output + 1 &&
416 !memcmp(output + 1, expected, length + 1) && output[0] == 0xA7 &&
417 output[length + 2] == 0xA7,
418 "getcwd exact capacity includes terminator");
419 require(syscall(SYS_getcwd, output + 1, length + 1) == (
long)length + 1 &&
420 output[length + 1] == 0 && output[length + 2] == 0xA7,
421 "getcwd syscall reports terminated extent");
422 memset(output, 0xA7,
sizeof(output));
424 require(getcwd((
char*)output + 1, length) == NULL && errno == ERANGE && output[1] == 0xA7,
425 "getcwd insufficient capacity preserves output");
427 require(getcwd(inaccessible, length + 1) == NULL && errno == EFAULT,
"getcwd output protection");
428 require(fchdir(original) == 0,
"restore working directory");
430 require(rmdir(path) == 0,
"remove working directory fixture");
431 puts(
"USERCOPY-CONTRACT: PASS working-directory-buffer");
434void test_regular_read_contracts(
const char* base) {
435 const size_t page = (size_t)sysconf(_SC_PAGESIZE);
436 const size_t capacity = 64 * 1024;
437 const size_t legacy_capacity = 4097;
438 const size_t file_size = 2 * capacity + 257;
439 const size_t read_lengths[] = {16384, capacity + 37};
440 unsigned char* source = malloc(file_size);
441 unsigned char* output = malloc(capacity + 39);
442 require(page && source && output,
"allocate regular read buffers");
443 for (
size_t i = 0; i < file_size; ++i)
444 source[i] = (
unsigned char)(i * 37 + i / 251);
447 const int path_length = snprintf(path,
sizeof(path),
"%s/regular-read-%ld", base, (
long)getpid());
448 require(path_length > 0 && (
size_t)path_length <
sizeof(path),
"regular read fixture path");
449 int file = open(path, O_CREAT | O_EXCL | O_RDWR, 0600);
450 require(file >= 0 && write(file, source, file_size) == (ssize_t)file_size,
451 "prepare regular read fixture");
452 int alias = dup(file);
453 require(alias >= 0,
"duplicate regular read descriptor");
455 for (
size_t i = 0; i <
sizeof(read_lengths) /
sizeof(read_lengths[0]); ++i) {
456 const size_t length = read_lengths[i];
457 memset(output, 0xA7, capacity + 39);
458 require(lseek(file, 13, SEEK_SET) == 13 && read(alias, output + 1, length) == (ssize_t)length &&
459 !memcmp(output + 1, source + 13, length) && output[0] == 0xA7 &&
460 output[length + 1] == 0xA7 && lseek(file, 0, SEEK_CUR) == (off_t)(13 + length),
461 "unaligned chunked read advances shared offset");
462 memset(output, 0xA7, capacity + 39);
463 require(pread(file, output + 1, length, 29) == (ssize_t)length &&
464 !memcmp(output + 1, source + 29, length) && output[0] == 0xA7 &&
465 output[length + 1] == 0xA7 && lseek(alias, 0, SEEK_CUR) == (off_t)(13 + length),
466 "unaligned chunked pread preserves shared offset");
469 memset(output, 0xA7, capacity + 39);
470 require(lseek(file, (off_t)file_size - 31, SEEK_SET) == (off_t)file_size - 31 &&
471 read(alias, output + 1, capacity + 37) == 31 &&
472 !memcmp(output + 1, source + file_size - 31, 31) && output[0] == 0xA7 &&
473 output[32] == 0xA7 && lseek(file, 0, SEEK_CUR) == (off_t)file_size,
474 "short read stops at EOF without overwriting suffix");
475 memset(output, 0xA7, capacity + 39);
476 require(pread(file, output + 1, capacity + 37, (off_t)file_size - 31) == 31 &&
477 !memcmp(output + 1, source + file_size - 31, 31) && output[0] == 0xA7 &&
478 output[32] == 0xA7 && pread(file, output + 1, capacity, file_size) == 0 &&
479 lseek(alias, 0, SEEK_CUR) == (off_t)file_size,
480 "short pread preserves suffix and shared offset");
482 const size_t valid_prefix = 2 * (legacy_capacity - 1);
483 const size_t writable_length = (valid_prefix + page - 1) / page * page;
484 unsigned char* guarded =
485 mmap(NULL, writable_length + page, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
486 require(guarded != MAP_FAILED && lseek(file, 23, SEEK_SET) == 23,
487 "prepare protected read destination");
489 require(read(alias, guarded, 1) == -1 && errno == EFAULT && lseek(file, 0, SEEK_CUR) == 23,
490 "first read fault preserves shared offset");
492 require(pread(file, guarded, 1, 47) == -1 && errno == EFAULT && lseek(alias, 0, SEEK_CUR) == 23,
493 "first pread fault preserves shared offset");
494 require(mprotect(guarded, writable_length, PROT_READ | PROT_WRITE) == 0,
495 "allow prefix of read destination");
496 unsigned char* destination = guarded + writable_length - valid_prefix;
497 memset(destination, 0xA7, valid_prefix);
498 require(read(alias, destination, valid_prefix + 1) == (ssize_t)legacy_capacity &&
499 !memcmp(destination, source + 23, legacy_capacity) &&
500 lseek(file, 0, SEEK_CUR) == (off_t)(23 + legacy_capacity),
501 "later read fault returns valid prefix and advances only copied bytes");
502 for (
size_t i = legacy_capacity; i < valid_prefix; ++i)
503 require(destination[i] == 0xA7,
"read fault preserves uncopied suffix");
504 memset(destination, 0xA7, valid_prefix);
505 require(pread(file, destination, valid_prefix + 1, 47) == (ssize_t)legacy_capacity &&
506 !memcmp(destination, source + 47, legacy_capacity) &&
507 lseek(alias, 0, SEEK_CUR) == (off_t)(23 + legacy_capacity),
508 "later pread fault returns valid prefix without changing shared offset");
509 for (
size_t i = legacy_capacity; i < valid_prefix; ++i)
510 require(destination[i] == 0xA7,
"pread fault preserves uncopied suffix");
511 require(munmap(guarded, writable_length + page) == 0,
"release protected read destination");
513 const size_t mapped_length = (file_size + page - 1) / page * page;
514 for (
int positional = 0; positional < 2; ++positional) {
515 unsigned char* mapped = mmap(NULL, mapped_length, PROT_READ | PROT_WRITE, MAP_PRIVATE, file, 0);
516 require(mapped != MAP_FAILED && lseek(file, 13, SEEK_SET) == 13,
517 "map cold file-backed read destination");
518 const ssize_t received = positional ? pread(file, mapped + 1, capacity + 37, 29)
519 : read(alias, mapped + 1, capacity + 37);
520 const size_t source_offset = positional ? 29 : 13;
521 const off_t expected_offset = positional ? 13 : (off_t)(13 + capacity + 37);
522 require(received == (ssize_t)(capacity + 37) &&
523 !memcmp(mapped + 1, source + source_offset, capacity + 37) &&
524 mapped[0] == source[0] && lseek(alias, 0, SEEK_CUR) == expected_offset,
525 "read materializes same-file destination after releasing the backing lock");
526 require(munmap(mapped, mapped_length) == 0,
"release file-backed read destination");
529 pread(file, output, capacity, 0) == (ssize_t)capacity && !memcmp(output, source, capacity),
530 "private destination leaves source file unchanged");
532 require(close(alias) == 0 && close(file) == 0 && unlink(path) == 0,
533 "remove regular read fixture");
536 puts(
"USERCOPY-CONTRACT: PASS regular-read-buffers");
539void test_usercopy_contracts(
void) {
540 const size_t page = (size_t)sysconf(_SC_PAGESIZE);
541 void* inaccessible = mmap(NULL, page, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
542 require(inaccessible != MAP_FAILED,
"protected user range");
543 socket_buffers(inaccessible, page);
544 test_tcp_receive_buffers();
545 socket_addresses(inaccessible);
546 test_socket_path_contracts();
547 console_buffers(inaccessible);
548 directory_buffers(inaccessible);
549 mapped_write_buffers(page);
550 metadata_buffers(inaccessible);
551 working_directory_buffer(inaccessible);
552 require(munmap(inaccessible, page) == 0,
"release protected user range");
553 puts(
"USERCOPY-CONTRACT: PASS all");