20#include <sys/resource.h>
23#include <sys/syscall.h>
29static void require(
int condition,
const char* operation) {
31 printf(
"SYSCALL-CONTRACT: FAIL %s errno=%d\n", operation, errno);
36static void file_contracts(
void) {
38 snprintf(path,
sizeof(path),
"/tmp/syscall-contract-%d", getpid());
39 int file = open(path, O_CREAT | O_TRUNC | O_RDWR, 0600);
40 require(file >= 0,
"create");
41 require(write(file,
"data", 4) == 4,
"write");
43 require(fstat(file, &status) == 0 && status.st_size == 4 && S_ISREG(status.st_mode) &&
44 status.st_blocks == sysconf(_SC_PAGESIZE) / 512,
49 struct iovec vector = {.iov_base = data, .iov_len =
sizeof(data)};
50 file = open(path, O_RDONLY | O_CLOEXEC);
51 require(file >= 0,
"read-only open");
52 require(read(file, data,
sizeof(data)) == 4 && !memcmp(data,
"data", 4),
"read");
54 require(write(file, data, 1) == -1 && errno == EBADF,
"read-only write");
56 require(writev(file, &vector, 1) == -1 && errno == EBADF,
"read-only writev");
58 require(write(file, data, 0) == -1 && errno == EBADF,
"read-only empty write");
59 require(lseek(file, 0, SEEK_SET) == 0 && readv(file, &vector, 1) == 4,
"readv");
62 file = open(path, O_WRONLY | O_CLOEXEC);
63 require(file >= 0,
"write-only open");
65 require(read(file, data, 1) == -1 && errno == EBADF,
"write-only read");
67 require(readv(file, &vector, 1) == -1 && errno == EBADF,
"write-only readv");
69 require(read(file, data, 0) == -1 && errno == EBADF,
"write-only empty read");
70 require(writev(file, &vector, 1) == 4,
"writev");
72 require(unlink(path) == 0,
"unlink");
75 require(pipe(descriptors) == 0,
"pipe");
76 require(fstat(descriptors[0], &status) == 0 && S_ISFIFO(status.st_mode),
"pipe metadata");
77 close(descriptors[0]);
78 close(descriptors[1]);
79 puts(
"SYSCALL-CONTRACT: PASS file-modes-and-stat");
82static void pipe_ioctl_contracts(
void) {
85 require(pipe(descriptors) == 0,
"FIONREAD pipe");
86 require(ioctl(descriptors[0], FIONREAD, &available) == 0 && available == 0,
87 "empty pipe FIONREAD");
88 require(write(descriptors[1],
"abcde", 5) == 5,
"FIONREAD pipe write");
89 for (
int endpoint = 0; endpoint < 2; ++endpoint) {
90 require(ioctl(descriptors[endpoint], FIONREAD, &available) == 0 && available == 5,
91 "pipe endpoints FIONREAD");
93 require(ioctl(descriptors[endpoint], FIONREAD, (
void*)1) == -1 && errno == EFAULT,
94 "pipe FIONREAD bad address");
97 require(read(descriptors[0], data, 2) == 2 && !memcmp(data,
"ab", 2),
"FIONREAD partial read");
98 require(ioctl(descriptors[0], FIONREAD, &available) == 0 && available == 3,
99 "FIONREAD remaining bytes");
100 close(descriptors[1]);
101 require(ioctl(descriptors[0], FIONREAD, &available) == 0 && available == 3,
102 "FIONREAD bytes after writer close");
103 require(read(descriptors[0], data,
sizeof(data)) == 3 && !memcmp(data,
"cde", 3),
104 "FIONREAD does not consume bytes");
105 require(ioctl(descriptors[0], FIONREAD, &available) == 0 && available == 0 &&
106 read(descriptors[0], data, 1) == 0,
107 "drained pipe FIONREAD and EOF");
108 close(descriptors[0]);
111 snprintf(path,
sizeof(path),
"/tmp/fionread-contract-%d", getpid());
112 require(mkfifo(path, 0600) == 0,
"FIONREAD FIFO create");
113 int fifo = open(path, O_RDWR | O_NONBLOCK);
114 require(fifo >= 0,
"FIONREAD FIFO open");
115 require(write(fifo,
"fifo", 4) == 4 && ioctl(fifo, FIONREAD, &available) == 0 && available == 4,
117 require(read(fifo, data,
sizeof(data)) == 4 && !memcmp(data,
"fifo", 4),
118 "FIFO FIONREAD preserves bytes");
120 require(unlink(path) == 0,
"FIONREAD FIFO unlink");
121 puts(
"SYSCALL-CONTRACT: PASS pipe-and-fifo-FIONREAD");
124static void socket_name_contracts(
void) {
126 require(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0,
"socketpair");
127 for (
int peer = 0; peer < 2; ++peer) {
128 struct sockaddr_storage complete;
129 memset(&complete, 0,
sizeof(complete));
130 socklen_t full_length =
sizeof(complete);
131 int result = peer ? getpeername(sockets[0], (
struct sockaddr*)&complete, &full_length)
132 : getsockname(sockets[0], (struct sockaddr*)&complete, &full_length);
133 require(result == 0 && full_length ==
sizeof(sa_family_t) && complete.ss_family == AF_UNIX,
134 "complete socket name");
136 struct sockaddr_storage short_name;
137 memset(&short_name, 0xA5,
sizeof(short_name));
138 socklen_t capacity = 1;
139 result = peer ? getpeername(sockets[0], (
struct sockaddr*)&short_name, &capacity)
140 : getsockname(sockets[0], (struct sockaddr*)&short_name, &capacity);
141 require(result == 0 && capacity == full_length &&
142 ((
unsigned char*)&short_name)[0] == ((
unsigned char*)&complete)[0],
143 "short socket name");
144 for (
size_t i = 1; i <
sizeof(short_name); ++i)
145 require(((
unsigned char*)&short_name)[i] == 0xA5,
"socket name capacity");
149 peer ? getpeername(sockets[0], NULL, &capacity) : getsockname(sockets[0], NULL, &capacity);
150 require(result == 0 && capacity == full_length,
"zero-capacity socket name");
154 puts(
"SYSCALL-CONTRACT: PASS socket-name-capacity");
157static void termios_contracts(
void) {
158 int master = posix_openpt(O_RDWR | O_NOCTTY | O_NONBLOCK);
159 require(master >= 0,
"open PTY");
160 unsigned int terminal = 0;
161 unsigned int raw_terminal = 0;
162 require(ioctl(master, TIOCGPTN, &terminal) == 0,
"ioctl signed request encoding");
163 require(syscall(SYS_ioctl, master, (
unsigned long)TIOCGPTN, &raw_terminal) == 0 &&
164 terminal == raw_terminal,
165 "ioctl raw request encoding");
167 require(ptsname_r(master, path,
sizeof(path)) == 0,
"PTY name");
168 int slave = open(path, O_RDWR | O_NOCTTY | O_NONBLOCK);
169 require(slave >= 0,
"open PTY slave");
171 struct termios attributes;
172 memset(&attributes, 0xA5,
sizeof(attributes));
173 require(tcgetattr(slave, &attributes) == 0,
"tcgetattr");
174 require(attributes.c_cc[VINTR] == 3 && attributes.c_cc[VERASE] == 8 &&
175 attributes.c_cc[VEOF] == 4 && attributes.c_cc[VSUSP] == 26,
176 "termios default character indices");
177 attributes.c_lflag |= ICANON;
178 attributes.c_lflag &= ~(ECHO | ISIG);
179 attributes.c_cc[VEOF] =
'~';
180 require(tcsetattr(slave, TCSANOW, &attributes) == 0,
"tcsetattr");
181 require(write(master,
"~", 1) == 1,
"PTY EOF input");
183 require(read(slave, &character, 1) == 0,
"termios EOF character behavior");
186 puts(
"SYSCALL-CONTRACT: PASS termios-musl-layout");
189static volatile sig_atomic_t caught_signal;
191static void caught(
int signal) {
196static void process_contracts(
void) {
198 require(getpriority(PRIO_PROCESS, 0) == 0 && errno == 0,
"getpriority libc encoding");
199 require(getpriority(PRIO_PROCESS, getpid()) == 0,
"getpriority process target");
200 require(syscall(SYS_getpriority, PRIO_PROCESS, 0) == 20,
"getpriority raw encoding");
202 require(getpriority(-1, 0) == -1 && errno == EINVAL,
"getpriority selector");
204 struct sigaction action = {0};
205 struct sigaction previous;
206 action.sa_handler = caught;
207 sigemptyset(&action.sa_mask);
208 require(sigaction(SIGUSR1, &action, &previous) == 0,
"wait signal disposition");
210 pid_t child = fork();
211 require(child >= 0,
"wait child fork");
214 kill(getppid(), SIGUSR1);
221 pid_t result = waitpid(child, &status, 0);
222 int interrupted = result == -1 && errno == EINTR && caught_signal;
223 if (result != child) {
225 result = waitpid(child, &status, 0);
226 }
while (result == -1 && errno == EINTR);
228 require(result == child && WIFEXITED(status) && WEXITSTATUS(status) == 0,
"reap wait child");
229 require(sigaction(SIGUSR1, &previous, NULL) == 0,
"restore signal disposition");
230 require(interrupted,
"caught signal interrupts waitpid");
231 puts(
"SYSCALL-CONTRACT: PASS priority-and-wait-interruption");
234void test_syscall_contracts(
void) {
236 pipe_ioctl_contracts();
237 socket_name_contracts();
240 puts(
"SYSCALL-CONTRACT: PASS all");