The Pedigree Project 0.1
descriptor-cloexec-contract-test/main.c
1#define _GNU_SOURCE
2#include <errno.h>
3#include <fcntl.h>
4#include <signal.h>
5#include <stdint.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include <time.h>
10#include <unistd.h>
11
12#include <sys/ioctl.h>
13#include <sys/socket.h>
14#include <sys/wait.h>
15
16#define CHECK(condition) \
17 do { \
18 if (!(condition)) { \
19 fprintf(stderr, "%s:%d: %s (errno=%d)\n", __FILE__, __LINE__, #condition, errno); \
20 failed = 1; \
21 goto out; \
22 } \
23 } while (0)
24
25static int toggle(int fd) {
26 int failed = 0, alias = -1, status = fcntl(fd, F_GETFL);
27 CHECK(status >= 0 && !fcntl(fd, F_SETFD, 0));
28 CHECK((alias = dup(fd)) >= 0 && fcntl(alias, F_GETFD) == 0);
29 // These descriptor commands must ignore the third argument entirely.
30 CHECK(!ioctl(fd, FIOCLEX, (void*)(uintptr_t)1));
31 CHECK(fcntl(fd, F_GETFD) == FD_CLOEXEC && fcntl(alias, F_GETFD) == 0);
32 CHECK(!ioctl(fd, FIOCLEX, NULL) && fcntl(fd, F_GETFD) == FD_CLOEXEC);
33 CHECK(!ioctl(alias, FIOCLEX, NULL));
34 CHECK(!ioctl(fd, FIONCLEX, (void*)(uintptr_t)1));
35 CHECK(fcntl(fd, F_GETFD) == 0 && fcntl(alias, F_GETFD) == FD_CLOEXEC);
36 CHECK(!ioctl(fd, FIONCLEX, NULL) && fcntl(fd, F_GETFD) == 0);
37 CHECK(!ioctl(alias, FIONCLEX, NULL) && fcntl(alias, F_GETFD) == 0);
38 CHECK(fcntl(fd, F_GETFL) == status && fcntl(alias, F_GETFL) == status);
39out:
40 if (alias >= 0)
41 close(alias);
42 return failed;
43}
44
45static int regular(void) {
46 int failed = 0, fd = -1;
47 char path[] = "/tmp/descriptor-cloexec-XXXXXX";
48 CHECK((fd = mkstemp(path)) >= 0);
49 CHECK(!toggle(fd));
50out:
51 if (fd >= 0) {
52 close(fd);
53 unlink(path);
54 }
55 return failed;
56}
57
58static int pipes(void) {
59 int failed = 0, pair[2] = {-1, -1};
60 CHECK(!pipe(pair));
61 CHECK(!toggle(pair[0]) && !toggle(pair[1]));
62out:
63 for (int n = 0; n < 2; ++n)
64 if (pair[n] >= 0)
65 close(pair[n]);
66 return failed;
67}
68
69static int sockets(void) {
70 int failed = 0, pair[2] = {-1, -1};
71 CHECK(!socketpair(AF_UNIX, SOCK_STREAM, 0, pair));
72 CHECK(!toggle(pair[0]) && !toggle(pair[1]));
73out:
74 for (int n = 0; n < 2; ++n)
75 if (pair[n] >= 0)
76 close(pair[n]);
77 return failed;
78}
79
80static int validation(void) {
81 int failed = 0, path = -1;
82 CHECK(ioctl(-1, FIOCLEX, NULL) == -1 && errno == EBADF);
83 CHECK(ioctl(-1, FIONCLEX, NULL) == -1 && errno == EBADF);
84 CHECK((path = open("/", O_PATH | O_DIRECTORY)) >= 0);
85 CHECK(ioctl(path, FIOCLEX, NULL) == -1 && errno == EBADF);
86 CHECK(ioctl(path, FIONCLEX, NULL) == -1 && errno == EBADF);
87 // O_PATH remains usable through the existing fcntl fallback.
88 CHECK(!fcntl(path, F_SETFD, FD_CLOEXEC) && fcntl(path, F_GETFD) == FD_CLOEXEC);
89 CHECK(!fcntl(path, F_SETFD, 0) && fcntl(path, F_GETFD) == 0);
90out:
91 if (path >= 0)
92 close(path);
93 return failed;
94}
95
96static int exec_child(const char* closed_arg, const char* kept_arg) {
97 int failed = 0, closed = atoi(closed_arg), kept = atoi(kept_arg);
98 char content[7];
99 alarm(5);
100 CHECK(closed >= 64 && kept > closed);
101 CHECK(fcntl(closed, F_GETFD) == -1 && errno == EBADF);
102 CHECK(fcntl(kept, F_GETFD) == 0);
103 CHECK(read(kept, content, sizeof(content)) == (ssize_t)sizeof(content));
104 CHECK(!memcmp(content, "closure", sizeof(content)));
105out:
106 close(kept);
107 return failed;
108}
109
110static int exec_closure(const char* executable) {
111 int failed = 0, fd = -1, closed = -1, kept = -1, status = -1;
112 pid_t child = -1;
113 char path[] = "/tmp/descriptor-cloexec-exec-XXXXXX";
114 CHECK((fd = mkstemp(path)) >= 0);
115 CHECK(write(fd, "closure", 7) == 7 && lseek(fd, 0, SEEK_SET) == 0);
116 // Avoid confusing a loader's reused low descriptor with one that survived exec.
117 CHECK((closed = fcntl(fd, F_DUPFD, 64)) >= 64);
118 CHECK((kept = fcntl(fd, F_DUPFD, closed + 1)) > closed);
119 CHECK(!ioctl(closed, FIOCLEX, NULL));
120 CHECK(!ioctl(kept, FIOCLEX, NULL) && !ioctl(kept, FIONCLEX, NULL));
121 CHECK((child = fork()) >= 0);
122 if (!child) {
123 char closed_arg[24], kept_arg[24];
124 snprintf(closed_arg, sizeof(closed_arg), "%d", closed);
125 snprintf(kept_arg, sizeof(kept_arg), "%d", kept);
126 char* arguments[] = {(char*)executable, "--exec", closed_arg, kept_arg, NULL};
127 close(fd);
128 alarm(5);
129 // Procfs link following is a separate contract from descriptor inheritance.
130 execv(executable, arguments);
131 _exit(127);
132 }
133 for (int n = 0; n < 1000; ++n) {
134 pid_t result = waitpid(child, &status, WNOHANG);
135 if (result == child) {
136 child = -1;
137 break;
138 }
139 CHECK(result == 0 || (result < 0 && errno == EINTR));
140 struct timespec pause = {0, 10000000};
141 nanosleep(&pause, NULL);
142 }
143 CHECK(child == -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0);
144 CHECK(fcntl(closed, F_GETFD) == FD_CLOEXEC && fcntl(kept, F_GETFD) == 0);
145out:
146 if (child > 0) {
147 kill(child, SIGKILL);
148 while (waitpid(child, NULL, 0) < 0 && errno == EINTR) {
149 }
150 }
151 if (kept >= 0)
152 close(kept);
153 if (closed >= 0)
154 close(closed);
155 if (fd >= 0) {
156 close(fd);
157 unlink(path);
158 }
159 return failed;
160}
161
162int main(int argc, char** argv) {
163 if (argc == 4 && !strcmp(argv[1], "--exec"))
164 return exec_child(argv[2], argv[3]);
165 if (argc != 1 || !argv[0] || argv[0][0] != '/' || !argv[0][1]) {
166 fputs("Invoke this contract test using its absolute executable path.\n", stderr);
167 return 2;
168 }
169 static const struct {
170 const char* name;
171 int (*test)(void);
172 } cases[] = {
173 {"regular", regular}, {"pipe", pipes}, {"socket", sockets}, {"validation", validation}};
174 for (unsigned n = 0; n < sizeof(cases) / sizeof(cases[0]); ++n) {
175 printf("DESCRIPTOR-CLOEXEC: BEGIN %s\n", cases[n].name);
176 fflush(stdout);
177 int result = cases[n].test();
178 printf("DESCRIPTOR-CLOEXEC: %s %s status=%d\n", result ? "FAIL" : "PASS", cases[n].name,
179 result);
180 fflush(stdout);
181 if (result)
182 return 1;
183 }
184 puts("DESCRIPTOR-CLOEXEC: BEGIN exec-closure");
185 fflush(stdout);
186 int result = exec_closure(argv[0]);
187 printf("DESCRIPTOR-CLOEXEC: %s exec-closure status=%d\n", result ? "FAIL" : "PASS", result);
188 if (result)
189 return 1;
190 puts("DESCRIPTOR-CLOEXEC: END PASS");
191 return 0;
192}