The Pedigree Project 0.1
rt-sigsuspend-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/PerProcessorScheduler.h"
12#include "pedigree/kernel/process/Process.h"
13#include "pedigree/kernel/process/Scheduler.h"
14#include "pedigree/kernel/process/SignalEvent.h"
15#include "pedigree/kernel/process/Thread.h"
16#include "pedigree/kernel/processor/Processor.h"
17#include "pedigree/kernel/processor/VirtualAddressSpace.h"
18#include "pedigree/kernel/time/Time.h"
19
20#include <signal.h>
21#include <stdint.h>
22
23#include "modules/subsys/posix/PosixSubsystem.h"
24#include "modules/subsys/posix/signal-syscalls.h"
25
26namespace {
27constexpr size_t TestSignal = 10;
28constexpr int PreservedErrno = 123;
29constexpr uint64_t TestSignalBit = static_cast<uint64_t>(1) << (TestSignal - 1);
30constexpr uint64_t PreservedSignalBit = static_cast<uint64_t>(1) << (12 - 1);
31constexpr uint64_t UnblockableSignalBits =
32 (static_cast<uint64_t>(1) << (SIGKILL - 1)) | (static_cast<uint64_t>(1) << (SIGSTOP - 1));
33constexpr uint64_t OriginalSignalMask = TestSignalBit | PreservedSignalBit;
34constexpr uint64_t RequestedSignalMask = PreservedSignalBit | UnblockableSignalBits;
35constexpr uint64_t ActiveSignalMask = PreservedSignalBit;
36
37Atomic<size_t> g_SigsuspendSignalCalls(0);
38Atomic<size_t> g_SigsuspendNonSignalCalls(0);
39Atomic<uint64_t> g_SigsuspendHandlerMask(0);
40Atomic<size_t> g_SigsuspendPreEnrolmentCalls(0);
41Atomic<size_t> g_SigsuspendPreEnrolmentQueued(0);
42Atomic<size_t> g_SigsuspendUnexpectedWaits(0);
43Atomic<size_t> g_SigsuspendDefaultStopCalls(0);
44Atomic<size_t> g_SigsuspendDefaultContinueCalls(0);
45Thread* g_SigsuspendPreEnrolmentTarget = nullptr;
46
47void sigsuspendSignalHandler(size_t) {
48 Thread* thread = Processor::information().getCurrentThread();
49 g_SigsuspendSignalCalls += 1;
50 g_SigsuspendHandlerMask = thread ? thread->getSignalMask() : 0;
51}
52
53void sigsuspendNonSignalHandler(size_t) {
54 g_SigsuspendNonSignalCalls += 1;
55}
56
57void sigsuspendDefaultStopHandler(size_t) {
58 Thread* thread = Processor::information().getCurrentThread();
59 g_SigsuspendDefaultStopCalls += 1;
60 if (thread) {
61 thread->getParent()->suspend();
62 }
63}
64
65void sigsuspendDefaultContinueHandler(size_t) {
66 Thread* thread = Processor::information().getCurrentThread();
67 g_SigsuspendDefaultContinueCalls += 1;
68 if (thread) {
69 thread->getParent()->resume();
70 }
71}
72
73void sigsuspendPreEnrolmentHook(Thread* thread) {
74 if (g_SigsuspendPreEnrolmentCalls) {
75 return;
76 }
77 g_SigsuspendPreEnrolmentCalls += 1;
78
79 SignalEvent signal(reinterpret_cast<uintptr_t>(&sigsuspendSignalHandler), TestSignal, ~0UL, 0,
80 true, false, Event::HandlerPrivilege::Kernel,
81 SignalEvent::DeliveryDisposition::CaughtHandler);
82 if (thread->sendEvent(&signal)) {
83 g_SigsuspendPreEnrolmentQueued += 1;
84 thread->getScheduler()->checkEventState(0);
85 if (thread->hasEvent(&signal)) {
86 thread->cullEvent(&signal);
87 }
88 }
89}
90
91void sigsuspendUnexpectedWaitHook(WaitQueue*, Thread* thread, const WaitQueue::Channel&,
92 size_t debugState) {
93 if (thread == g_SigsuspendPreEnrolmentTarget && debugState == Thread::EventWait) {
94 g_SigsuspendUnexpectedWaits += 1;
96 }
97}
98
99class SigsuspendNonSignalEvent : public Event {
100 public:
101 SigsuspendNonSignalEvent()
102 : Event(reinterpret_cast<uintptr_t>(&sigsuspendNonSignalHandler), false) {}
103
104 size_t serialize(uint8_t*) override {
105 return 0;
106 }
107
108 size_t getNumber() override {
109 return 0x53555350;
110 }
111};
112
113bool waitForEventBlock(Thread* thread) {
114 const Time::Timestamp deadline = Time::getTicks() + (2 * Time::Multiplier::Second);
115 while (Time::getTicks() < deadline) {
116 Thread::WaitDebugInfo info = {};
117 uintptr_t debugAddress = 0;
118 if (thread->getWaitDebugInfo(info) && info.queue && info.queued &&
119 thread->getDebugState(debugAddress) == Thread::EventWait) {
120 return true;
121 }
123 }
124 return false;
125}
126
127bool waitForCounter(const Atomic<size_t>& counter, size_t value) {
128 const Time::Timestamp deadline = Time::getTicks() + (2 * Time::Multiplier::Second);
129 while (Time::getTicks() < deadline) {
130 if (counter == value) {
131 return true;
132 }
134 }
135 return counter == value;
136}
137
138bool waitForSuspensionState(Process* process, bool suspended) {
139 const Time::Timestamp deadline = Time::getTicks() + (2 * Time::Multiplier::Second);
140 while (Time::getTicks() < deadline) {
141 if (process->isSuspended() == suspended) {
142 return true;
143 }
145 }
146 return process->isSuspended() == suspended;
147}
148
149struct SigsuspendValidationContext {
150 SigsuspendValidationContext() : entered(0), returned(0), passed(false) {}
151
152 Atomic<size_t> entered;
153 Atomic<size_t> returned;
154 bool passed;
155};
156
157int sigsuspendValidationWorker(void* parameter) {
158 SigsuspendValidationContext* context = reinterpret_cast<SigsuspendValidationContext*>(parameter);
159 Thread* thread = Processor::information().getCurrentThread();
160 context->entered += 1;
161 thread->setSignalMask(OriginalSignalMask);
162 thread->clearInterruption();
163 g_SigsuspendSignalCalls = 0;
164 g_SigsuspendHandlerMask = 0;
165
166 SignalEvent pendingSignal(reinterpret_cast<uintptr_t>(&sigsuspendSignalHandler), TestSignal);
167 const bool signalQueued = thread->sendEvent(&pendingSignal);
168 const uintptr_t kernelStart = Processor::information().getVirtualAddressSpace().getKernelStart();
169
170 thread->setErrno(0);
171 const int wrongSize =
172 posix_rt_sigsuspend(reinterpret_cast<const uint64_t*>(kernelStart), sizeof(uint64_t) - 1);
173 const int wrongSizeError = thread->getErrno();
174 const bool wrongSizePreserved = thread->getSignalMask() == OriginalSignalMask &&
175 thread->hasEvent(&pendingSignal) && !g_SigsuspendSignalCalls;
176
177 thread->setErrno(0);
178 const int badAddress =
179 posix_rt_sigsuspend(reinterpret_cast<const uint64_t*>(kernelStart), sizeof(uint64_t));
180 const int badAddressError = thread->getErrno();
181 const bool badAddressPreserved = thread->getSignalMask() == OriginalSignalMask &&
182 thread->hasEvent(&pendingSignal) && !g_SigsuspendSignalCalls;
183
184 thread->setErrno(0);
185 const int nullAddress = posix_rt_sigsuspend(nullptr, sizeof(uint64_t));
186 const int nullAddressError = thread->getErrno();
187 const bool nullAddressPreserved = thread->getSignalMask() == OriginalSignalMask &&
188 thread->hasEvent(&pendingSignal) && !g_SigsuspendSignalCalls;
189
190 thread->setErrno(PreservedErrno);
191 const int result = posix_rt_sigsuspend(&RequestedSignalMask, sizeof(RequestedSignalMask));
192 const int error = thread->getErrno();
193 const uint64_t restoredMask = thread->getSignalMask();
194 const bool signalConsumed = !thread->hasEvent(&pendingSignal);
195 const bool interruptionConsumed = thread->getInterruptionReason() == Thread::NotInterrupted;
196
197 if (thread->hasEvent(&pendingSignal)) {
198 thread->cullEvent(&pendingSignal);
199 }
200 thread->setSignalMask(0);
201 thread->clearInterruption();
202
203 context->passed =
204 signalQueued && wrongSize == -1 && wrongSizeError == Error::InvalidArgument &&
205 wrongSizePreserved && badAddress == -1 && badAddressError == Error::BadAddress &&
206 badAddressPreserved && nullAddress == -1 && nullAddressError == Error::BadAddress &&
207 nullAddressPreserved && result == -1 && error == Error::Interrupted &&
208 restoredMask == OriginalSignalMask && signalConsumed && interruptionConsumed &&
209 g_SigsuspendSignalCalls == 1 && g_SigsuspendHandlerMask == (ActiveSignalMask | TestSignalBit);
210 context->returned += 1;
211 return context->passed ? 0 : 1;
212}
213
214bool sigsuspendValidationAndPrequeued(Process* kernelProcess) {
215 Process* process = new Process(kernelProcess);
216 process->setSubsystem(new PosixSubsystem);
217 SigsuspendValidationContext context;
218 Thread* worker =
219 new Thread(process, sigsuspendValidationWorker, &context, nullptr, false, true, true);
220 worker->setName("hosted rt_sigsuspend validation worker");
221 const bool started = worker->start();
222 const bool entered = started && waitForCounter(context.entered, 1);
223 const bool returned = entered && waitForCounter(context.returned, 1);
224 if (started && !context.returned) {
225 worker->setUnwindState(Thread::TerminateThread);
226 }
227 const bool joined = started && worker->joinForCompletion();
228 if (!started) {
229 delete worker;
230 }
231
232 const bool passed =
233 started && entered && returned && joined && context.returned == 1 && context.passed;
234 delete process;
235 if (!passed) {
236 ERROR(
237 "HOSTED-SYSCALL-TEST: FAIL rt-sigsuspend-validation: "
238 "Linux sigset validation, prequeued delivery, or restoration regressed");
239 return false;
240 }
241
242 NOTICE("HOSTED-SYSCALL-TEST: PASS rt-sigsuspend-validation");
243 return true;
244}
245
246struct SigsuspendWaitContext {
247 SigsuspendWaitContext() : entered(0), returned(0), result(-2), error(0), restoredMask(0) {}
248
249 Atomic<size_t> entered;
250 Atomic<size_t> returned;
251 int result;
252 int error;
253 uint64_t restoredMask;
254};
255
256int sigsuspendWaitWorker(void* parameter) {
257 SigsuspendWaitContext* context = reinterpret_cast<SigsuspendWaitContext*>(parameter);
258 Thread* thread = Processor::information().getCurrentThread();
259 thread->setSignalMask(OriginalSignalMask);
260 thread->clearInterruption();
261 context->entered += 1;
262 thread->setErrno(PreservedErrno);
263 context->result = posix_rt_sigsuspend(&RequestedSignalMask, sizeof(RequestedSignalMask));
264 context->error = thread->getErrno();
265 context->restoredMask = thread->getSignalMask();
266 thread->setSignalMask(0);
267 thread->clearInterruption();
268 context->returned += 1;
269 return 0;
270}
271
272bool sigsuspendClosesPreEnrolmentWindow(Process* kernelProcess) {
273 Process* process = new Process(kernelProcess);
274 process->setSubsystem(new PosixSubsystem);
275 g_SigsuspendSignalCalls = 0;
276 g_SigsuspendHandlerMask = 0;
277 g_SigsuspendPreEnrolmentCalls = 0;
278 g_SigsuspendPreEnrolmentQueued = 0;
279 g_SigsuspendUnexpectedWaits = 0;
280
281 SigsuspendWaitContext context;
282 Thread* worker = new Thread(process, sigsuspendWaitWorker, &context, nullptr, false, true, true);
283 worker->setName("hosted rt_sigsuspend pre-enrolment worker");
284 g_SigsuspendPreEnrolmentTarget = worker;
285 Thread::setSignalWaitPreEnrolmentHookForHostedTest(worker, sigsuspendPreEnrolmentHook);
286 WaitQueue::setBeforeBlockHook(sigsuspendUnexpectedWaitHook);
287
288 const bool started = worker->start();
289 const bool returned = started && waitForCounter(context.returned, 1);
290
291 Thread::setSignalWaitPreEnrolmentHookForHostedTest(nullptr, nullptr);
292 WaitQueue::setBeforeBlockHook(nullptr);
293 g_SigsuspendPreEnrolmentTarget = nullptr;
294 if (started && !context.returned) {
295 worker->setUnwindState(Thread::TerminateThread);
296 }
297 const bool joined = started && worker->joinForCompletion();
298 if (!started) {
299 delete worker;
300 }
301
302 const bool passed = started && returned && joined && context.result == -1 &&
303 context.error == Error::Interrupted &&
304 context.restoredMask == OriginalSignalMask &&
305 g_SigsuspendPreEnrolmentCalls == 1 && g_SigsuspendPreEnrolmentQueued == 1 &&
306 g_SigsuspendUnexpectedWaits == 0 && g_SigsuspendSignalCalls == 1 &&
307 g_SigsuspendHandlerMask == (ActiveSignalMask | TestSignalBit);
308 delete process;
309 if (!passed) {
310 ERROR(
311 "HOSTED-SYSCALL-TEST: FAIL rt-sigsuspend-pre-enrolment: "
312 "a caught signal was lost between predicate inspection and wait publication");
313 return false;
314 }
315
316 NOTICE("HOSTED-SYSCALL-TEST: PASS rt-sigsuspend-pre-enrolment");
317 return true;
318}
319
320void installDefaultSignalAction(PosixSubsystem* subsystem, size_t signal, uintptr_t handler) {
322 disposition->type = 1;
323 disposition->pEvent =
324 new SignalEvent(handler, signal, ~0UL, 0, true, false, Event::HandlerPrivilege::Kernel,
325 SignalEvent::DeliveryDisposition::DefaultAction);
326 subsystem->setSignalHandler(signal, disposition);
327}
328
329bool sigsuspendContinuesAfterDefaultActions(Process* kernelProcess) {
330 Process* process = new Process(kernelProcess);
331 PosixSubsystem* subsystem = new PosixSubsystem;
332 process->setSubsystem(subsystem);
333 installDefaultSignalAction(subsystem, SIGCHLD,
334 reinterpret_cast<uintptr_t>(&sigsuspendNonSignalHandler));
335 installDefaultSignalAction(subsystem, SIGTSTP,
336 reinterpret_cast<uintptr_t>(&sigsuspendDefaultStopHandler));
337 installDefaultSignalAction(subsystem, SIGCONT,
338 reinterpret_cast<uintptr_t>(&sigsuspendDefaultContinueHandler));
339
340 g_SigsuspendSignalCalls = 0;
341 g_SigsuspendHandlerMask = 0;
342 g_SigsuspendNonSignalCalls = 0;
343 g_SigsuspendDefaultStopCalls = 0;
344 g_SigsuspendDefaultContinueCalls = 0;
345 SigsuspendWaitContext context;
346 Thread* worker = new Thread(process, sigsuspendWaitWorker, &context, nullptr, false, true, true);
347 worker->setName("hosted rt_sigsuspend default-action worker");
348
349 const bool started = worker->start();
350 const bool entered = started && waitForCounter(context.entered, 1);
351 const bool initiallyBlocked = entered && waitForEventBlock(worker);
352 const PosixSubsystem::SignalDeliveryResult ignoredResult =
353 initiallyBlocked ? subsystem->queueSignalDelivery(worker, SIGCHLD)
354 : PosixSubsystem::SignalDeliveryResult::Unavailable;
355 const bool ignoredStayedBlocked =
356 ignoredResult == PosixSubsystem::SignalDeliveryResult::Ignored && !context.returned &&
357 waitForEventBlock(worker) && !g_SigsuspendNonSignalCalls;
358
359 const PosixSubsystem::SignalDeliveryResult stopResult =
360 ignoredStayedBlocked ? subsystem->queueSignalDelivery(worker, SIGTSTP)
361 : PosixSubsystem::SignalDeliveryResult::Unavailable;
362 const bool stopped = stopResult == PosixSubsystem::SignalDeliveryResult::Queued &&
363 waitForSuspensionState(process, true) &&
364 waitForCounter(g_SigsuspendDefaultStopCalls, 1) && !context.returned;
365
366 if (process->isSuspended()) {
367 process->resume();
368 }
369 const PosixSubsystem::SignalDeliveryResult continueResult =
370 stopped ? subsystem->queueSignalDelivery(worker, SIGCONT)
371 : PosixSubsystem::SignalDeliveryResult::Unavailable;
372 const bool continued = waitForSuspensionState(process, false);
373 const bool defaultHandlersReturned =
374 continueResult == PosixSubsystem::SignalDeliveryResult::Queued && continued &&
375 waitForCounter(g_SigsuspendDefaultContinueCalls, 1);
376 const bool blockedAgain =
377 defaultHandlersReturned && !context.returned && waitForEventBlock(worker);
378
379 SignalEvent caughtSignal(reinterpret_cast<uintptr_t>(&sigsuspendSignalHandler), TestSignal, ~0UL,
380 0, true, false, Event::HandlerPrivilege::Kernel,
381 SignalEvent::DeliveryDisposition::CaughtHandler);
382 const bool caughtQueued = blockedAgain && worker->sendEvent(&caughtSignal);
383 const bool returned = caughtQueued && waitForCounter(context.returned, 1);
384 if (process->isSuspended()) {
385 process->resume();
386 }
387 if (started && !context.returned) {
388 worker->setUnwindState(Thread::TerminateThread);
389 process->resume();
390 }
391 const bool joined = started && worker->joinForCompletion();
392 process->resume();
393 if (!started) {
394 delete worker;
395 }
396
397 const bool passed = started && initiallyBlocked && ignoredStayedBlocked && stopped &&
398 defaultHandlersReturned && blockedAgain && caughtQueued && returned &&
399 joined && context.result == -1 && context.error == Error::Interrupted &&
400 context.restoredMask == OriginalSignalMask &&
401 g_SigsuspendDefaultStopCalls == 1 && g_SigsuspendDefaultContinueCalls == 1 &&
402 g_SigsuspendSignalCalls == 1;
403 delete process;
404 if (!passed) {
405 ERROR(
406 "HOSTED-SYSCALL-TEST: FAIL rt-sigsuspend-default-actions: "
407 "an ignored, stop, or continue default action ended the signal wait");
408 return false;
409 }
410
411 NOTICE("HOSTED-SYSCALL-TEST: PASS rt-sigsuspend-default-actions");
412 return true;
413}
414
415bool sigsuspendIgnoresNonSignalWake(Process* kernelProcess) {
416 Process* process = new Process(kernelProcess);
417 process->setSubsystem(new PosixSubsystem);
418 g_SigsuspendSignalCalls = 0;
419 g_SigsuspendNonSignalCalls = 0;
420 g_SigsuspendHandlerMask = 0;
421
422 SigsuspendWaitContext context;
423 Thread* worker = new Thread(process, sigsuspendWaitWorker, &context, nullptr, false, true, true);
424 worker->setName("hosted rt_sigsuspend non-signal wake worker");
425 SigsuspendNonSignalEvent nonSignal;
426 SignalEvent signal(reinterpret_cast<uintptr_t>(&sigsuspendSignalHandler), TestSignal);
427
428 const bool started = worker->start();
429 const bool entered = started && waitForCounter(context.entered, 1);
430 const bool initiallyBlocked = entered && waitForEventBlock(worker);
431 const bool activeMaskObserved = initiallyBlocked && worker->getSignalMask() == ActiveSignalMask;
432 const bool nonSignalQueued = initiallyBlocked && worker->sendEvent(&nonSignal);
433 const bool nonSignalHandled = nonSignalQueued && waitForCounter(g_SigsuspendNonSignalCalls, 1);
434 const bool stayedInCall = nonSignalHandled && !context.returned;
435 const bool blockedAgain = stayedInCall && waitForEventBlock(worker);
436 const bool signalQueued = blockedAgain && worker->sendEvent(&signal);
437
438 const Time::Timestamp returnDeadline = Time::getTicks() + (2 * Time::Multiplier::Second);
439 while (started && !context.returned && Time::getTicks() < returnDeadline) {
441 }
442 if (started && !context.returned) {
443 worker->setUnwindState(Thread::TerminateThread);
444 }
445 const bool joined = started && worker->joinForCompletion();
446 if (!started) {
447 delete worker;
448 }
449
450 const bool passed =
451 started && initiallyBlocked && activeMaskObserved && nonSignalQueued && nonSignalHandled &&
452 stayedInCall && blockedAgain && signalQueued && joined && context.returned == 1 &&
453 context.result == -1 && context.error == Error::Interrupted &&
454 context.restoredMask == OriginalSignalMask && g_SigsuspendNonSignalCalls == 1 &&
455 g_SigsuspendSignalCalls == 1 && g_SigsuspendHandlerMask == (ActiveSignalMask | TestSignalBit);
456 delete process;
457 if (!passed) {
458 ERROR(
459 "HOSTED-SYSCALL-TEST: FAIL rt-sigsuspend-nonsignal: "
460 "a non-signal wake returned early or signal restoration regressed");
461 return false;
462 }
463
464 NOTICE("HOSTED-SYSCALL-TEST: PASS rt-sigsuspend-nonsignal");
465 return true;
466}
467} // namespace
468
469bool runHostedRtSigsuspendRegressions(Process* process) {
470 return sigsuspendValidationAndPrequeued(process) && sigsuspendClosesPreEnrolmentWindow(process) &&
471 sigsuspendContinuesAfterDefaultActions(process) && sigsuspendIgnoresNonSignalWake(process);
472}
Definition Event.h:49
virtual size_t getNumber()=0
virtual size_t serialize(uint8_t *pBuffer)=0
void checkEventState(uintptr_t userStack)
SignalDeliveryResult queueSignalDelivery(Thread *target, size_t sig, uint32_t *flags=nullptr, int32_t signalCode=0, bool processDirected=false, uint64_t signalValue=0, const SharedPointer< SignalEventState > &state=SharedPointer< SignalEventState >())
void setSignalHandler(size_t sig, SignalHandler *handler)
void suspend(int stopSignal=0)
Definition Process.cc:1864
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
uint64_t getSignalMask()
Definition Thread.cc:2002
bool getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3184
bool hasEvent(Event *pEvent)
Definition Thread.cc:2639
size_t getErrno()
Definition Thread.h:473
void cullEvent(Event *pEvent)
Definition Thread.cc:2193
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:570
Process * getParent() const
Definition Thread.h:338
void setSignalMask(uint64_t mask)
Definition Thread.cc:2007
class PerProcessorScheduler * getScheduler() const
Definition Thread.h:925
bool sendEvent(Event *pEvent)
Definition Thread.cc:1158
int type
Type - 0 = normal, 1 = SIG_DFL, 2 = SIG_IGN.
SignalEvent * pEvent
Event for the signal handler.