The Pedigree Project 0.1
signal-exec-test/exec.c
1#define _GNU_SOURCE
2
3#include <errno.h>
4#include <fcntl.h>
5#include <pthread.h>
6#include <signal.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <unistd.h>
11
12#include "contracts.h"
13#include <sys/stat.h>
14#include <sys/syscall.h>
15
16static volatile sig_atomic_t delivered_signals;
17static volatile sig_atomic_t delivered_process_signals;
18static volatile sig_atomic_t delivered_private_signals;
19static volatile sig_atomic_t delivered_sibling_signals;
20static volatile sig_atomic_t invalid_signal_info;
21
22struct exec_probe {
23 const char* program;
24 pthread_mutex_t mutex;
25 pthread_cond_t condition;
26 int gate[2];
27 int inherited;
28 int cloexec;
29 int stop;
30 int pending_signals;
31 volatile int read_ready;
32 volatile int condition_ready;
33 volatile int main_ready;
34 volatile int read_done;
35 volatile int condition_done;
36 volatile int race_start;
37 volatile int clone_ready;
38 volatile int signals_queued;
39 volatile int sibling_mask_checked;
40 long read_tid;
41 long condition_tid;
42};
43
44static void* fresh_thread(void* argument) {
45 return argument;
46}
47
48static void caught_signal(int number) {
49 (void)number;
50}
51
52static void pending_signal(int number, siginfo_t* info, void* context) {
53 const int saved_errno = errno;
54 (void)context;
55 if (number == SIGHUP) {
56 ++delivered_sibling_signals;
57 errno = saved_errno;
58 return;
59 }
60 if (number != SIGUSR1 || !info || info->si_signo != SIGUSR1 || info->si_errno ||
61 info->si_pid != getpid() || info->si_uid != getuid()) {
62 invalid_signal_info = 1;
63 } else if (info->si_code == SI_USER) {
64 ++delivered_process_signals;
65 } else if (info->si_code == SI_TKILL) {
66 ++delivered_private_signals;
67 } else {
68 invalid_signal_info = 1;
69 }
70 ++delivered_signals;
71 errno = saved_errno;
72}
73
74static int pending_delivery_contract(const sigset_t* original, int expect_private) {
75 struct sigaction action = {.sa_sigaction = pending_signal, .sa_flags = SA_SIGINFO};
76 sigset_t unblocked = *original;
77 const long long start = test_milliseconds();
78 if (start < 0 || sigemptyset(&action.sa_mask) || sigaction(SIGUSR1, &action, 0) ||
79 sigaction(SIGHUP, &action, 0) || sigdelset(&unblocked, SIGUSR1) ||
80 sigdelset(&unblocked, SIGHUP) || pthread_sigmask(SIG_SETMASK, &unblocked, 0)) {
81 return 107;
82 }
83 const int expected = 1 + expect_private;
84 while (delivered_signals < expected) {
85 const long long now = test_milliseconds();
86 if (now < 0 || now - start >= 3000) {
87 break;
88 }
89 test_pause();
90 }
91 for (int grace = 0; grace < 4; ++grace) {
92 test_pause();
93 }
94 if (pthread_sigmask(SIG_SETMASK, original, 0)) {
95 return 108;
96 }
97 if (delivered_signals != expected || delivered_process_signals != 1 ||
98 delivered_private_signals != expect_private || invalid_signal_info ||
99 delivered_sibling_signals) {
100 fprintf(stderr, "pending delivery: total=%d process=%d private=%d sibling=%d invalid=%d\n",
101 delivered_signals, delivered_process_signals, delivered_private_signals,
102 delivered_sibling_signals, invalid_signal_info);
103 return 109;
104 }
105 return 0;
106}
107
108static int set_exec_signals(int blocked_signal, int pending_signals) {
109 struct sigaction caught = {.sa_handler = caught_signal, .sa_flags = SA_RESTART};
110 struct sigaction ignored = {.sa_handler = SIG_IGN};
111 sigset_t mask;
112 return sigemptyset(&caught.sa_mask) || sigemptyset(&ignored.sa_mask) ||
113 sigaction(SIGUSR1, &caught, 0) || sigaction(SIGUSR2, &ignored, 0) || sigemptyset(&mask) ||
114 sigaddset(&mask, blocked_signal) ||
115 (pending_signals && (sigaction(SIGHUP, &caught, 0) || sigaddset(&mask, SIGUSR1) ||
116 sigaddset(&mask, SIGHUP))) ||
117 pthread_sigmask(SIG_SETMASK, &mask, 0);
118}
119
120static void* blocked_read(void* argument) {
121 struct exec_probe* probe = argument;
122 char token = 0;
123 probe->read_tid = syscall(SYS_gettid);
124 __atomic_store_n(&probe->read_ready, 1, __ATOMIC_RELEASE);
125 if (probe->pending_signals) {
126 sigset_t mask;
127 if (test_wait_flag(&probe->signals_queued) || pthread_sigmask(SIG_SETMASK, 0, &mask) ||
128 sigismember(&mask, SIGHUP) != 1 || sigismember(&mask, SIGUSR1) != 1) {
129 _exit(96);
130 }
131 __atomic_store_n(&probe->sibling_mask_checked, 1, __ATOMIC_RELEASE);
132 }
133 const int valid = read(probe->gate[0], &token, 1) == 1 && token == 'x';
134 __atomic_store_n(&probe->read_done, valid ? 1 : -1, __ATOMIC_RELEASE);
135 return 0;
136}
137
138static void* blocked_condition(void* argument) {
139 struct exec_probe* probe = argument;
140 if (pthread_mutex_lock(&probe->mutex)) {
141 _exit(70);
142 }
143 probe->condition_tid = syscall(SYS_gettid);
144 __atomic_store_n(&probe->condition_ready, 1, __ATOMIC_RELEASE);
145 while (!probe->stop) {
146 if (pthread_cond_wait(&probe->condition, &probe->mutex)) {
147 _exit(71);
148 }
149 }
150 pthread_mutex_unlock(&probe->mutex);
151 __atomic_store_n(&probe->condition_done, 1, __ATOMIC_RELEASE);
152 return 0;
153}
154
155static int replace_image(struct exec_probe* probe, int blocked_signal, int racing) {
156 char inherited[24], cloexec[24], expected_mask[24], read_tid[24], condition_tid[24], caller[24];
157 snprintf(inherited, sizeof(inherited), "%d", probe->inherited);
158 snprintf(cloexec, sizeof(cloexec), "%d", probe->cloexec);
159 snprintf(expected_mask, sizeof(expected_mask), "%d", blocked_signal);
160 snprintf(read_tid, sizeof(read_tid), "%ld", probe->read_tid);
161 snprintf(condition_tid, sizeof(condition_tid), "%ld", probe->condition_tid);
162 snprintf(caller, sizeof(caller), "%ld", syscall(SYS_gettid));
163 char* arguments[] = {(char*)probe->program,
164 (char*)"--exec-image",
165 inherited,
166 cloexec,
167 expected_mask,
168 read_tid,
169 condition_tid,
170 caller,
171 0};
172 if (racing) {
173 arguments[1] = (char*)"--exec-race-image";
174 arguments[5] = 0;
175 } else if (probe->pending_signals) {
176 arguments[1] = (char*)"--exec-pending-image";
177 }
178 while (1) {
179 execv(probe->program, arguments);
180 if (!racing || errno != EAGAIN) {
181 break;
182 }
183 test_pause();
184 }
185 fprintf(stderr, "threaded exec: execv errno=%d\n", errno);
186 _exit(72);
187}
188
189static void* exec_worker(void* argument) {
190 struct exec_probe* probe = argument;
191 if (set_exec_signals(SIGTERM, probe->pending_signals) || test_wait_flag(&probe->main_ready) ||
192 pthread_mutex_lock(&probe->mutex)) {
193 _exit(73);
194 }
195 // The main thread publishes readiness with this mutex held, so acquiring
196 // it establishes that the main thread has entered its condition wait.
197 pthread_mutex_unlock(&probe->mutex);
198 _exit(replace_image(probe, SIGTERM, 0));
199}
200
201static void* competing_exec(void* argument) {
202 struct exec_probe* probe = argument;
203 if (test_wait_flag(&probe->race_start) || test_wait_flag(&probe->clone_ready)) {
204 _exit(74);
205 }
206 _exit(replace_image(probe, SIGUSR1, 1));
207}
208
209static void* competing_clone(void* argument) {
210 struct exec_probe* probe = argument;
211 if (test_wait_flag(&probe->race_start)) {
212 _exit(75);
213 }
214 for (int attempt = 0; attempt < 16; ++attempt) {
215 pthread_t worker;
216 const int error = pthread_create(&worker, 0, fresh_thread, probe);
217 if (error && error != EAGAIN) {
218 _exit(76);
219 }
220 void* result = 0;
221 if (!error && (pthread_join(worker, &result) || result != probe)) {
222 _exit(77);
223 }
224 if (!error) {
225 __atomic_store_n(&probe->clone_ready, 1, __ATOMIC_RELEASE);
226 }
227 }
228 return 0;
229}
230
231static int race_contract(struct exec_probe* probe) {
232 pthread_t first, second, cloner;
233 if (pthread_create(&first, 0, competing_exec, probe) ||
234 pthread_create(&second, 0, competing_exec, probe) ||
235 pthread_create(&cloner, 0, competing_clone, probe)) {
236 _exit(78);
237 }
238 __atomic_store_n(&probe->race_start, 1, __ATOMIC_RELEASE);
239 while (1) {
240 test_pause();
241 }
242}
243
244static int failed_exec_contract(struct exec_probe* probe, pthread_t reader, pthread_t waiter) {
245 char* const arguments[] = {(char*)probe->program, 0};
246 errno = 0;
247 execv("/signal-exec-test-does-not-exist", arguments);
248 if (errno != ENOENT) {
249 _exit(80);
250 }
251
252 char path[96];
253 snprintf(path, sizeof(path), "/tmp/signal-exec-invalid-%ld", (long)getpid());
254 const int fixture = open(path, O_CREAT | O_EXCL | O_WRONLY, 0700);
255 if (fixture < 0) {
256 _exit(81);
257 }
258 static const char contents[] = "not an executable\n";
259 if (write(fixture, contents, sizeof(contents) - 1) != sizeof(contents) - 1 || close(fixture)) {
260 unlink(path);
261 _exit(82);
262 }
263 errno = 0;
264 execv(path, arguments);
265 const int error = errno;
266 if (unlink(path) || error != ENOEXEC) {
267 _exit(83);
268 }
269
270 struct sigaction current;
271 sigset_t mask;
272 if (sigaction(SIGUSR1, 0, &current) || current.sa_handler != caught_signal ||
273 !(current.sa_flags & SA_RESTART) || pthread_sigmask(SIG_SETMASK, 0, &mask) ||
274 sigismember(&mask, SIGUSR1) != 1 || fcntl(probe->cloexec, F_GETFD) != FD_CLOEXEC ||
275 __atomic_load_n(&probe->read_done, __ATOMIC_ACQUIRE) ||
276 __atomic_load_n(&probe->condition_done, __ATOMIC_ACQUIRE)) {
277 _exit(84);
278 }
279 if (write(probe->gate[1], "x", 1) != 1 || pthread_mutex_lock(&probe->mutex)) {
280 _exit(85);
281 }
282 probe->stop = 1;
283 pthread_cond_broadcast(&probe->condition);
284 pthread_mutex_unlock(&probe->mutex);
285 if (test_wait_flag(&probe->read_done) || test_wait_flag(&probe->condition_done) ||
286 pthread_join(reader, 0) || pthread_join(waiter, 0) || probe->read_done != 1 ||
287 probe->condition_done != 1) {
288 _exit(86);
289 }
290 return close(probe->gate[0]) || close(probe->gate[1]) || close(probe->inherited) ||
291 close(probe->cloexec) || pthread_cond_destroy(&probe->condition) ||
292 pthread_mutex_destroy(&probe->mutex)
293 ? 87
294 : 0;
295}
296
297static void queue_exec_signals(struct exec_probe* probe, pthread_t reader) {
298 // Queue the private copy first so coalescing must retain the later process
299 // signal when the original main thread retires during worker exec.
300 if (pthread_kill(pthread_self(), SIGUSR1) || pthread_kill(reader, SIGUSR1) ||
301 pthread_kill(reader, SIGHUP) || kill(getpid(), SIGUSR1)) {
302 _exit(97);
303 }
304 __atomic_store_n(&probe->signals_queued, 1, __ATOMIC_RELEASE);
305 if (test_wait_flag(&probe->sibling_mask_checked)) {
306 _exit(98);
307 }
308 test_pause();
309 if (__atomic_load_n(&probe->read_done, __ATOMIC_ACQUIRE)) {
310 _exit(99);
311 }
312}
313
315 const char* program;
316 volatile int exec_ready;
317 volatile int exit_ready;
318 volatile int start;
319};
320
321static void* race_exec_with_exit(void* argument) {
322 struct exit_race_probe* probe = argument;
323 char* const arguments[] = {(char*)probe->program, (char*)"--exec-exit-image", 0};
324 __atomic_store_n(&probe->exec_ready, 1, __ATOMIC_RELEASE);
325 if (test_wait_flag(&probe->start)) {
326 _exit(110);
327 }
328 while (1) {
329 execv(probe->program, arguments);
330 if (errno != EAGAIN) {
331 _exit(111);
332 }
333 test_pause();
334 }
335}
336
337static void* race_exit_with_exec(void* argument) {
338 struct exit_race_probe* probe = argument;
339 __atomic_store_n(&probe->exit_ready, 1, __ATOMIC_RELEASE);
340 if (test_wait_flag(&probe->start)) {
341 _exit(113);
342 }
343 _exit(37);
344}
345
346static int exec_exit_contract(const char* program) {
347 struct exit_race_probe probe = {.program = program};
348 pthread_t exec_worker, exit_worker;
349 if (pthread_create(&exec_worker, 0, race_exec_with_exit, &probe) ||
350 pthread_create(&exit_worker, 0, race_exit_with_exec, &probe) ||
351 test_wait_flag(&probe.exec_ready) || test_wait_flag(&probe.exit_ready)) {
352 _exit(114);
353 }
354 __atomic_store_n(&probe.start, 1, __ATOMIC_RELEASE);
355 while (1) {
356 test_pause();
357 }
358}
359
360int exec_contract(const char* program, const char* name) {
361 if (!strcmp(name, "exec-exit-race")) {
362 return exec_exit_contract(program);
363 }
364 struct exec_probe probe = {.program = program,
365 .mutex = PTHREAD_MUTEX_INITIALIZER,
366 .condition = PTHREAD_COND_INITIALIZER,
367 .pending_signals = !strncmp(name, "exec-pending-", 13)};
368 if (set_exec_signals(SIGUSR1, probe.pending_signals) || pipe(probe.gate)) {
369 return 90;
370 }
371 probe.inherited = open("/dev/null", O_RDONLY);
372 probe.cloexec = open("/dev/null", O_RDONLY | O_CLOEXEC);
373 if (probe.inherited < 0 || probe.cloexec < 0) {
374 return 91;
375 }
376 pthread_t reader, waiter;
377 if (pthread_create(&reader, 0, blocked_read, &probe) ||
378 pthread_create(&waiter, 0, blocked_condition, &probe) || test_wait_flag(&probe.read_ready) ||
379 test_wait_flag(&probe.condition_ready) || pthread_mutex_lock(&probe.mutex)) {
380 _exit(92);
381 }
382 pthread_mutex_unlock(&probe.mutex);
383 test_pause();
384 if (probe.read_tid <= 0 || probe.condition_tid <= 0 ||
385 __atomic_load_n(&probe.read_done, __ATOMIC_ACQUIRE) ||
386 __atomic_load_n(&probe.condition_done, __ATOMIC_ACQUIRE)) {
387 _exit(93);
388 }
389 if (!strcmp(name, "exec-failure")) {
390 return failed_exec_contract(&probe, reader, waiter);
391 }
392 if (!strcmp(name, "exec-main") || !strcmp(name, "exec-pending-main")) {
393 if (probe.pending_signals) {
394 queue_exec_signals(&probe, reader);
395 }
396 return replace_image(&probe, SIGUSR1, 0);
397 }
398 if (!strcmp(name, "exec-race")) {
399 return race_contract(&probe);
400 }
401 pthread_t worker;
402 if (pthread_create(&worker, 0, exec_worker, &probe) || pthread_mutex_lock(&probe.mutex)) {
403 _exit(94);
404 }
405 if (probe.pending_signals) {
406 queue_exec_signals(&probe, reader);
407 }
408 __atomic_store_n(&probe.main_ready, 1, __ATOMIC_RELEASE);
409 while (1) {
410 if (pthread_cond_wait(&probe.condition, &probe.mutex)) {
411 _exit(95);
412 }
413 }
414}
415
416int exec_image_contract(int argc, char* argv[]) {
417 if (!strcmp(argv[1], "--exec-exit-image")) {
418 return argc == 2 && syscall(SYS_gettid) == getpid() ? 0 : 112;
419 }
420 const int racing = !strcmp(argv[1], "--exec-race-image");
421 const int pending_signals = !strcmp(argv[1], "--exec-pending-image");
422 if (argc != (racing ? 5 : 8) || syscall(SYS_gettid) != getpid()) {
423 return 100;
424 }
425 const int inherited = atoi(argv[2]);
426 const int cloexec = atoi(argv[3]);
427 const int blocked_signal = atoi(argv[4]);
428 struct sigaction action;
429 sigset_t mask;
430 if (sigaction(SIGUSR1, 0, &action) || action.sa_handler != SIG_DFL ||
431 sigaction(SIGUSR2, 0, &action) || action.sa_handler != SIG_IGN ||
432 pthread_sigmask(SIG_SETMASK, 0, &mask) || sigismember(&mask, blocked_signal) != 1 ||
433 sigismember(&mask, SIGTERM) != (blocked_signal == SIGTERM) ||
434 sigismember(&mask, SIGUSR1) != (pending_signals || blocked_signal == SIGUSR1)) {
435 return 101;
436 }
437 if (pending_signals) {
438 if (sigaction(SIGHUP, 0, &action) || action.sa_handler != SIG_DFL ||
439 sigismember(&mask, SIGHUP) != 1) {
440 return 106;
441 }
442 const int delivery_result = pending_delivery_contract(&mask, blocked_signal == SIGUSR1);
443 if (delivery_result) {
444 return delivery_result;
445 }
446 }
447 char byte;
448 if (fcntl(inherited, F_GETFD) != 0 || read(inherited, &byte, 1) != 0) {
449 return 102;
450 }
451 errno = 0;
452 if (fcntl(cloexec, F_GETFD) != -1 || errno != EBADF) {
453 return 103;
454 }
455 for (int i = 5; i < argc; ++i) {
456 const long old_tid = atol(argv[i]);
457 if (old_tid == getpid()) {
458 continue;
459 }
460 errno = 0;
461 if (syscall(SYS_tgkill, getpid(), old_tid, 0) != -1 || errno != ESRCH) {
462 return 104;
463 }
464 }
465 pthread_t worker;
466 void* result = 0;
467 if (pthread_create(&worker, 0, fresh_thread, &mask) || pthread_join(worker, &result) ||
468 result != &mask || close(inherited)) {
469 return 105;
470 }
471 return 0;
472}
Definition waits.c:9