The Pedigree Project 0.1
ppoll-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#include "pedigree/kernel/processor/VirtualAddressSpace.h"
17#include "pedigree/kernel/time/Time.h"
18
19#include <fcntl.h>
20#include <poll.h>
21#include <signal.h>
22#include <stddef.h>
23#include <stdint.h>
24
25#include "modules/subsys/posix/FileDescriptor.h"
26#include "modules/subsys/posix/PosixSubsystem.h"
27#include "modules/subsys/posix/linux-wait-abi.h"
28#include "modules/subsys/posix/poll-syscalls.h"
29#include "modules/system/vfs/Pipe.h"
30
31static_assert(sizeof(LinuxKernelTimespec) == 16);
32static_assert(offsetof(LinuxKernelTimespec, tv_sec) == 0);
33static_assert(offsetof(LinuxKernelTimespec, tv_nsec) == 8);
34
35namespace {
36constexpr size_t TestSignal = 10;
37constexpr int PreservedErrno = 123;
38constexpr uint64_t TestSignalBit = static_cast<uint64_t>(1) << (TestSignal - 1);
39constexpr uint64_t PreservedSignalBit = static_cast<uint64_t>(1) << (12 - 1);
40constexpr uint64_t UnblockableSignalBits =
41 (static_cast<uint64_t>(1) << (SIGKILL - 1)) | (static_cast<uint64_t>(1) << (SIGSTOP - 1));
42constexpr uint64_t OriginalSignalMask = TestSignalBit | PreservedSignalBit;
43constexpr uint64_t RequestedSignalMask = PreservedSignalBit | UnblockableSignalBits;
44constexpr uint64_t ActiveSignalMask = PreservedSignalBit;
45
46Atomic<size_t> g_PpollSignalHandlerCalls(0);
47Atomic<size_t> g_PpollSignalHandlerWrites(0);
48FileDescriptor* g_PpollSignalWriter = nullptr;
49
50void ppollSignalHandler(size_t) {
51 g_PpollSignalHandlerCalls += 1;
52 if (g_PpollSignalWriter) {
53 char value = 'r';
54 if (g_PpollSignalWriter->write(1, reinterpret_cast<uintptr_t>(&value), true) == 1) {
55 g_PpollSignalHandlerWrites += 1;
56 }
57 }
58}
59
60bool closeDescriptor(PosixSubsystem* subsystem, size_t fd) {
61 DescriptorLease descriptor;
62 if (!subsystem->acquireFileDescriptor(fd, descriptor)) {
63 return false;
64 }
65 const bool closed = subsystem->closeFileDescriptor(fd, descriptor);
66 descriptor.reset();
67 return closed;
68}
69
70bool waitForPpollBlock(Thread* thread) {
71 const Time::Timestamp deadline = Time::getTicks() + (2 * Time::Multiplier::Second);
72 while (Time::getTicks() < deadline) {
73 Thread::WaitDebugInfo info = {};
74 uintptr_t debugAddress = 0;
75 if (thread->getWaitDebugInfo(info) && info.queue && info.queued &&
76 thread->getDebugState(debugAddress) == Thread::SemWait) {
77 return true;
78 }
80 }
81 return false;
82}
83
84struct PpollValidationContext {
85 explicit PpollValidationContext(size_t readFd) : readFd(readFd), passed(false), returned(0) {}
86
87 size_t readFd;
88 bool passed;
89 Atomic<size_t> returned;
90};
91
92int ppollValidationWorker(void* parameter) {
93 PpollValidationContext* context = reinterpret_cast<PpollValidationContext*>(parameter);
94 Thread* thread = Processor::information().getCurrentThread();
95 bool passed = true;
96
97 LinuxKernelTimespec zero = {0, 0};
98 thread->setErrno(PreservedErrno);
99 const int zeroResult = posix_ppoll(nullptr, 0, &zero, nullptr, 37);
100 passed = passed && zeroResult == 0 && thread->getErrno() == PreservedErrno;
101
102 LinuxKernelTimespec oneNanosecond = {0, 1};
103 thread->setErrno(PreservedErrno);
104 const int oneNanosecondResult = posix_ppoll(nullptr, 0, &oneNanosecond, nullptr, 0);
105 passed = passed && oneNanosecondResult == 0 && !oneNanosecond.tv_sec && !oneNanosecond.tv_nsec &&
106 thread->getErrno() == PreservedErrno;
107
108 LinuxKernelTimespec negativeSeconds = {-1, 0};
109 thread->setErrno(0);
110 const int negativeSecondsResult = posix_ppoll(nullptr, 0, &negativeSeconds, nullptr, 0);
111 passed = passed && negativeSecondsResult == -1 && thread->getErrno() == Error::InvalidArgument;
112
113 LinuxKernelTimespec negativeNanoseconds = {0, -1};
114 thread->setErrno(0);
115 const int negativeNanosecondsResult = posix_ppoll(nullptr, 0, &negativeNanoseconds, nullptr, 0);
116 passed =
117 passed && negativeNanosecondsResult == -1 && thread->getErrno() == Error::InvalidArgument;
118
119 LinuxKernelTimespec excessiveNanoseconds = {0, 1000000000};
120 thread->setErrno(0);
121 const int excessiveNanosecondsResult = posix_ppoll(nullptr, 0, &excessiveNanoseconds, nullptr, 0);
122 passed =
123 passed && excessiveNanosecondsResult == -1 && thread->getErrno() == Error::InvalidArgument;
124
125 uint64_t signalMask = 0;
126 zero = {0, 0};
127 thread->setErrno(0);
128 const int wrongMaskSizeResult =
129 posix_ppoll(nullptr, 0, &zero, &signalMask, sizeof(signalMask) - 1);
130 passed = passed && wrongMaskSizeResult == -1 && thread->getErrno() == Error::InvalidArgument;
131
132 const uintptr_t kernelStart = Processor::information().getVirtualAddressSpace().getKernelStart();
133 thread->setErrno(0);
134 const int badTimeoutResult =
135 posix_ppoll(nullptr, 0, reinterpret_cast<LinuxKernelTimespec*>(kernelStart), nullptr, 0);
136 passed = passed && badTimeoutResult == -1 && thread->getErrno() == Error::BadAddress;
137
138 zero = {0, 0};
139 thread->setErrno(0);
140 const int excessiveDescriptorsResult = posix_ppoll(nullptr, 16385, &zero, nullptr, 0);
141 passed =
142 passed && excessiveDescriptorsResult == -1 && thread->getErrno() == Error::InvalidArgument;
143
144 struct pollfd ready = {static_cast<int>(context->readFd), POLLIN, 0};
145 thread->setErrno(PreservedErrno);
146 const int readyResult = posix_ppoll(&ready, 1, nullptr, nullptr, 91);
147 passed = passed && readyResult == 1 && (ready.revents & POLLIN) &&
148 thread->getErrno() == PreservedErrno;
149
150 LinuxKernelTimespec saturatedTimeout = {INT64_MAX, 999999999};
151 struct pollfd saturatedReady = {static_cast<int>(context->readFd), POLLIN, 0};
152 thread->setErrno(PreservedErrno);
153 const int saturatedResult = posix_ppoll(&saturatedReady, 1, &saturatedTimeout, nullptr, 0);
154 passed = passed && saturatedResult == 1 && (saturatedReady.revents & POLLIN) &&
155 saturatedTimeout.tv_sec >= 0 && saturatedTimeout.tv_nsec >= 0 &&
156 saturatedTimeout.tv_nsec < 1000000000 && thread->getErrno() == PreservedErrno;
157
158 const uint64_t previousMask = thread->getSignalMask();
159 const uint64_t blockedMask = previousMask | TestSignalBit;
160 const uint64_t temporaryMask = blockedMask & ~TestSignalBit;
161 thread->setSignalMask(blockedMask);
162 thread->clearInterruption();
163
164 LinuxKernelTimespec badInputTimeout = {10, 0};
165 thread->setErrno(0);
166 const int badInputResult = posix_ppoll(reinterpret_cast<struct pollfd*>(kernelStart), 1,
167 &badInputTimeout, &temporaryMask, sizeof(temporaryMask));
168 const int badInputError = thread->getErrno();
169 const bool badInputMaskRestored = thread->getSignalMask() == blockedMask;
170 const bool validErrorRemainder = badInputTimeout.tv_sec >= 0 && badInputTimeout.tv_sec <= 10 &&
171 badInputTimeout.tv_nsec >= 0 &&
172 badInputTimeout.tv_nsec < 1000000000 &&
173 (badInputTimeout.tv_sec < 10 || !badInputTimeout.tv_nsec);
174
175 g_PpollSignalHandlerCalls = 0;
176 g_PpollSignalWriter = nullptr;
177 SignalEvent pendingSignal(reinterpret_cast<uintptr_t>(&ppollSignalHandler), TestSignal);
178 const bool signalQueued = thread->sendEvent(&pendingSignal);
179
180 zero = {0, 0};
181 thread->setErrno(0);
182 const int badMaskBeforeArmResult = posix_ppoll(
183 nullptr, 0, &zero, reinterpret_cast<const uint64_t*>(kernelStart), sizeof(signalMask));
184 const bool maskRestoredBeforeReturn = thread->getSignalMask() == blockedMask;
185 const bool signalStillPending = thread->hasEvent(&pendingSignal);
186 const bool signalStayedBlocked = !g_PpollSignalHandlerCalls;
187 if (signalStillPending) {
188 thread->cullEvent(&pendingSignal);
189 }
190 thread->setSignalMask(previousMask);
191 thread->clearInterruption();
192
193 passed = passed && signalQueued && badInputResult == -1 && badInputError == Error::BadAddress &&
194 badInputMaskRestored && validErrorRemainder && badMaskBeforeArmResult == -1 &&
195 thread->getErrno() == Error::BadAddress && maskRestoredBeforeReturn &&
196 signalStillPending && signalStayedBlocked;
197 context->passed = passed;
198 context->returned += 1;
199 return passed ? 0 : 1;
200}
201
202bool ppollValidationAndImmediateReadiness(Process* kernelProcess) {
203 constexpr size_t ReadDescriptor = 89;
204 constexpr size_t WriteDescriptor = 90;
205
206 Process* process = new Process(kernelProcess);
207 PosixSubsystem* subsystem = new PosixSubsystem;
208 process->setSubsystem(subsystem);
209 Pipe* pipe = new Pipe;
210 FileDescriptor* reader = new FileDescriptor(pipe, 0, ReadDescriptor, 0, O_RDONLY);
211 FileDescriptor* writer = new FileDescriptor(pipe, 0, WriteDescriptor, 0, O_WRONLY);
212 subsystem->addFileDescriptor(ReadDescriptor, reader);
213 subsystem->addFileDescriptor(WriteDescriptor, writer);
214
215 char value = 'v';
216 const bool madeReady = writer->write(1, reinterpret_cast<uintptr_t>(&value), true) == 1;
217 PpollValidationContext context(ReadDescriptor);
218 Thread* worker = new Thread(process, ppollValidationWorker, &context, nullptr, false, true, true);
219 worker->setName("hosted ppoll validation worker");
220 const bool started = madeReady && worker->start();
221 const bool joined = started && worker->joinForCompletion();
222 if (!started) {
223 delete worker;
224 }
225
226 const bool writerClosed = closeDescriptor(subsystem, WriteDescriptor);
227 const bool readerClosed = closeDescriptor(subsystem, ReadDescriptor);
228 const bool passed =
229 started && joined && context.returned == 1 && context.passed && writerClosed && readerClosed;
230 delete process;
231
232 if (!passed) {
233 ERROR(
234 "HOSTED-SYSCALL-TEST: FAIL ppoll-validation-immediate: "
235 "Linux timeout, sigset, input-copy, or immediate-readiness semantics regressed");
236 return false;
237 }
238
239 NOTICE("HOSTED-SYSCALL-TEST: PASS ppoll-validation-immediate");
240 return true;
241}
242
243struct PpollSignalContext {
244 explicit PpollSignalContext(size_t readFd)
245 : readFd(readFd), entered(0), returned(0), result(-2), error(0), events(0), restoredMask(0) {}
246
247 size_t readFd;
248 Atomic<size_t> entered;
249 Atomic<size_t> returned;
250 int result;
251 int error;
252 short events;
253 uint64_t restoredMask;
254};
255
256int ppollSignalWorker(void* parameter) {
257 PpollSignalContext* context = reinterpret_cast<PpollSignalContext*>(parameter);
258 Thread* thread = Processor::information().getCurrentThread();
259 thread->setSignalMask(OriginalSignalMask);
260 thread->clearInterruption();
261
262 struct pollfd descriptor = {static_cast<int>(context->readFd), POLLIN, 0};
263 context->entered += 1;
264 thread->setErrno(PreservedErrno);
265 context->result =
266 posix_ppoll(&descriptor, 1, nullptr, &RequestedSignalMask, sizeof(RequestedSignalMask));
267 context->error = thread->getErrno();
268 context->events = descriptor.revents;
269 context->restoredMask = thread->getSignalMask();
270 thread->setSignalMask(0);
271 thread->clearInterruption();
272 context->returned += 1;
273 return 0;
274}
275
276bool ppollSignalRace(Process* kernelProcess, bool readyWins) {
277 constexpr size_t ReadDescriptor = 91;
278 constexpr size_t WriteDescriptor = 92;
279
280 Process* process = new Process(kernelProcess);
281 PosixSubsystem* subsystem = new PosixSubsystem;
282 process->setSubsystem(subsystem);
283 Pipe* pipe = new Pipe;
284 FileDescriptor* reader = new FileDescriptor(pipe, 0, ReadDescriptor, 0, O_RDONLY);
285 FileDescriptor* writer = new FileDescriptor(pipe, 0, WriteDescriptor, 0, O_WRONLY);
286 subsystem->addFileDescriptor(ReadDescriptor, reader);
287 subsystem->addFileDescriptor(WriteDescriptor, writer);
288
289 g_PpollSignalHandlerCalls = 0;
290 g_PpollSignalHandlerWrites = 0;
291 g_PpollSignalWriter = readyWins ? writer : nullptr;
292 PpollSignalContext context(ReadDescriptor);
293 Thread* worker = new Thread(process, ppollSignalWorker, &context, nullptr, false, true, true);
294 if (readyWins) {
295 worker->setName("hosted ppoll ready-signal worker");
296 } else {
297 worker->setName("hosted ppoll EINTR worker");
298 }
299 const bool started = worker->start();
300 while (started && !context.entered) {
302 }
303 const bool blocked = started && waitForPpollBlock(worker);
304 const bool activeMaskObserved = blocked && worker->getSignalMask() == ActiveSignalMask;
305
306 SignalEvent* signal = new SignalEvent(reinterpret_cast<uintptr_t>(&ppollSignalHandler),
307 TestSignal, ~0UL, 0, true, true);
308 const bool signalQueued = blocked && worker->sendEvent(signal);
309 if (!signalQueued) {
310 delete signal;
311 }
312
313 bool rescueWrite = false;
314 if (!blocked && started && !context.returned) {
315 char rescue = 'x';
316 rescueWrite = writer->write(1, reinterpret_cast<uintptr_t>(&rescue), true) == 1;
317 }
318 const Time::Timestamp returnDeadline = Time::getTicks() + (2 * Time::Multiplier::Second);
319 while (started && !context.returned && Time::getTicks() < returnDeadline) {
321 }
322 if (started && !context.returned) {
323 char rescue = 'x';
324 rescueWrite = writer->write(1, reinterpret_cast<uintptr_t>(&rescue), true) == 1 || rescueWrite;
325 }
326 const bool joined = started && worker->joinForCompletion();
327 if (!started) {
328 delete worker;
329 }
330 g_PpollSignalWriter = nullptr;
331
332 const bool resultPassed =
333 readyWins
334 ? context.result == 1 && (context.events & POLLIN) && g_PpollSignalHandlerWrites == 1
335 : context.result == -1 && context.error == Error::Interrupted && !context.events &&
336 !g_PpollSignalHandlerWrites;
337 bool passed = started && blocked && activeMaskObserved && signalQueued && !rescueWrite &&
338 joined && context.returned == 1 && resultPassed &&
339 context.restoredMask == OriginalSignalMask && g_PpollSignalHandlerCalls == 1;
340
341 const bool writerClosed = closeDescriptor(subsystem, WriteDescriptor);
342 const bool readerClosed = closeDescriptor(subsystem, ReadDescriptor);
343 passed = passed && writerClosed && readerClosed;
344 delete process;
345
346 if (!passed) {
347 ERROR("HOSTED-SYSCALL-TEST: FAIL "
348 << (readyWins ? "ppoll-ready-beats-signal: " : "ppoll-eintr-mask: ")
349 << "temporary masking, restoration, or ready-vs-signal precedence regressed");
350 return false;
351 }
352
353 NOTICE("HOSTED-SYSCALL-TEST: PASS "
354 << (readyWins ? "ppoll-ready-beats-signal" : "ppoll-eintr-mask"));
355 return true;
356}
357} // namespace
358
359bool runHostedPpollRegressions(Process* process) {
360 return ppollValidationAndImmediateReadiness(process) && ppollSignalRace(process, false) &&
361 ppollSignalRace(process, true);
362}
uint64_t write(uint64_t size, uintptr_t buffer, bool canBlock=true)
Definition Pipe.h:36
bool acquireFileDescriptor(size_t fd, DescriptorLease &descriptor)
bool closeFileDescriptor(size_t fd, const DescriptorLease &descriptor)
void addFileDescriptor(size_t fd, FileDescriptor *pFd)
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
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
void setSignalMask(uint64_t mask)
Definition Thread.cc:2007
bool sendEvent(Event *pEvent)
Definition Thread.cc:1158
Definition waits.c:9