16#include <sys/syscall.h>
19#define CHECK(expression) \
21 if (!(expression)) { \
22 fprintf(stderr, "VHANGUP-CONTRACT: FAIL line=%d errno=%d\n", __LINE__, errno); \
27static volatile sig_atomic_t hangups, continuations;
28static volatile sig_atomic_t control_interrupts, control_error;
29static int control_terminal = -1;
31static void control_handler(
int signal) {
33 int saved_errno = errno;
35 if (ioctl(control_terminal, TIOCGWINSZ, &size) || tcgetpgrp(control_terminal) != getpgrp() ||
36 tcsetpgrp(control_terminal, getpgrp()) || syscall(SYS_vhangup))
41static void signal_handler(
int signal) {
44 if (signal == SIGCONT)
57 char data[16] =
"blocked";
58 io->result = io->writing ? write(io->fd, data,
sizeof(data)) : read(io->fd, data, sizeof(data));
63static int transferred_fd(
int fd) {
65 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets))
68 struct cmsghdr alignment;
69 char data[CMSG_SPACE(
sizeof(
int))];
72 struct iovec vector = {&byte, 1};
73 struct msghdr
message = {.msg_iov = &vector,
75 .msg_control = control.data,
76 .msg_controllen =
sizeof(control)};
77 struct cmsghdr* header = CMSG_FIRSTHDR(&
message);
78 header->cmsg_level = SOL_SOCKET;
79 header->cmsg_type = SCM_RIGHTS;
80 header->cmsg_len = CMSG_LEN(
sizeof(
int));
81 memcpy(CMSG_DATA(header), &fd,
sizeof(fd));
83 if (sendmsg(sockets[0], &
message, 0) == 1 && recvmsg(sockets[1], &
message, 0) == 1) {
84 header = CMSG_FIRSTHDR(&
message);
85 if (header && header->cmsg_level == SOL_SOCKET && header->cmsg_type == SCM_RIGHTS)
86 memcpy(&received, CMSG_DATA(header),
sizeof(received));
93static int revoked_descriptor(
int fd) {
96 CHECK(read(fd, &
byte, 1) == 0);
97 CHECK(write(fd,
"x", 1) == -1 && errno == EIO);
98 CHECK(ioctl(fd, TIOCGWINSZ, &size) == -1 && errno == EIO);
99 CHECK(tcsetpgrp(fd, getpgrp()) == -1 && errno == ENOTTY);
100 struct pollfd pollfd = {.fd = fd, .events = POLLIN | POLLOUT};
101 CHECK(poll(&pollfd, 1, 0) == 1);
102 CHECK((pollfd.revents & (POLLIN | POLLOUT | POLLERR | POLLHUP)) ==
103 (POLLIN | POLLOUT | POLLERR | POLLHUP));
107static int control_character_test(
void) {
108 puts(
"VHANGUP-CONTRACT: control-character");
109 int master = open(
"/dev/ptmx", O_RDWR | O_NOCTTY);
112 CHECK(ioctl(master, TIOCGPTN, &number) == 0);
114 snprintf(path,
sizeof(path),
"/dev/pts/%u", number);
115 int slave = open(path, O_RDWR | O_NOCTTY);
116 CHECK(slave >= 0 && ioctl(slave, TIOCSCTTY, 0) == 0);
117 struct termios attributes;
118 CHECK(tcgetattr(slave, &attributes) == 0);
119 cfmakeraw(&attributes);
120 attributes.c_lflag |= ISIG;
121 CHECK(tcsetattr(slave, TCSANOW, &attributes) == 0);
122 struct sigaction action = {.sa_handler = control_handler};
123 sigemptyset(&action.sa_mask);
124 CHECK(sigaction(SIGINT, &action, NULL) == 0);
125 control_terminal = slave;
126 char interrupt = attributes.c_cc[VINTR];
127 CHECK(interrupt && write(master, &interrupt, 1) == 1);
128 for (
int i = 0; i < 100 && !control_interrupts; ++i)
130 CHECK(control_interrupts == 1 && !control_error);
131 CHECK(hangups == 1 && continuations == 1);
132 CHECK(revoked_descriptor(slave) == 0);
133 CHECK(open(
"/dev/tty", O_RDWR) == -1 && errno == ENXIO);
134 CHECK(close(slave) == 0 && close(master) == 0);
135 control_terminal = -1;
136 hangups = continuations = 0;
137 puts(
"VHANGUP-CONTRACT: control-character-reentry-pass");
141static int session_test(
void) {
143 CHECK(setsid() == getpid());
144 CHECK(syscall(SYS_vhangup) == 0);
145 pid_t unprivileged = fork();
146 CHECK(unprivileged >= 0);
150 _exit(syscall(SYS_vhangup) == -1 && errno == EPERM ? 0 : 3);
153 CHECK(waitpid(unprivileged, &status, 0) == unprivileged && WIFEXITED(status) &&
154 WEXITSTATUS(status) == 0);
155 struct sigaction action = {.sa_handler = signal_handler};
156 sigemptyset(&action.sa_mask);
157 CHECK(sigaction(SIGHUP, &action, NULL) == 0 && sigaction(SIGCONT, &action, NULL) == 0);
158 CHECK(control_character_test() == 0);
160 puts(
"VHANGUP-CONTRACT: private-pty");
161 int master = open(
"/dev/ptmx", O_RDWR | O_NOCTTY);
164 CHECK(ioctl(master, TIOCGPTN, &number) == 0);
166 snprintf(path,
sizeof(path),
"/dev/pts/%u", number);
167 int slave = open(path, O_RDWR | O_NOCTTY);
169 CHECK(open(
"/dev/tty", O_RDWR) == -1 && errno == ENXIO);
170 CHECK(ioctl(slave, TIOCSCTTY, 0) == 0);
171 CHECK(tcgetpgrp(slave) == getpgrp());
172 struct termios attributes;
173 CHECK(tcgetattr(slave, &attributes) == 0);
174 cfmakeraw(&attributes);
175 CHECK(tcsetattr(slave, TCSANOW, &attributes) == 0);
176 int duplicate = dup(slave), passed = transferred_fd(slave);
177 CHECK(duplicate >= 0 && passed >= 0);
179 pid_t foreign = fork();
182 if (setsid() != getpid())
184 int other = open(path, O_RDWR | O_NOCTTY);
187 _exit(ioctl(other, TIOCSCTTY, 0) == -1 && errno == EPERM ? 0 : 6);
189 CHECK(waitpid(foreign, &status, 0) == foreign && WIFEXITED(status) && WEXITSTATUS(status) == 0);
190 puts(
"VHANGUP-CONTRACT: foreign-session-refused");
191 CHECK(tcsetpgrp(slave, 0) == -1 && errno == EPERM);
193 int commands[2], ready[2];
194 CHECK(pipe(commands) == 0 && pipe(ready) == 0);
195 puts(
"VHANGUP-CONTRACT: foreground-fork");
196 pid_t child = fork();
201 if (setpgid(0, 0) || write(ready[1],
"r", 1) != 1)
204 if (read(commands[0], &
byte, 1) != 1)
206 if (hangups || continuations || open(
"/dev/tty", O_RDWR) != -1 || errno != ENXIO ||
207 revoked_descriptor(slave))
214 CHECK(read(ready[0], &
byte, 1) == 1);
215 puts(
"VHANGUP-CONTRACT: foreground-assign");
216 CHECK(tcsetpgrp(slave, child) == 0 && tcgetpgrp(slave) == child);
218 int epoll = epoll_create1(EPOLL_CLOEXEC);
220 struct epoll_event event = {.events = EPOLLIN | EPOLLOUT | EPOLLET, .data.fd = slave};
221 puts(
"VHANGUP-CONTRACT: epoll-add");
222 CHECK(epoll_ctl(epoll, EPOLL_CTL_ADD, slave, &event) == 0);
223 puts(
"VHANGUP-CONTRACT: epoll-initial");
224 CHECK(epoll_wait(epoll, &event, 1, 0) == 1 && (event.events & EPOLLOUT));
225 CHECK(fcntl(slave, F_SETFL, O_NONBLOCK) == 0);
227 memset(fill,
'q',
sizeof(fill));
228 puts(
"VHANGUP-CONTRACT: fill-output");
231 ssize_t amount = write(slave, fill,
sizeof(fill));
233 CHECK(errno == EAGAIN);
238 CHECK(total < 16 * 1024 * 1024);
240 puts(
"VHANGUP-CONTRACT: output-full");
241 CHECK(fcntl(slave, F_SETFL, 0) == 0);
243 struct blocked_io writer = {.fd = passed, .writing = 1, .result = -2};
244 pthread_t read_thread, write_thread;
246 CHECK(pthread_create(&write_thread, NULL,
blocked_io, &writer) == 0);
248 puts(
"VHANGUP-CONTRACT: revoke");
249 CHECK(syscall(SYS_vhangup) == 0);
250 CHECK(pthread_join(read_thread, NULL) == 0 && pthread_join(write_thread, NULL) == 0);
251 CHECK(
reader.result == 0 && writer.result == -1 && writer.error == EIO);
252 CHECK(hangups == 1 && continuations == 1);
253 CHECK(revoked_descriptor(slave) == 0 && revoked_descriptor(duplicate) == 0 &&
254 revoked_descriptor(passed) == 0);
255 CHECK(epoll_wait(epoll, &event, 1, 0) == 1 &&
256 (event.events & (EPOLLIN | EPOLLERR | EPOLLHUP)) == (EPOLLIN | EPOLLERR | EPOLLHUP));
257 CHECK(epoll_wait(epoll, &event, 1, 0) == 0);
258 CHECK(open(
"/dev/tty", O_RDWR) == -1 && errno == ENXIO);
259 CHECK(write(commands[1],
"g", 1) == 1);
260 CHECK(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0);
262 puts(
"VHANGUP-CONTRACT: reopen");
263 int fresh = open(path, O_RDWR | O_NOCTTY);
265 struct pollfd pollfd = {.fd = fresh, .events = POLLIN | POLLOUT};
266 CHECK(poll(&pollfd, 1, 0) == 1 && !(pollfd.revents & (POLLERR | POLLHUP)));
267 CHECK(tcgetattr(fresh, &attributes) == 0 && (attributes.c_lflag & ICANON));
268 cfmakeraw(&attributes);
269 CHECK(tcsetattr(fresh, TCSANOW, &attributes) == 0);
270 CHECK(write(master,
"new", 3) == 3);
272 CHECK(read(fresh, data, 3) == 3 && !memcmp(data,
"new", 3));
273 CHECK(write(fresh,
"out", 3) == 3);
274 CHECK(read(master, data, 3) == 3 && !memcmp(data,
"out", 3));
275 CHECK(ioctl(fresh, TIOCSCTTY, 0) == 0 && tcgetpgrp(fresh) == getpgrp());
276 CHECK(revoked_descriptor(slave) == 0);
277 CHECK(syscall(SYS_vhangup) == 0);
278 CHECK(close(fresh) == 0 && close(master) == 0 && close(slave) == 0 && close(duplicate) == 0 &&
279 close(passed) == 0 && close(epoll) == 0);
285static int session_exit_test(
void) {
286 puts(
"VHANGUP-CONTRACT: session-exit");
287 int master = open(
"/dev/ptmx", O_RDWR | O_NOCTTY);
290 CHECK(ioctl(master, TIOCGPTN, &number) == 0);
292 snprintf(path,
sizeof(path),
"/dev/pts/%u", number);
293 int slave = open(path, O_RDWR | O_NOCTTY);
295 pid_t leader = fork();
298 if (setsid() != getpid() || ioctl(slave, TIOCSCTTY, 0))
303 CHECK(waitpid(leader, &status, 0) == leader && WIFEXITED(status) && WEXITSTATUS(status) == 0);
304 CHECK(revoked_descriptor(slave) == 0);
305 int fresh = open(path, O_RDWR | O_NOCTTY);
307 struct termios attributes;
308 CHECK(tcgetattr(fresh, &attributes) == 0);
309 cfmakeraw(&attributes);
310 CHECK(tcsetattr(fresh, TCSANOW, &attributes) == 0);
311 CHECK(write(master,
"e", 1) == 1);
313 CHECK(read(fresh, &
byte, 1) == 1 &&
byte ==
'e');
314 CHECK(close(fresh) == 0 && close(slave) == 0 && close(master) == 0);
319 setvbuf(stdout, NULL, _IONBF, 0);
321 CHECK(geteuid() == 0);
322 pid_t child = fork();
325 _exit(session_test());
327 CHECK(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0);
328 CHECK(session_exit_test() == 0);
329 puts(
"VHANGUP-CONTRACT: PASS");