The Pedigree Project 0.1
ptrace-contract-test/signals.c
1#define _GNU_SOURCE
2#include <fcntl.h>
3#include <string.h>
4#include <unistd.h>
5
6#include "contract.h"
7
8static volatile sig_atomic_t child_notice = -1;
9
10static void child_changed(int signal, siginfo_t* info, void* context) {
11 (void)context;
12 int saved = errno;
13 struct tc_message message = {.kind = 'C',
14 .signal = signal,
15 .code = info->si_code,
16 .pid = info->si_pid,
17 .uid = info->si_uid,
18 .result = info->si_status};
19 if (child_notice >= 0) {
20 ssize_t ignored = write(child_notice, &message, sizeof(message));
21 (void)ignored;
22 }
23 errno = saved;
24}
25
26static int parent_notification(void) {
27 int failed = 0, installed = 0, mask_saved = 0;
28 int report[2] = {-1, -1};
29 struct tc_child child = TC_CHILD_INIT;
30 struct tc_message message;
31 struct sigaction action = {.sa_sigaction = child_changed, .sa_flags = SA_SIGINFO | SA_RESTART};
32 struct sigaction old;
33 sigset_t mask, previous;
34 sigemptyset(&action.sa_mask);
35 sigemptyset(&mask);
36 sigaddset(&mask, SIGCHLD);
37 CHECK(pipe2(report, O_CLOEXEC | O_NONBLOCK) == 0);
38 child_notice = report[1];
39 CHECK(sigaction(SIGCHLD, &action, &old) == 0);
40 installed = 1;
41 CHECK(sigprocmask(SIG_UNBLOCK, &mask, &previous) == 0);
42 mask_saved = 1;
43 CHECK(tc_spawn(&child, 1) == 0 && tc_stop(&child, SIGUSR1) == 0);
44 CHECK(tc_receive(report[0], 'C', &message) == 0);
45 CHECK(message.signal == SIGCHLD && message.code == CLD_TRAPPED && message.pid == child.pid &&
46 message.uid == getuid() && message.result == SIGUSR1);
47 CHECK(tc_resume(child.pid, PTRACE_CONT, 0) == 0 && tc_receive(child.report, 'A', NULL) == 0);
48 CHECK(tc_finish(&child) == 0);
49out:
50 tc_close(&child);
51 child_notice = -1;
52 if (installed && sigaction(SIGCHLD, &old, NULL))
53 failed = 1;
54 if (mask_saved && sigprocmask(SIG_SETMASK, &previous, NULL))
55 failed = 1;
56 if (report[0] >= 0)
57 close(report[0]);
58 if (report[1] >= 0)
59 close(report[1]);
60 return failed;
61}
62
63static int configure(struct tc_child* child, int operation, int signal) {
64 struct tc_message message;
65 if (tc_send(child, operation, signal) || tc_receive(child->report, operation, &message))
66 return -1;
67 return message.result ? -1 : 0;
68}
69
70static int handler(struct tc_child* child, int signal, int code, pid_t pid) {
71 struct tc_message message;
72 if (tc_receive(child->report, 'H', &message))
73 return -1;
74 if (message.signal == signal && message.code == code && message.pid == pid &&
75 message.uid == getuid())
76 return 0;
77 fprintf(stderr, "handler actual=%d/%d/%d/%u expected=%d/%d/%d/%u\n", message.signal, message.code,
78 message.pid, message.uid, signal, code, pid, getuid());
79 return -1;
80}
81
82static int decisions(void) {
83 int failed = 0, status;
84 struct tc_child child = TC_CHILD_INIT;
85 siginfo_t info;
86 struct user_regs_struct registers;
87 CHECK(tc_spawn(&child, 1) == 0);
88 CHECK(tc_stop(&child, SIGUSR1) == 0);
89 CHECK(tc_resume(child.pid, PTRACE_CONT, 0) == 0);
90 CHECK(tc_receive(child.report, 'A', NULL) == 0);
91
92 CHECK(tc_stop(&child, SIGUSR1) == 0);
93 CHECK(ptrace(PTRACE_GETSIGINFO, child.pid, (void*)0, (void*)&info) == 0);
94 CHECK(tc_signal_info(&info, SIGUSR1, SI_TKILL, child.pid, getuid()) == 0);
95 CHECK(tc_resume(child.pid, PTRACE_CONT, SIGUSR1) == 0);
96 CHECK(handler(&child, SIGUSR1, SI_TKILL, child.pid) == 0);
97 CHECK(tc_receive(child.report, 'A', NULL) == 0);
98
99 CHECK(tc_stop(&child, SIGUSR1) == 0);
100 CHECK(tc_resume(child.pid, PTRACE_CONT, SIGUSR2) == 0);
101 CHECK(handler(&child, SIGUSR2, SI_USER, getpid()) == 0);
102 CHECK(tc_receive(child.report, 'A', NULL) == 0);
103
104 CHECK(configure(&child, 'B', SIGUSR2) == 0 && tc_stop(&child, SIGUSR1) == 0);
105 CHECK(tc_resume(child.pid, PTRACE_CONT, SIGUSR2) == 0);
106 CHECK(tc_receive(child.report, 'A', NULL) == 0);
107 CHECK(tc_send(&child, 'U', SIGUSR2) == 0);
108 CHECK(tc_wait_stop(&child, SIGUSR2) == 0);
109 CHECK(ptrace(PTRACE_GETSIGINFO, child.pid, (void*)0, (void*)&info) == 0);
110 CHECK(tc_signal_info(&info, SIGUSR2, SI_USER, getpid(), getuid()) == 0);
111 CHECK(tc_resume(child.pid, PTRACE_CONT, SIGUSR2) == 0);
112 CHECK(handler(&child, SIGUSR2, SI_USER, getpid()) == 0);
113 CHECK(tc_receive(child.report, 'U', NULL) == 0);
114
115 CHECK(configure(&child, 'I', SIGUSR2) == 0 && tc_stop(&child, SIGUSR2) == 0);
116 CHECK(tc_resume(child.pid, PTRACE_CONT, SIGUSR2) == 0);
117 CHECK(tc_receive(child.report, 'A', NULL) == 0);
118 CHECK(configure(&child, 'F', SIGCHLD) == 0 && tc_stop(&child, SIGCHLD) == 0);
119 CHECK(ptrace(PTRACE_GETSIGINFO, child.pid, (void*)0, (void*)&info) == 0);
120 CHECK(tc_signal_info(&info, SIGCHLD, SI_TKILL, child.pid, getuid()) == 0);
121 CHECK(tc_resume(child.pid, PTRACE_CONT, SIGCHLD) == 0);
122 CHECK(tc_receive(child.report, 'A', NULL) == 0);
123
124 CHECK(tc_stop(&child, SIGUSR1) == 0 && tc_resume(child.pid, PTRACE_DETACH, 0) == 0);
125 CHECK(tc_receive(child.report, 'A', NULL) == 0);
126 errno = 0;
127 CHECK(ptrace(PTRACE_GETREGS, child.pid, (void*)0, (void*)&registers) == -1 && errno == ESRCH);
128 CHECK(tc_send(&child, 'S', SIGUSR1) == 0);
129 CHECK(handler(&child, SIGUSR1, SI_TKILL, child.pid) == 0);
130 CHECK(tc_receive(child.report, 'A', NULL) == 0);
131 CHECK(wait4(child.pid, &status, WNOHANG, NULL) == 0);
132 CHECK(tc_finish(&child) == 0);
133out:
134 tc_close(&child);
135 return failed;
136}
137
138static int default_fatal(void) {
139 int failed = 0, status;
140 struct tc_child child = TC_CHILD_INIT;
141 siginfo_t info;
142 CHECK(tc_spawn(&child, 1) == 0 && tc_stop(&child, SIGTERM) == 0);
143 CHECK(ptrace(PTRACE_GETSIGINFO, child.pid, (void*)0, (void*)&info) == 0);
144 CHECK(tc_signal_info(&info, SIGTERM, SI_TKILL, child.pid, getuid()) == 0);
145 CHECK(tc_resume(child.pid, PTRACE_CONT, SIGTERM) == 0);
146 CHECK(tc_wait(child.pid, &status, 10000) == 0);
147 if (WIFEXITED(status) || WIFSIGNALED(status))
148 child.pid = -1;
149 CHECK(WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM);
150out:
151 tc_close(&child);
152 return failed;
153}
154
155static int group_stop(void) {
156 int failed = 0;
157 struct tc_child child = TC_CHILD_INIT;
158 siginfo_t info, untouched;
159 struct user_regs_struct first, second;
160 CHECK(tc_spawn(&child, 1) == 0 && tc_stop(&child, SIGSTOP) == 0);
161 CHECK(ptrace(PTRACE_GETSIGINFO, child.pid, (void*)0, (void*)&info) == 0);
162 CHECK(tc_signal_info(&info, SIGSTOP, SI_TKILL, child.pid, getuid()) == 0);
163 CHECK(tc_resume(child.pid, PTRACE_CONT, SIGSTOP) == 0);
164 CHECK(tc_wait_stop(&child, SIGSTOP) == 0);
165 memset(&info, 0xa5, sizeof(info));
166 untouched = info;
167 errno = 0;
168 CHECK(ptrace(PTRACE_GETSIGINFO, child.pid, (void*)0, (void*)&info) == -1 && errno == EINVAL);
169 CHECK(memcmp(&info, &untouched, sizeof(info)) == 0);
170 CHECK(ptrace(PTRACE_GETREGS, child.pid, (void*)0, (void*)&first) == 0);
171 CHECK(kill(child.pid, SIGCONT) == 0);
172 CHECK(ptrace(PTRACE_GETREGS, child.pid, (void*)0, (void*)&second) == 0);
173 CHECK(memcmp(&first, &second, sizeof(first)) == 0);
174 errno = 0;
175 CHECK(ptrace(PTRACE_GETSIGINFO, child.pid, (void*)0, (void*)&info) == -1 && errno == EINVAL);
176 for (unsigned i = 0; i < 2; ++i) {
177 CHECK(waitid(P_PID, child.pid, &info, WCONTINUED | WNOWAIT) == 0);
178 CHECK(info.si_pid == child.pid && info.si_code == CLD_CONTINUED && info.si_status == SIGCONT);
179 }
180 CHECK(waitid(P_PID, child.pid, &info, WCONTINUED) == 0 && info.si_pid == child.pid);
181 CHECK(waitid(P_PID, child.pid, &info, WCONTINUED | WNOHANG) == 0 && !info.si_pid);
182 CHECK(tc_resume(child.pid, PTRACE_CONT, 0) == 0);
183 CHECK(tc_wait_stop(&child, SIGCONT) == 0);
184 CHECK(ptrace(PTRACE_GETSIGINFO, child.pid, (void*)0, (void*)&info) == 0);
185 CHECK(tc_signal_info(&info, SIGCONT, SI_USER, getpid(), getuid()) == 0);
186 CHECK(tc_resume(child.pid, PTRACE_CONT, 0) == 0 && tc_receive(child.report, 'A', NULL) == 0);
187 CHECK(tc_finish(&child) == 0);
188out:
189 tc_close(&child);
190 return failed;
191}
192
193static int group_cont_alone(void) {
194 int failed = 0;
195 struct tc_child child = TC_CHILD_INIT;
196 siginfo_t info;
197 CHECK(tc_spawn(&child, 1) == 0 && tc_stop(&child, SIGSTOP) == 0);
198 CHECK(tc_resume(child.pid, PTRACE_CONT, SIGSTOP) == 0 && tc_wait_stop(&child, SIGSTOP) == 0);
199 errno = 0;
200 CHECK(ptrace(PTRACE_GETSIGINFO, child.pid, (void*)0, (void*)&info) == -1 && errno == EINVAL);
201 // Ordinary CONT resumes a group stop; retaining it would require deferred LISTEN.
202 CHECK(tc_resume(child.pid, PTRACE_CONT, 0) == 0 && tc_receive(child.report, 'A', NULL) == 0);
203 CHECK(tc_finish(&child) == 0);
204out:
205 tc_close(&child);
206 return failed;
207}
208
209int tc_signals(void) {
210 return parent_notification() || decisions() || default_fatal() || group_stop() ||
211 group_cont_alone();
212}