The Pedigree Project 0.1
thread-signal-syscall-regressions.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/errors.h"
11#include "pedigree/kernel/process/Process.h"
12#include "pedigree/kernel/process/Scheduler.h"
13#include "pedigree/kernel/process/SignalEvent.h"
14#include "pedigree/kernel/process/Thread.h"
15#include "pedigree/kernel/processor/Processor.h"
16
17#include <limits.h>
18#include <signal.h>
19
20#include "modules/subsys/posix/PosixProcess.h"
21#include "modules/subsys/posix/PosixSubsystem.h"
22#include "modules/subsys/posix/signal-syscalls.h"
23
24namespace {
25constexpr size_t HostedAttempts = 10000;
26constexpr size_t MuslTimerSignal = 32;
27constexpr size_t MuslCancelSignal = 33;
28constexpr size_t MuslSyncCallSignal = 34;
29constexpr size_t FirstRealtimeSignal = 35;
30constexpr size_t LastRealtimeSignal = 64;
31constexpr size_t FirstUnsupportedSignal = 65;
32
33void threadSignalHandler(size_t) {}
34
35void installDisposition(PosixSubsystem* subsystem, size_t signal, int type) {
37 handler->type = type;
38 handler->pEvent = new SignalEvent(reinterpret_cast<uintptr_t>(&threadSignalHandler), signal);
39 subsystem->setSignalHandler(signal, handler);
40}
41
42PosixProcess* makeProcess(Process* parent, int64_t uid) {
43 PosixProcess* process = new PosixProcess(parent);
44 process->setSubsystem(new PosixSubsystem);
45 process->setUserId(uid);
46 process->setEffectiveUserId(uid);
47 process->setSavedUserId(uid);
48 return process;
49}
50
51int dormantThread(void*) {
52 return 0;
53}
54
55struct ThreadSignalContext {
56 ThreadSignalContext(PosixProcess* targetProcess, Thread* sameTarget, Thread* remoteTarget,
57 PosixProcess* deniedProcess, Thread* deniedTarget)
58 : targetProcess(targetProcess),
59 sameTarget(sameTarget),
60 remoteTarget(remoteTarget),
61 deniedProcess(deniedProcess),
62 deniedTarget(deniedTarget),
63 entered(0),
64 passed(0) {}
65
66 PosixProcess* targetProcess;
67 Thread* sameTarget;
68 Thread* remoteTarget;
69 PosixProcess* deniedProcess;
70 Thread* deniedTarget;
71 Atomic<size_t> entered;
72 Atomic<size_t> passed;
73};
74
75bool expectResult(Thread* current, int result, int expectedResult, size_t expectedError) {
76 return result == expectedResult && current->getErrno() == expectedError;
77}
78
79int runThreadSignalCalls(void* parameter) {
80 ThreadSignalContext* context = reinterpret_cast<ThreadSignalContext*>(parameter);
81 Thread* current = Processor::information().getCurrentThread();
82 PosixProcess* caller = static_cast<PosixProcess*>(current->getParent());
83 bool passed = true;
84 context->entered += 1;
85
86 current->setErrno(0);
87 passed &= expectResult(current, posix_tkill(context->sameTarget->getId(), 0), 0, 0);
88 passed &= !context->sameTarget->hasEvent(SIGUSR1);
89
90 current->setErrno(0);
91 passed &= expectResult(current, posix_tkill(context->sameTarget->getId(), SIGUSR2), 0, 0);
92 passed &= !context->sameTarget->hasEvent(SIGUSR2);
93
94 const uint64_t originalMask = context->sameTarget->getSignalMask();
95 context->sameTarget->setSignalMask(originalMask | (static_cast<uint64_t>(1) << (SIGUSR1 - 1)));
96 current->setErrno(0);
97 passed &= expectResult(current, posix_tkill(context->sameTarget->getId(), SIGUSR1), 0, 0);
98 passed &= context->sameTarget->hasEvent(SIGUSR1) && !current->hasEvent(SIGUSR1);
99 context->sameTarget->cullSignalEvent(SIGUSR1);
100 context->sameTarget->setSignalMask(originalMask);
101
102 context->sameTarget->setSignalMask(originalMask |
103 (static_cast<uint64_t>(1) << (MuslTimerSignal - 1)));
104 current->setErrno(0);
105 passed &= expectResult(current, posix_tkill(context->sameTarget->getId(), MuslTimerSignal), 0, 0);
106 passed &= context->sameTarget->hasEvent(MuslTimerSignal);
107 context->sameTarget->cullSignalEvent(MuslTimerSignal);
108 context->sameTarget->setSignalMask(originalMask);
109
110 current->setErrno(0);
111 passed &=
112 expectResult(current, posix_tkill(context->sameTarget->getId(), MuslCancelSignal), 0, 0);
113 passed &= context->sameTarget->hasEvent(MuslCancelSignal);
114 context->sameTarget->cullSignalEvent(MuslCancelSignal);
115
116 current->setErrno(0);
117 passed &= expectResult(current, posix_tkill(0, 0), -1, Error::InvalidArgument);
118 current->setErrno(0);
119 passed &= expectResult(current, posix_tkill(-1, 0), -1, Error::InvalidArgument);
120 current->setErrno(0);
121 passed &=
122 expectResult(current, posix_tkill(context->remoteTarget->getId(), FirstUnsupportedSignal), -1,
123 Error::NoSuchProcess);
124 current->setErrno(0);
125 passed &= expectResult(current, posix_tkill(context->sameTarget->getId(), FirstUnsupportedSignal),
126 -1, Error::InvalidArgument);
127
128 current->setErrno(0);
129 passed &=
130 expectResult(current, posix_tgkill(caller->getId(), context->sameTarget->getId(), 0), 0, 0);
131 current->setErrno(0);
132 passed &= expectResult(current, posix_tgkill(0, context->sameTarget->getId(), 0), -1,
133 Error::InvalidArgument);
134 current->setErrno(0);
135 passed &= expectResult(current, posix_tgkill(caller->getId(), 0, 0), -1, Error::InvalidArgument);
136
137 current->setErrno(0);
138 passed &= expectResult(
139 current,
140 posix_tgkill(context->targetProcess->getId(), context->remoteTarget->getId(), SIGUSR1), 0, 0);
141 passed &= context->remoteTarget->hasEvent(SIGUSR1);
142 context->remoteTarget->cullSignalEvent(SIGUSR1);
143
144 current->setErrno(0);
145 passed &= expectResult(
146 current,
147 posix_tgkill(context->targetProcess->getId(), context->remoteTarget->getId(), SIGUSR2), 0, 0);
148 passed &= !context->remoteTarget->hasEvent(SIGUSR2);
149
150 current->setErrno(0);
151 passed &= expectResult(current,
152 posix_tgkill(context->targetProcess->getId(),
153 context->remoteTarget->getId(), MuslSyncCallSignal),
154 0, 0);
155 passed &= context->remoteTarget->hasEvent(MuslSyncCallSignal);
156 context->remoteTarget->cullSignalEvent(MuslSyncCallSignal);
157
158 const size_t realtimeSignals[] = {FirstRealtimeSignal, LastRealtimeSignal};
159 for (size_t signal : realtimeSignals) {
160 context->sameTarget->setSignalMask(originalMask | (static_cast<uint64_t>(1) << (signal - 1)));
161 current->setErrno(0);
162 passed &= expectResult(current, posix_tkill(context->sameTarget->getId(), signal), 0, 0);
163 passed &= context->sameTarget->hasEvent(signal) && !current->hasEvent(signal);
164 context->sameTarget->cullSignalEvent(signal);
165 context->sameTarget->setSignalMask(originalMask);
166
167 current->setErrno(0);
168 passed &= expectResult(
169 current,
170 posix_tgkill(context->targetProcess->getId(), context->remoteTarget->getId(), signal), 0,
171 0);
172 passed &= context->remoteTarget->hasEvent(signal);
173 context->remoteTarget->cullSignalEvent(signal);
174
175 current->setErrno(0);
176 passed &= expectResult(
177 current,
178 posix_tgkill(context->deniedProcess->getId(), context->deniedTarget->getId(), signal), -1,
179 Error::NotEnoughPermissions);
180 }
181
182 current->setErrno(0);
183 passed &= expectResult(
184 current, posix_tgkill(context->targetProcess->getId(), INT_MAX, FirstUnsupportedSignal), -1,
185 Error::NoSuchProcess);
186 current->setErrno(0);
187 passed &= expectResult(
188 current, posix_tgkill(INT_MAX, context->remoteTarget->getId(), FirstUnsupportedSignal), -1,
189 Error::NoSuchProcess);
190 current->setErrno(0);
191 passed &= expectResult(current,
192 posix_tgkill(context->targetProcess->getId(),
193 context->remoteTarget->getId(), FirstUnsupportedSignal),
194 -1, Error::InvalidArgument);
195
196 current->setErrno(0);
197 passed &= expectResult(current, posix_kill(caller->getId(), MuslCancelSignal), -1,
198 Error::InvalidArgument);
199 current->setErrno(0);
200 passed &= expectResult(current, posix_kill(caller->getId(), FirstUnsupportedSignal), -1,
201 Error::InvalidArgument);
202
203 current->setErrno(0);
204 passed &= expectResult(
205 current, posix_tgkill(context->deniedProcess->getId(), context->deniedTarget->getId(), 0), -1,
206 Error::NotEnoughPermissions);
207
208 if (passed) {
209 context->passed += 1;
210 }
211 current->setErrno(0);
212 return 0;
213}
214
215struct SuspendContext {
216 explicit SuspendContext(Process* process) : process(process), entered(0), returned(0) {}
217
218 Process* process;
219 Atomic<size_t> entered;
220 Atomic<size_t> returned;
221};
222
223int suspendTarget(void* parameter) {
224 SuspendContext* context = reinterpret_cast<SuspendContext*>(parameter);
225 context->entered += 1;
226 context->process->suspend();
227 context->returned += 1;
228 return 0;
229}
230
231bool waitForSuspension(Process* process) {
232 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
233 if (process->isSuspended()) {
234 return true;
235 }
237 }
238 return false;
239}
240} // namespace
241
242bool runHostedThreadSignalSyscallRegressions(Process* kernelProcess) {
243 PosixProcess* caller = makeProcess(kernelProcess, 1000);
244 PosixSubsystem* callerSubsystem = static_cast<PosixSubsystem*>(caller->getSubsystem());
245 installDisposition(callerSubsystem, SIGUSR1, 0);
246 installDisposition(callerSubsystem, SIGUSR2, 2);
247 installDisposition(callerSubsystem, MuslTimerSignal, 0);
248 installDisposition(callerSubsystem, MuslCancelSignal, 0);
249 installDisposition(callerSubsystem, FirstRealtimeSignal, 0);
250 installDisposition(callerSubsystem, LastRealtimeSignal, 0);
251 Thread* sameTarget = new Thread(caller, dormantThread, nullptr, nullptr, false, true, true);
252
253 PosixProcess* target = makeProcess(kernelProcess, 1000);
254 PosixSubsystem* targetSubsystem = static_cast<PosixSubsystem*>(target->getSubsystem());
255 installDisposition(targetSubsystem, SIGUSR1, 0);
256 installDisposition(targetSubsystem, SIGUSR2, 2);
257 installDisposition(targetSubsystem, MuslSyncCallSignal, 0);
258 installDisposition(targetSubsystem, FirstRealtimeSignal, 0);
259 installDisposition(targetSubsystem, LastRealtimeSignal, 0);
260 new Thread(target, dormantThread, nullptr, nullptr, false, true, true);
261 new Thread(target, dormantThread, nullptr, nullptr, false, true, true);
262 Thread* remoteTarget = new Thread(target, dormantThread, nullptr, nullptr, false, true, true);
263
264 PosixProcess* denied = makeProcess(kernelProcess, 2000);
265 PosixSubsystem* deniedSubsystem = static_cast<PosixSubsystem*>(denied->getSubsystem());
266 installDisposition(deniedSubsystem, SIGUSR1, 0);
267 Thread* deniedTarget = new Thread(denied, dormantThread, nullptr, nullptr, false, true, true);
268
269 ThreadSignalContext context(target, sameTarget, remoteTarget, denied, deniedTarget);
270 Thread* callerThread =
271 new Thread(caller, runThreadSignalCalls, &context, nullptr, false, true, true);
272 callerThread->setName("hosted thread-signal syscall caller");
273
274 caller->publish();
275 target->publish();
276 denied->publish();
277
278 SuspendContext suspendContext(target);
279 Thread* suspender =
280 new Thread(kernelProcess, suspendTarget, &suspendContext, nullptr, false, true, true);
281 suspender->setName("hosted thread-signal suspended-target fixture");
282 const bool suspenderStarted = suspender->start();
283 const bool suspended = suspenderStarted && waitForSuspension(target);
284 const bool callerStarted = suspended && callerThread->start();
285 const bool callerJoined = callerStarted && callerThread->joinForCompletion();
286
287 if (suspenderStarted && !suspended) {
289 }
290 if (target->isSuspended()) {
291 target->resume();
292 }
293 const bool suspenderJoined = suspenderStarted && suspender->joinForCompletion();
294 if (target->isSuspended()) {
295 target->resume();
296 }
297 if (!callerStarted) {
298 delete callerThread;
299 }
300 if (!suspenderStarted) {
301 delete suspender;
302 }
303
304 const bool passed = suspended && callerJoined && suspenderJoined && suspendContext.entered == 1 &&
305 suspendContext.returned == 1 && context.entered == 1 && context.passed == 1;
306
307 delete caller;
308 delete target;
309 delete denied;
310
311 if (!passed) {
312 ERROR(
313 "HOSTED-SYSCALL-TEST: FAIL thread-signal-syscalls: "
314 "tkill/tgkill target, mask, suspension, error, or permission semantics regressed");
315 return false;
316 }
317
318 NOTICE("HOSTED-SYSCALL-TEST: PASS thread-signal-syscalls");
319 return true;
320}
void setSignalHandler(size_t sig, SignalHandler *handler)
size_t getId()
Definition Process.h:463
void resume()
Definition Process.cc:1968
static ProcessorInformation & information()
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:226
void setErrno(size_t err)
Definition Thread.h:478
void setUnwindState(UnwindType ut)
Definition Thread.cc:3628
@ TerminateThread
Exit only this thread during Process exit.
Definition Thread.h:515
bool hasEvent(Event *pEvent)
Definition Thread.cc:2639
size_t getErrno()
Definition Thread.h:473
bool joinForCompletion()
Definition Thread.cc:2771
Process * getParent() const
Definition Thread.h:338
bool start()
Definition Thread.cc:794
int type
Type - 0 = normal, 1 = SIG_DFL, 2 = SIG_IGN.
SignalEvent * pEvent
Event for the signal handler.