The Pedigree Project 0.1
static-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 "modules/Module.h"
9#include "modules/subsys/pedigree-c/pedigreecSyscallNumbers.h"
10#include "modules/subsys/posix/FileDescriptor.h"
11#include "modules/subsys/posix/PosixProcess.h"
12#include "modules/subsys/posix/PosixSubsystem.h"
13#include "modules/subsys/posix/ResolvedPath.h"
14#include "modules/subsys/posix/UnixFilesystem.h"
15#include "modules/subsys/posix/epoll-syscalls.h"
16#include "modules/subsys/posix/eventfd-syscalls.h"
17#include "modules/subsys/posix/file-syscalls.h"
18#include "modules/subsys/posix/net-syscalls.h"
19#include "modules/subsys/posix/poll-syscalls.h"
20#include "modules/subsys/posix/select-syscalls.h"
21#include "modules/subsys/posix/system-syscalls.h"
22#include "modules/system/vfs/Directory.h"
23#include "modules/system/vfs/File.h"
25#include "modules/system/vfs/MountView.h"
26#include "modules/system/vfs/Pipe.h"
27#include "modules/system/vfs/VFS.h"
28#undef PEDIGREE_INIT_SIGRET
29#undef PEDIGREE_SIGRET
30#include "pedigree/kernel/Atomic.h"
31#include "pedigree/kernel/Log.h"
32#include "pedigree/kernel/errors.h"
33#include "pedigree/kernel/linker/KernelElf.h"
34#include "pedigree/kernel/panic.h"
35#include "pedigree/kernel/process/Process.h"
36#include "pedigree/kernel/process/Scheduler.h"
37#include "pedigree/kernel/process/Semaphore.h"
38#include "pedigree/kernel/process/TerminationDeferral.h"
39#include "pedigree/kernel/process/Thread.h"
40#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
41#include "pedigree/kernel/processor/Processor.h"
42#include "pedigree/kernel/processor/ProcessorInformation.h"
43#include "pedigree/kernel/processor/SyscallManager.h"
44#include "pedigree/kernel/processor/hosted/smoke.h"
45#include "pedigree/kernel/utilities/StringView.h"
46#include "pedigree/kernel/utilities/utility.h"
47
48#include <fcntl.h>
49#include <limits.h>
50#include <sched.h>
51
52#include "modules/subsys/posix/syscalls/posixSyscallNumbers.h"
53
54extern void system_reset();
55extern bool hostedRunSyscallProfile();
56extern "C" bool posixDuplicateInitRollbackPreservesProcessForTest(Process* processIdentity);
57extern "C" void posixSetCloneBeforeStartHookForTest(void (*hook)(Thread*, size_t, void*),
58 void* context);
59extern "C" unsigned int posixSelectProjectionForTest(short revents, bool checkRead, bool checkWrite,
60 bool checkExceptional);
61extern bool runHostedAccessSyscallRegressions(Process* process);
62extern bool runHostedAdvisoryLockRegressions(Process* process);
63extern bool runHostedChildResourceRegressions(Process* process);
64extern bool runHostedCloneRoutingRegressions(Process* process);
65extern bool runHostedDup3Regressions(Process* process);
66extern bool runHostedEventFdRegressions(Process* process);
67extern bool runHostedFileContractRegressions(Process* process);
68extern bool runHostedFutexRobustRegressions(Process* process);
69extern bool runHostedSystemUsercopyRegressions(Process* process);
70extern bool runHostedVmPermissionRegressions();
71extern bool runHostedProcessQueryRegressions(Process* process);
72extern bool runHostedTermiosSyscallRegressions(Process* process);
73extern bool runHostedInotifyRegressions(Process* process);
74extern bool runHostedMmapPlacementRegressions(Process* process);
75extern bool runHostedPosixExitStatusRegressions(Process* process);
76extern bool runHostedPpollRegressions(Process* process);
77extern bool runHostedPositionalIoRegressions(Process* process);
78extern bool runHostedPositionalVectorIoRegressions(Process* process);
79extern bool runHostedPselectRegressions(Process* process);
80extern bool runHostedResourceSyscallRegressions(Process* process);
81extern bool runHostedRtSigsuspendRegressions(Process* process);
82extern bool runHostedScalarIoRegressions(Process* process);
83extern bool runHostedScmRightsRegressions(Process* process);
84extern bool runHostedScmStreamRegressions(Process* process);
85extern bool runHostedSleepClockSyscallRegressions(Process* process);
86extern bool runHostedThreadSignalSyscallRegressions(Process* process);
87extern bool runHostedTimeSyscallRegressions(Process* process);
88extern bool runHostedUnixStreamInterruptionRegressions(Process* process);
89extern bool runHostedUsercopyRegressions(Process* process);
90extern bool runHostedVectorIoRegressions(Process* process);
91
92namespace {
93constexpr size_t HostedAttempts = 10000;
94constexpr int PollCloseReuseTimeoutMilliseconds = 5000;
95constexpr uint64_t EpollInitialData = 0x1111222233334444ULL;
96constexpr uint64_t EpollLevelData = 0x3333444455556666ULL;
97constexpr uint64_t EpollOneShotData = 0x5555666677778888ULL;
98constexpr uint64_t EpollRearmedData = 0x9999AAAABBBBCCCCULL;
99constexpr uint64_t EpollAliasData = 0xDDDDEEEEFFFF0001ULL;
100constexpr uint64_t EpollEventFdData = 0x123456789ABCDEF0ULL;
101constexpr uint64_t EpollReorderedData = 0x0DDBA11C0FFEE123ULL;
102constexpr uint64_t EpollFifoData = 0xF1F0C105ED6E0001ULL;
103size_t g_RuntimePinnedLifecycleCalls = 0;
104
105struct TerminalBlockedHandlerContext {
106 TerminalBlockedHandlerContext()
107 : blocker(0, true),
108 thread(nullptr),
109 hookEntered(0),
110 exitStaged(0),
111 releasedByTermination(0),
112 unexpectedRelease(0),
113 syscallReturned(0) {}
114
115 Semaphore blocker;
116 Thread* thread;
117 Atomic<size_t> hookEntered;
118 Atomic<size_t> exitStaged;
119 Atomic<size_t> releasedByTermination;
120 Atomic<size_t> unexpectedRelease;
121 Atomic<size_t> syscallReturned;
122};
123
124TerminalBlockedHandlerContext* g_TerminalBlockedHandlerContext = nullptr;
125
126int terminalCreatedFixtureEntry(void*) {
127 FATAL("HOSTED-SYSCALL-TEST: FAIL posix-terminal-drain-created-entry-ran");
128 return 0;
129}
130
131void terminalBlockedHandlerPin(Service_t service, SyscallHandler*) {
132 TerminalBlockedHandlerContext* context = g_TerminalBlockedHandlerContext;
133 Thread* current = Processor::information().getCurrentThread();
134 if (!context || service != posix || current != context->thread) {
135 return;
136 }
137
138 context->hookEntered += 1;
139 if (SyscallManager::instance().requestProcessExit(73)) {
140 context->exitStaged += 1;
141 }
142
143 const bool acquired = context->blocker.acquire();
144 if (!acquired && current->getUnwindState() == Thread::TerminateThread) {
145 context->releasedByTermination += 1;
146 } else {
147 context->unexpectedRelease += 1;
148 }
149}
150
151int terminalBlockedHandlerEntry(void* parameter) {
152 TerminalBlockedHandlerContext* context =
153 reinterpret_cast<TerminalBlockedHandlerContext*>(parameter);
154 SyscallManager::instance().syscall(posix, POSIX_GETPID);
155 context->syscallReturned += 1;
156 return 1;
157}
158
159void runtimePinnedLifecycleProbe() {
160 ++g_RuntimePinnedLifecycleCalls;
161}
162
163class DescriptorRetirementProbe : public FileDescriptor {
164 public:
165 explicit DescriptorRetirementProbe(Atomic<size_t>& destructions)
166 : FileDescriptor(), m_Destructions(destructions) {}
167
168 ~DescriptorRetirementProbe() override {
169 m_Destructions += 1;
170 }
171
172 private:
173 Atomic<size_t>& m_Destructions;
174};
175
176class EstablishedAliasFileProbe : public File {
177 public:
178 explicit EstablishedAliasFileProbe(Atomic<size_t>& destructions)
179 : File(), m_Destructions(destructions) {}
180
181 ~EstablishedAliasFileProbe() override {
182 m_Destructions += 1;
183 }
184
185 private:
186 Atomic<size_t>& m_Destructions;
187};
188
189void repairAliasFileProbe(EstablishedAliasFileProbe* file, Atomic<size_t>& destructions) {
190 if (!destructions) {
192 }
193 if (!destructions) {
194 delete file;
195 }
196}
197
198class RetainedLookupDirectory;
199
200class RetainedLookupFilesystem final : public Filesystem {
201 public:
202 using RemoveHook = bool (*)(File*, File*, void*);
203
204 RetainedLookupFilesystem()
205 : m_Root(nullptr),
206 m_Label("retained-lookup-test"),
207 m_RemoveHook(nullptr),
208 m_RemoveHookContext(nullptr) {}
209
210 void setRoot(File* root) {
211 m_Root = root;
212 }
213
214 void setRemoveHook(RemoveHook hook, void* context) {
215 m_RemoveHook = hook;
216 m_RemoveHookContext = context;
217 }
218
219 bool initialise(Disk*) override {
220 return true;
221 }
222
223 File* getRoot() const override {
224 return m_Root;
225 }
226
227 const String& getVolumeLabel() const override {
228 return m_Label;
229 }
230
231 protected:
232 bool createFile(File*, const String&, uint32_t) override {
233 return false;
234 }
235
236 bool createDirectory(File*, const String&, uint32_t) override {
237 return false;
238 }
239
240 bool createSymlink(File*, const String&, const String&) override {
241 return false;
242 }
243
244 bool removeNode(File* parent, const String&, File* file) override {
245 return m_RemoveHook ? m_RemoveHook(parent, file, m_RemoveHookContext) : false;
246 }
247
248 private:
249 File* m_Root;
250 String m_Label;
251 RemoveHook m_RemoveHook;
252 void* m_RemoveHookContext;
253};
254
255class RetainedLookupFile final : public File {
256 public:
257 RetainedLookupFile(const String& name, Filesystem* filesystem, File* parent,
258 Atomic<size_t>& destructions, RetainedLookupDirectory* lockProbe = nullptr,
259 Atomic<size_t>* lockAvailable = nullptr)
260 : File(name, 0, 0, 0, 0, filesystem, 0, parent),
261 m_Destructions(destructions),
262 m_LockProbe(lockProbe),
263 m_LockAvailable(lockAvailable) {}
264
265 ~RetainedLookupFile() override;
266
267 private:
268 Atomic<size_t>& m_Destructions;
269 RetainedLookupDirectory* m_LockProbe;
270 Atomic<size_t>* m_LockAvailable;
271};
272
273class RetainedLookupDirectory final : public Directory {
274 public:
275 RetainedLookupDirectory(const String& name, Filesystem* filesystem,
276 Atomic<size_t>* destructions = nullptr)
277 : Directory(name, 0, 0, 0, 0, filesystem, 0, nullptr),
278 m_LazyTarget(nullptr),
279 m_Conversions(nullptr),
280 m_FirstConversionEntered(nullptr),
281 m_FirstConversionRelease(nullptr),
282 m_SecondConversionEntered(nullptr),
283 m_SecondConversionRelease(nullptr),
284 m_Destructions(destructions) {}
285
286 ~RetainedLookupDirectory() override {
287 if (m_Destructions) {
288 *m_Destructions += 1;
289 }
290 }
291
292 void publish(const String& name, File* file) {
293 addDirectoryEntry(name, file);
294 }
295
296 bool removePublished(const String& name, File* expected) {
297 return removeDirectoryEntry(HashedStringView(name), expected);
298 }
299
300 const void* namespaceLockAddress() {
301 return static_cast<const void*>(&namespaceMutationLock());
302 }
303
304 bool publishEphemeral(File* file) {
305 return addEphemeralFile(file) == AddStatus::Added;
306 }
307
308 void publishLazy(const String& name, File* file, Atomic<size_t>& conversions,
309 Atomic<size_t>& firstEntered, Semaphore& firstRelease,
310 Atomic<size_t>& secondEntered, Semaphore& secondRelease) {
311 m_LazyTarget = file;
312 m_Conversions = &conversions;
313 m_FirstConversionEntered = &firstEntered;
314 m_FirstConversionRelease = &firstRelease;
315 m_SecondConversionEntered = &secondEntered;
316 m_SecondConversionRelease = &secondRelease;
317
318 DirectoryEntryMetadata metadata;
319 metadata.pDirectory = this;
320 metadata.filename = name;
321 addDirectoryEntry(name, pedigree_std::move(metadata));
322 }
323
324 void publishFailedLazy(const String& name, Atomic<size_t>& conversions) {
325 m_LazyTarget = nullptr;
326 m_Conversions = &conversions;
327 m_FirstConversionEntered = nullptr;
328 m_FirstConversionRelease = nullptr;
329 m_SecondConversionEntered = nullptr;
330 m_SecondConversionRelease = nullptr;
331
332 DirectoryEntryMetadata metadata;
333 metadata.pDirectory = this;
334 metadata.filename = name;
335 addDirectoryEntry(name, pedigree_std::move(metadata));
336 }
337
338 protected:
339 File* convertToFile(const DirectoryEntryMetadata&) override {
340 const size_t conversion = (*m_Conversions += 1);
341 Atomic<size_t>* entered = nullptr;
342 Semaphore* release = nullptr;
343 if (conversion == static_cast<size_t>(1)) {
344 entered = m_FirstConversionEntered;
345 release = m_FirstConversionRelease;
346 } else if (conversion == static_cast<size_t>(2)) {
347 entered = m_SecondConversionEntered;
348 release = m_SecondConversionRelease;
349 }
350 if (entered && release) {
351 *entered += 1;
352 const bool released = release->acquireForCompletion();
353 (void)released;
354 }
355 return m_LazyTarget;
356 }
357
358 private:
359 File* m_LazyTarget;
360 Atomic<size_t>* m_Conversions;
361 Atomic<size_t>* m_FirstConversionEntered;
362 Semaphore* m_FirstConversionRelease;
363 Atomic<size_t>* m_SecondConversionEntered;
364 Semaphore* m_SecondConversionRelease;
365 Atomic<size_t>* m_Destructions;
366};
367
368RetainedLookupFile::~RetainedLookupFile() {
369 if (m_LockProbe && m_LockAvailable && m_LockProbe->tryCacheLockForHostedTest()) {
370 *m_LockAvailable += 1;
371 }
372 m_Destructions += 1;
373}
374
375struct RetainedLookupHookContext {
376 RetainedLookupHookContext(Directory* directory, File* file, bool pauseBefore, bool pauseAfter)
377 : directory(directory),
378 file(file),
379 pauseBefore(pauseBefore),
380 pauseAfter(pauseAfter),
381 beforeRelease(0, false),
382 afterRelease(0, false),
383 beforeClaimed(0),
384 beforeEntered(0),
385 beforeReturned(0),
386 beforeNullFile(0),
387 afterClaimed(0),
388 afterEntered(0),
389 afterReturned(0) {}
390
391 Directory* directory;
392 File* file;
393 bool pauseBefore;
394 bool pauseAfter;
395 Semaphore beforeRelease;
396 Semaphore afterRelease;
397 Atomic<size_t> beforeClaimed;
398 Atomic<size_t> beforeEntered;
399 Atomic<size_t> beforeReturned;
400 Atomic<size_t> beforeNullFile;
401 Atomic<size_t> afterClaimed;
402 Atomic<size_t> afterEntered;
403 Atomic<size_t> afterReturned;
404};
405
406struct RetainedLookupWorkerContext {
407 RetainedLookupWorkerContext(Directory* directory, const String& name)
408 : directory(directory), name(name), child(nullptr), result(false), returned(0) {}
409
410 Directory* directory;
411 String name;
412 File* child;
413 bool result;
414 Atomic<size_t> returned;
415};
416
417struct DirectoryRemoveWorkerContext {
418 DirectoryRemoveWorkerContext(Directory* directory, const String& name)
419 : directory(directory), name(name), returned(0) {}
420
421 Directory* directory;
422 String name;
423 Atomic<size_t> returned;
424};
425
426struct RetainedLookupReplacementWorkerContext {
427 RetainedLookupReplacementWorkerContext(Directory* directory, const String& oldName,
428 const String& replacementName, File* oldFile,
429 File* replacementFile)
430 : directory(directory),
431 oldName(oldName),
432 replacementName(replacementName),
433 oldFile(oldFile),
434 replacementFile(replacementFile),
435 oldRetained(false),
436 missingPreserved(false),
437 replaced(false),
438 returned(0) {}
439
440 Directory* directory;
441 String oldName;
442 String replacementName;
443 File* oldFile;
444 File* replacementFile;
445 bool oldRetained;
446 bool missingPreserved;
447 bool replaced;
448 Atomic<size_t> returned;
449};
450
451Atomic<RetainedLookupHookContext*> g_RetainedLookupHookContext(nullptr);
452
453void pauseRetainedLookup(Directory* directory, File* file, Directory::RetainedLookupPhase phase) {
454 RetainedLookupHookContext* context = g_RetainedLookupHookContext;
455 if (!context || context->directory != directory) {
456 return;
457 }
458
459 const bool before = phase == Directory::RetainedLookupPhase::BeforeLookup;
460 if ((before && file) || (!before && context->file != file)) {
461 return;
462 }
463 const bool pause = before ? context->pauseBefore : context->pauseAfter;
464 Atomic<size_t>& claimed = before ? context->beforeClaimed : context->afterClaimed;
465 if (!pause || !claimed.compareAndSwap(0, 1)) {
466 return;
467 }
468
469 Atomic<size_t>& entered = before ? context->beforeEntered : context->afterEntered;
470 Atomic<size_t>& returned = before ? context->beforeReturned : context->afterReturned;
471 Semaphore& release = before ? context->beforeRelease : context->afterRelease;
472 if (before) {
473 context->beforeNullFile += 1;
474 }
475 entered += 1;
476 if (release.acquireForCompletion()) {
477 returned += 1;
478 }
479}
480
481int retainedLookupWorker(void* parameter) {
482 RetainedLookupWorkerContext* context = reinterpret_cast<RetainedLookupWorkerContext*>(parameter);
484 context->result = context->directory->lookupRetained(HashedStringView(context->name), child);
485 context->child = child.get();
486 child.reset();
487 context->returned += 1;
488 return context->result ? 0 : 1;
489}
490
491int directoryRemoveWorker(void* parameter) {
492 DirectoryRemoveWorkerContext* context =
493 reinterpret_cast<DirectoryRemoveWorkerContext*>(parameter);
494 context->directory->remove(HashedStringView(context->name));
495 context->returned += 1;
496 return 0;
497}
498
499int retainedLookupReplacementWorker(void* parameter) {
500 RetainedLookupReplacementWorkerContext* context =
501 reinterpret_cast<RetainedLookupReplacementWorkerContext*>(parameter);
503 context->oldRetained =
504 context->directory->lookupRetained(HashedStringView(context->oldName), child) &&
505 child.get() == context->oldFile;
506 if (context->oldRetained) {
507 context->directory->remove(HashedStringView(context->oldName));
508 context->missingPreserved =
509 !context->directory->lookupRetained(HashedStringView("retained-missing"), child) &&
510 child.get() == context->oldFile;
511 }
512 if (context->missingPreserved) {
513 context->replaced =
514 context->directory->lookupRetained(HashedStringView(context->replacementName), child) &&
515 child.get() == context->replacementFile;
516 }
517 child.reset();
518 context->returned += 1;
519 return context->oldRetained && context->missingPreserved && context->replaced ? 0 : 1;
520}
521
522bool waitForDirectoryLock(Thread* thread, const Directory& directory, Atomic<size_t>& returned) {
523 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
524 if (returned) {
525 return false;
526 }
527 Thread::WaitDebugInfo info = {};
528 if (thread->getWaitDebugInfo(info) && info.queue && info.queued &&
529 info.channelOwner == directory.cacheLockAddressForHostedTest() &&
530 thread->getStatus() == Thread::Sleeping) {
531 return true;
532 }
534 }
535 return false;
536}
537
538bool waitForRetainedLookupPause(Thread* thread, Atomic<size_t>& entered, Semaphore& release,
539 Atomic<size_t>& returned) {
540 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
541 if (returned) {
542 return false;
543 }
544 Thread::WaitDebugInfo info = {};
545 if (entered == static_cast<size_t>(1) && thread->getWaitDebugInfo(info) && info.queue &&
546 info.queued && info.channelOwner == &release && thread->getStatus() == Thread::Sleeping) {
547 return true;
548 }
550 }
551 return false;
552}
553
554size_t drainTrackedFileOwners(File* file, Atomic<size_t>& destructions) {
555 for (size_t release = 1; release <= 8 && !destructions; ++release) {
556 if (VFS::instance().untrackFile(file, false)) {
557 delete file;
558 return release;
559 }
560 }
561 return 0;
562}
563
564bool directoryRetainedLookupRemoval(Process* kernelProcess) {
565 RetainedLookupFilesystem filesystem;
566 RetainedLookupDirectory directory(String("retained-removal-root"), &filesystem);
567 filesystem.setRoot(&directory);
568 Atomic<size_t> destructions(0);
569 const String alias("retained-removal-alias");
570 RetainedLookupFile* child = new RetainedLookupFile(String("retained-removal-child"), &filesystem,
571 &directory, destructions);
572 directory.publish(alias, child);
573 const bool firstEmergencyRetained = VFS::instance().retainTrackedFile(child);
574 const bool secondEmergencyRetained =
575 firstEmergencyRetained && VFS::instance().retainTrackedFile(child);
576
577 RetainedLookupHookContext hook(&directory, child, true, true);
578 RetainedLookupWorkerContext lookupContext(&directory, alias);
579 DirectoryRemoveWorkerContext removeContext(&directory, alias);
580 Thread* lookup =
581 new Thread(kernelProcess, retainedLookupWorker, &lookupContext, nullptr, false, true, true);
582 lookup->setName("hosted retained directory lookup");
583 Thread* remover = nullptr;
584
585 g_RetainedLookupHookContext = &hook;
586 Directory::setRetainedLookupHookForHostedTest(pauseRetainedLookup);
587 const bool lookupStarted = secondEmergencyRetained && lookup->start();
588 const bool beforePaused =
589 lookupStarted && waitForRetainedLookupPause(lookup, hook.beforeEntered, hook.beforeRelease,
590 lookupContext.returned);
591
592 bool removeStarted = false;
593 bool removeQueuedBefore = false;
594 if (beforePaused) {
595 remover = new Thread(kernelProcess, directoryRemoveWorker, &removeContext, nullptr, false, true,
596 true);
597 remover->setName("hosted retained directory remover");
598 removeStarted = remover->start();
599 if (removeStarted) {
600 removeQueuedBefore = waitForDirectoryLock(remover, directory, removeContext.returned);
601 }
602 }
603
604 const bool removalStayedPendingBefore = removeQueuedBefore && !removeContext.returned;
605 hook.beforeRelease.release();
606
607 const bool afterPaused =
608 lookupStarted && waitForRetainedLookupPause(lookup, hook.afterEntered, hook.afterRelease,
609 lookupContext.returned);
610 const bool removeQueuedAfter = afterPaused && removeStarted &&
611 waitForDirectoryLock(remover, directory, removeContext.returned);
612 const bool removalStayedPendingAfter = removeQueuedAfter && !removeContext.returned;
613
614 // Every blocking gate gets a rescue token before completion-safe joins.
615 hook.beforeRelease.release();
616 hook.afterRelease.release();
617 const bool lookupJoined = lookupStarted && lookup->joinForCompletion();
618 const bool removeJoined = removeStarted && remover->joinForCompletion();
619 if (!lookupStarted) {
620 delete lookup;
621 }
622 if (remover && !removeStarted) {
623 delete remover;
624 }
625 Directory::setRetainedLookupHookForHostedTest(nullptr);
626 g_RetainedLookupHookContext = nullptr;
627
628 const bool lookupSucceeded = lookupContext.result && lookupContext.child == child;
629 directory.remove(HashedStringView(alias));
630 const size_t cleanupReleases = drainTrackedFileOwners(child, destructions);
631
632 return firstEmergencyRetained && secondEmergencyRetained && lookupStarted && beforePaused &&
633 removeStarted && removalStayedPendingBefore && afterPaused && removalStayedPendingAfter &&
634 lookupJoined && removeJoined && hook.beforeReturned == static_cast<size_t>(1) &&
635 hook.beforeNullFile == static_cast<size_t>(1) &&
636 hook.afterReturned == static_cast<size_t>(1) && lookupSucceeded &&
637 removeContext.returned == static_cast<size_t>(1) && cleanupReleases == 2 &&
638 destructions == static_cast<size_t>(1);
639}
640
641bool directoryRetainedLazyLookup(Process* kernelProcess) {
642 RetainedLookupFilesystem filesystem;
643 RetainedLookupDirectory directory(String("retained-lazy-root"), &filesystem);
644 filesystem.setRoot(&directory);
645 Atomic<size_t> destructions(0);
646 Atomic<size_t> conversions(0);
647 Atomic<size_t> firstConversionEntered(0);
648 Semaphore firstConversionRelease(0, false);
649 Atomic<size_t> secondConversionEntered(0);
650 Semaphore secondConversionRelease(0, false);
651 const String alias("retained-lazy-alias");
652 RetainedLookupFile* child =
653 new RetainedLookupFile(String("retained-lazy-child"), &filesystem, &directory, destructions);
654 directory.publishLazy(alias, child, conversions, firstConversionEntered, firstConversionRelease,
655 secondConversionEntered, secondConversionRelease);
656
657 RetainedLookupWorkerContext firstContext(&directory, alias);
658 RetainedLookupWorkerContext secondContext(&directory, alias);
659 Thread* first =
660 new Thread(kernelProcess, retainedLookupWorker, &firstContext, nullptr, false, true, true);
661 first->setName("hosted first lazy retained lookup");
662 Thread* second = nullptr;
663 const bool firstStarted = first->start();
664
665 bool firstPaused = false;
666 for (size_t attempt = 0; attempt < HostedAttempts && firstStarted; ++attempt) {
667 Thread::WaitDebugInfo info = {};
668 if (firstConversionEntered == static_cast<size_t>(1) && first->getWaitDebugInfo(info) &&
669 info.queue && info.queued && info.channelOwner == &firstConversionRelease &&
670 first->getStatus() == Thread::Sleeping) {
671 firstPaused = true;
672 break;
673 }
675 }
676
677 bool secondStarted = false;
678 bool secondQueued = false;
679 bool secondConverted = false;
680 if (firstStarted && firstPaused) {
681 second =
682 new Thread(kernelProcess, retainedLookupWorker, &secondContext, nullptr, false, true, true);
683 second->setName("hosted second lazy retained lookup");
684 secondStarted = second->start();
685 if (secondStarted) {
686 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
687 Thread::WaitDebugInfo info = {};
688 if (secondConversionEntered == static_cast<size_t>(1) && second->getWaitDebugInfo(info) &&
689 info.queue && info.queued && info.channelOwner == &secondConversionRelease &&
690 second->getStatus() == Thread::Sleeping) {
691 secondConverted = true;
692 break;
693 }
694 if (second->getWaitDebugInfo(info) && info.queue && info.queued &&
695 info.channelOwner == directory.cacheLockAddressForHostedTest() &&
696 second->getStatus() == Thread::Sleeping) {
697 secondQueued = true;
698 break;
699 }
700 if (secondContext.returned) {
701 break;
702 }
704 }
705 }
706 }
707
708 const bool singleConversionWhilePaused = secondQueued && !secondConverted &&
709 !secondContext.returned &&
710 conversions == static_cast<size_t>(1);
711
712 bool secondFinishedBeforeFirstRelease = false;
713 if (secondConverted) {
714 secondConversionRelease.release();
715 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
716 if (secondContext.returned) {
717 secondFinishedBeforeFirstRelease = true;
718 break;
719 }
721 }
722 }
723
724 // In an unlocked mutant, conversion two completes before conversion one is
725 // allowed to write its LazyEvaluate result.
726 firstConversionRelease.release();
727 firstConversionRelease.release();
728 secondConversionRelease.release();
729 const bool firstJoined = firstStarted && first->joinForCompletion();
730 const bool secondJoined = secondStarted && second->joinForCompletion();
731 if (!firstStarted) {
732 delete first;
733 }
734 if (second && !secondStarted) {
735 delete second;
736 }
737
738 const bool lookupsSucceeded = firstContext.result && secondContext.result &&
739 firstContext.child == child && secondContext.child == child;
740 directory.remove(HashedStringView(alias));
741 const size_t cleanupReleases = drainTrackedFileOwners(child, destructions);
742 if (!destructions) {
743 delete child;
744 }
745
746 return firstStarted && firstPaused && secondStarted && singleConversionWhilePaused &&
747 !secondFinishedBeforeFirstRelease && firstJoined && secondJoined && lookupsSucceeded &&
748 conversions == static_cast<size_t>(1) && cleanupReleases == 0 &&
749 destructions == static_cast<size_t>(1);
750}
751
752bool directoryRetainedLookupDisjoint(Process* kernelProcess) {
753 RetainedLookupFilesystem filesystem;
754 RetainedLookupDirectory firstDirectory(String("retained-disjoint-first"), &filesystem);
755 RetainedLookupDirectory secondDirectory(String("retained-disjoint-second"), &filesystem);
756 filesystem.setRoot(&firstDirectory);
757 Atomic<size_t> destructions(0);
758 const String firstAlias("retained-disjoint-first-alias");
759 const String secondAlias("retained-disjoint-second-alias");
760 RetainedLookupFile* firstChild = new RetainedLookupFile(
761 String("retained-disjoint-first-child"), &filesystem, &firstDirectory, destructions);
762 RetainedLookupFile* secondChild = new RetainedLookupFile(
763 String("retained-disjoint-second-child"), &filesystem, &secondDirectory, destructions);
764 firstDirectory.publish(firstAlias, firstChild);
765 secondDirectory.publish(secondAlias, secondChild);
766
767 RetainedLookupHookContext hook(&firstDirectory, firstChild, false, true);
768 RetainedLookupWorkerContext firstContext(&firstDirectory, firstAlias);
769 RetainedLookupWorkerContext secondContext(&secondDirectory, secondAlias);
770 Thread* first =
771 new Thread(kernelProcess, retainedLookupWorker, &firstContext, nullptr, false, true, true);
772 first->setName("hosted blocked retained lookup");
773 Thread* second = nullptr;
774
775 g_RetainedLookupHookContext = &hook;
776 Directory::setRetainedLookupHookForHostedTest(pauseRetainedLookup);
777 const bool firstStarted = first->start();
778
779 const bool firstPaused =
780 firstStarted && waitForRetainedLookupPause(first, hook.afterEntered, hook.afterRelease,
781 firstContext.returned);
782
783 bool secondStarted = false;
784 bool secondFinishedWhilePaused = false;
785 if (firstStarted && firstPaused) {
786 second =
787 new Thread(kernelProcess, retainedLookupWorker, &secondContext, nullptr, false, true, true);
788 second->setName("hosted disjoint retained lookup");
789 secondStarted = second->start();
790 for (size_t attempt = 0; attempt < HostedAttempts && secondStarted; ++attempt) {
791 if (secondContext.returned) {
792 secondFinishedWhilePaused = true;
793 break;
794 }
796 }
797 }
798
799 hook.beforeRelease.release();
800 hook.afterRelease.release();
801 const bool firstJoined = firstStarted && first->joinForCompletion();
802 const bool secondJoined = secondStarted && second->joinForCompletion();
803 if (!firstStarted) {
804 delete first;
805 }
806 if (second && !secondStarted) {
807 delete second;
808 }
809 Directory::setRetainedLookupHookForHostedTest(nullptr);
810 g_RetainedLookupHookContext = nullptr;
811
812 const bool lookupsSucceeded = firstContext.result && secondContext.result &&
813 firstContext.child == firstChild &&
814 secondContext.child == secondChild;
815 firstDirectory.remove(HashedStringView(firstAlias));
816 secondDirectory.remove(HashedStringView(secondAlias));
817
818 return firstStarted && firstPaused && secondStarted && secondFinishedWhilePaused && firstJoined &&
819 secondJoined && hook.beforeEntered == static_cast<size_t>(0) &&
820 hook.afterReturned == static_cast<size_t>(1) && lookupsSucceeded &&
821 destructions == static_cast<size_t>(2);
822}
823
824bool directoryRetainedLookupDeletion() {
825 RetainedLookupFilesystem filesystem;
826 RetainedLookupDirectory directory(String("retained-delete-root"), &filesystem);
827 filesystem.setRoot(&directory);
828 Atomic<size_t> destructions(0);
829 Atomic<size_t> lockAvailable(0);
830 const String alias("retained-delete-alias");
831 RetainedLookupFile* child =
832 new RetainedLookupFile(String("retained-delete-child"), &filesystem, &directory, destructions,
833 &directory, &lockAvailable);
834 directory.publish(alias, child);
835 directory.remove(HashedStringView(alias));
836 return destructions == static_cast<size_t>(1) && lockAvailable == static_cast<size_t>(1);
837}
838
839bool directoryRetainedLookupReplacement(Process* kernelProcess) {
840 RetainedLookupFilesystem filesystem;
841 RetainedLookupDirectory directory(String("retained-replacement-root"), &filesystem);
842 filesystem.setRoot(&directory);
843 Atomic<size_t> oldDestructions(0);
844 Atomic<size_t> oldLockAvailable(0);
845 Atomic<size_t> replacementDestructions(0);
846 const String oldAlias("retained-replacement-old-alias");
847 const String replacementAlias("retained-replacement-new-alias");
848 RetainedLookupFile* oldFile =
849 new RetainedLookupFile(String("retained-replacement-old"), &filesystem, &directory,
850 oldDestructions, &directory, &oldLockAvailable);
851 RetainedLookupFile* replacement = new RetainedLookupFile(
852 String("retained-replacement-new"), &filesystem, &directory, replacementDestructions);
853 directory.publish(oldAlias, oldFile);
854 directory.publish(replacementAlias, replacement);
855 const bool firstReplacementEmergency = VFS::instance().retainTrackedFile(replacement);
856 const bool secondReplacementEmergency =
857 firstReplacementEmergency && VFS::instance().retainTrackedFile(replacement);
858
859 RetainedLookupHookContext hook(&directory, replacement, false, true);
860 RetainedLookupReplacementWorkerContext workerContext(&directory, oldAlias, replacementAlias,
861 oldFile, replacement);
862 Thread* worker = new Thread(kernelProcess, retainedLookupReplacementWorker, &workerContext,
863 nullptr, false, true, true);
864 worker->setName("hosted retained lookup replacement");
865
866 g_RetainedLookupHookContext = &hook;
867 Directory::setRetainedLookupHookForHostedTest(pauseRetainedLookup);
868 const bool workerStarted = secondReplacementEmergency && worker->start();
869 const bool afterPaused =
870 workerStarted && waitForRetainedLookupPause(worker, hook.afterEntered, hook.afterRelease,
871 workerContext.returned);
872 const bool oldStayedAliveThroughRetain = afterPaused && !oldDestructions;
873
874 hook.afterRelease.release();
875 const bool workerJoined = workerStarted && worker->joinForCompletion();
876 if (!workerStarted) {
877 delete worker;
878 }
879 Directory::setRetainedLookupHookForHostedTest(nullptr);
880 g_RetainedLookupHookContext = nullptr;
881
882 directory.remove(HashedStringView(oldAlias));
883 directory.remove(HashedStringView(replacementAlias));
884 if (!oldDestructions) {
885 drainTrackedFileOwners(oldFile, oldDestructions);
886 }
887 const size_t replacementCleanupReleases =
888 drainTrackedFileOwners(replacement, replacementDestructions);
889
890 return firstReplacementEmergency && secondReplacementEmergency && workerStarted && afterPaused &&
891 oldStayedAliveThroughRetain && workerJoined && workerContext.oldRetained &&
892 workerContext.missingPreserved && workerContext.replaced &&
893 workerContext.returned == static_cast<size_t>(1) &&
894 hook.afterReturned == static_cast<size_t>(1) &&
895 oldDestructions == static_cast<size_t>(1) && oldLockAvailable == static_cast<size_t>(1) &&
896 replacementCleanupReleases == 2 && replacementDestructions == static_cast<size_t>(1);
897}
898
899bool directoryRetainedFailedLazyLookup() {
900 RetainedLookupFilesystem filesystem;
901 RetainedLookupDirectory directory(String("retained-failed-lazy-root"), &filesystem);
902 filesystem.setRoot(&directory);
903 Atomic<size_t> conversions(0);
904 const String alias("retained-failed-lazy-alias");
905
906 // This seed makes any attempt to track a null conversion result observable.
907 VFS::instance().trackFile(nullptr);
908 directory.publishFailedLazy(alias, conversions);
909
912 const bool firstFailed = !directory.lookupRetained(HashedStringView(alias), first);
913 const bool secondFailed = !directory.lookupRetained(HashedStringView(alias), second);
914 directory.remove(HashedStringView(alias));
915
916 const bool seedWasFinal = VFS::instance().untrackFile(nullptr, false);
917 bool mutantExtrasDrained = seedWasFinal;
918 for (size_t release = 0; release < 4 && !mutantExtrasDrained; ++release) {
919 mutantExtrasDrained = VFS::instance().untrackFile(nullptr, false);
920 }
921
922 return firstFailed && secondFailed && !first && !second &&
923 conversions == static_cast<size_t>(2) && seedWasFinal && mutantExtrasDrained;
924}
925
926struct DirectoryMutationSerializationContext {
927 DirectoryMutationSerializationContext(RetainedLookupDirectory* directory, const String& alias,
928 RetainedLookupFile* oldChild,
929 Atomic<size_t>& oldDestructions,
930 RetainedLookupFile* replacement)
931 : directory(directory),
932 alias(alias),
933 oldChild(oldChild),
934 oldDestructions(oldDestructions),
935 replacement(replacement),
936 publisher(nullptr),
937 publishStart(0),
938 publishAttempted(0),
939 publishReturned(0),
940 callbacks(0),
941 firstSawOld(false),
942 oldStayedAliveAfterAliasRemoval(false),
943 publisherBlocked(false) {}
944
945 RetainedLookupDirectory* directory;
946 String alias;
947 RetainedLookupFile* oldChild;
948 Atomic<size_t>& oldDestructions;
949 RetainedLookupFile* replacement;
950 Thread* publisher;
951 Semaphore publishStart;
952 Atomic<size_t> publishAttempted;
953 Atomic<size_t> publishReturned;
954 size_t callbacks;
955 bool firstSawOld;
956 bool oldStayedAliveAfterAliasRemoval;
957 bool publisherBlocked;
958};
959
960int publishDuringDirectoryRemoval(void* opaque) {
961 DirectoryMutationSerializationContext* context =
962 reinterpret_cast<DirectoryMutationSerializationContext*>(opaque);
963 if (!context->publishStart.acquireForCompletion()) {
964 return 1;
965 }
966 context->publishAttempted += 1;
967 context->directory->publish(context->alias, context->replacement);
968 context->publishReturned += 1;
969 return 0;
970}
971
972bool serializeDirectoryMutationRemove(File* parent, File* file, void* opaque) {
973 DirectoryMutationSerializationContext* context =
974 reinterpret_cast<DirectoryMutationSerializationContext*>(opaque);
975 ++context->callbacks;
976 context->firstSawOld =
977 context->callbacks == 1 && parent == context->directory && file == context->oldChild;
978 if (!context->firstSawOld ||
979 !context->directory->removePublished(context->alias, context->oldChild)) {
980 return false;
981 }
982
983 context->oldStayedAliveAfterAliasRemoval = context->oldDestructions == static_cast<size_t>(0);
984 context->publishStart.release();
985 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
986 Thread::WaitDebugInfo info = {};
987 if (context->publishAttempted == static_cast<size_t>(1) &&
988 context->publisher->getWaitDebugInfo(info) && info.queue && info.queued &&
989 info.channelOwner == context->directory->namespaceLockAddress() &&
990 context->publisher->getStatus() == Thread::Sleeping) {
991 context->publisherBlocked = true;
992 break;
993 }
995 }
996 return context->oldStayedAliveAfterAliasRemoval && context->publisherBlocked;
997}
998
999bool directoryMutationSerializesSameKeyReplacement(Process* kernelProcess) {
1000 RetainedLookupFilesystem filesystem;
1001 RetainedLookupDirectory directory(String("retained-mutation-root"), &filesystem);
1002 filesystem.setRoot(&directory);
1003 Atomic<size_t> oldDestructions(0);
1004 Atomic<size_t> replacementDestructions(0);
1005 const String alias("retained-mutation-alias");
1006 RetainedLookupFile* oldChild = new RetainedLookupFile(String("retained-mutation-old"),
1007 &filesystem, &directory, oldDestructions);
1008 RetainedLookupFile* replacement = new RetainedLookupFile(
1009 String("retained-mutation-replacement"), &filesystem, &directory, replacementDestructions);
1010 directory.publish(alias, oldChild);
1011
1012 DirectoryMutationSerializationContext context(&directory, alias, oldChild, oldDestructions,
1013 replacement);
1014 Thread* publisher = new Thread(kernelProcess, publishDuringDirectoryRemoval, &context, nullptr,
1015 false, true, true);
1016 publisher->setName("hosted directory mutation publisher");
1017 context.publisher = publisher;
1018 const bool publisherStarted = publisher->start();
1019
1020 filesystem.setRemoveHook(serializeDirectoryMutationRemove, &context);
1021 const bool removed = publisherStarted && filesystem.remove(&directory, oldChild);
1022 filesystem.setRemoveHook(nullptr, nullptr);
1023 if (!removed) {
1024 context.publishStart.release();
1025 }
1026 const bool publisherJoined = publisherStarted && publisher->joinForCompletion();
1027 if (!publisherStarted) {
1028 delete publisher;
1029 }
1030
1031 Directory::ChildLease retainedReplacement;
1032 const bool replacementVisible =
1033 directory.lookupRetained(HashedStringView(alias), retainedReplacement) &&
1034 retainedReplacement.get() == replacement;
1035
1036 directory.remove(HashedStringView(alias));
1037 retainedReplacement.reset();
1038 if (!oldDestructions) {
1039 drainTrackedFileOwners(oldChild, oldDestructions);
1040 }
1041 if (!replacementDestructions) {
1042 delete replacement;
1043 }
1044
1045 return publisherStarted && removed && publisherJoined && context.callbacks == 1 &&
1046 context.firstSawOld && context.oldStayedAliveAfterAliasRemoval &&
1047 context.publisherBlocked && context.publishAttempted == static_cast<size_t>(1) &&
1048 context.publishReturned == static_cast<size_t>(1) && replacementVisible &&
1049 oldDestructions == static_cast<size_t>(1) &&
1050 replacementDestructions == static_cast<size_t>(1);
1051}
1052
1053bool directoryRetainedDuplicateEphemeral() {
1054 RetainedLookupFilesystem filesystem;
1055 RetainedLookupDirectory directory(String("retained-ephemeral-root"), &filesystem);
1056 filesystem.setRoot(&directory);
1057 Atomic<size_t> originalDestructions(0);
1058 Atomic<size_t> duplicateDestructions(0);
1059 const String name("retained-ephemeral-child");
1060 RetainedLookupFile* original =
1061 new RetainedLookupFile(name, &filesystem, &directory, originalDestructions);
1062 RetainedLookupFile* duplicate =
1063 new RetainedLookupFile(name, &filesystem, &directory, duplicateDestructions);
1064 directory.publish(name, original);
1065
1066 const bool duplicateAdded = directory.publishEphemeral(duplicate);
1067 Directory::ChildLease visible;
1068 const bool originalVisible =
1069 directory.lookupRetained(HashedStringView(name), visible) && visible.get() == original;
1070 const bool duplicateWasTracked = VFS::instance().untrackFile(duplicate, false);
1071
1072 directory.remove(HashedStringView(name));
1073 visible.reset();
1074 const size_t originalCleanupReleases = drainTrackedFileOwners(original, originalDestructions);
1075 if (!duplicateDestructions) {
1076 delete duplicate;
1077 }
1078
1079 return !duplicateAdded && originalVisible && !duplicateWasTracked &&
1080 originalCleanupReleases == 0 && originalDestructions == static_cast<size_t>(1) &&
1081 duplicateDestructions == static_cast<size_t>(1);
1082}
1083
1084bool directoryRetainedLookupAtomicity(Process* kernelProcess) {
1085 const bool removal = directoryRetainedLookupRemoval(kernelProcess);
1086 const bool lazy = directoryRetainedLazyLookup(kernelProcess);
1087 const bool failedLazy = directoryRetainedFailedLazyLookup();
1088 if (!removal || !lazy || !failedLazy) {
1089 ERROR(
1090 "HOSTED-SYSCALL-TEST: FAIL directory-retained-lookup-atomicity: "
1091 "lookup did not linearise child retention with removal and lazy evaluation");
1092 return false;
1093 }
1094 NOTICE("HOSTED-SYSCALL-TEST: PASS directory-retained-lookup-atomicity");
1095 return true;
1096}
1097
1098bool directoryRetainedLookupLifecycle(Process* kernelProcess) {
1099 const bool disjoint = directoryRetainedLookupDisjoint(kernelProcess);
1100 const bool deletion = directoryRetainedLookupDeletion();
1101 const bool replacement = directoryRetainedLookupReplacement(kernelProcess);
1102 const bool mutationSerialization = directoryMutationSerializesSameKeyReplacement(kernelProcess);
1103 const bool duplicateEphemeral = directoryRetainedDuplicateEphemeral();
1104 if (!disjoint || !deletion || !replacement || !mutationSerialization || !duplicateEphemeral) {
1105 ERROR(
1106 "HOSTED-SYSCALL-TEST: FAIL directory-retained-lookup-lifecycle: "
1107 "directory locks were global or child destruction ran while locked");
1108 return false;
1109 }
1110 NOTICE("HOSTED-SYSCALL-TEST: PASS directory-retained-lookup-lifecycle");
1111 return true;
1112}
1113
1114struct TrackedFileRetainHookContext {
1115 explicit TrackedFileRetainHookContext(File* target)
1116 : target(target), release(0, false), claimed(0), entered(0), returned(0) {}
1117
1118 File* target;
1119 Semaphore release;
1120 Atomic<size_t> claimed;
1121 Atomic<size_t> entered;
1122 Atomic<size_t> returned;
1123};
1124
1125struct TrackedFileRetainWorkerContext {
1126 explicit TrackedFileRetainWorkerContext(File* file) : file(file), retained(0), returned(0) {}
1127
1128 File* file;
1129 Atomic<size_t> retained;
1130 Atomic<size_t> returned;
1131};
1132
1133Atomic<TrackedFileRetainHookContext*> g_TrackedFileRetainHookContext(nullptr);
1134
1135void pauseFirstTrackedFileRetain(File* file) {
1136 TrackedFileRetainHookContext* context = g_TrackedFileRetainHookContext;
1137 if (!context || context->target != file || !context->claimed.compareAndSwap(0, 1)) {
1138 return;
1139 }
1140
1141 context->entered += 1;
1142 if (context->release.acquireForCompletion()) {
1143 context->returned += 1;
1144 }
1145}
1146
1147int retainTrackedFileWorker(void* parameter) {
1148 TrackedFileRetainWorkerContext* context =
1149 reinterpret_cast<TrackedFileRetainWorkerContext*>(parameter);
1150 context->retained = VFS::instance().retainTrackedFile(context->file) ? 1 : 0;
1151 context->returned += 1;
1152 return context->retained ? 0 : 1;
1153}
1154
1155bool establishedAliasRetainSerialization(Process* kernelProcess) {
1156 Atomic<size_t> destructions(0);
1157 EstablishedAliasFileProbe* file = new EstablishedAliasFileProbe(destructions);
1158 VFS::instance().trackFile(file);
1159
1160 TrackedFileRetainHookContext hook(file);
1161 TrackedFileRetainWorkerContext workerAContext(file);
1162 TrackedFileRetainWorkerContext workerBContext(file);
1163 Thread* workerA = new Thread(kernelProcess, retainTrackedFileWorker, &workerAContext, nullptr,
1164 false, true, true);
1165 Thread* workerB = nullptr;
1166 workerA->setName("hosted VFS retain serializer A");
1167
1168 g_TrackedFileRetainHookContext = &hook;
1169 VFS::setRetainTrackedFileHookForHostedTest(pauseFirstTrackedFileRetain);
1170 const bool startedA = workerA->start();
1171
1172 bool workerABlocked = false;
1173 for (size_t attempt = 0; attempt < HostedAttempts && startedA; ++attempt) {
1174 Thread::WaitDebugInfo info = {};
1175 if (hook.entered == static_cast<size_t>(1) && workerA->getWaitDebugInfo(info) && info.queue &&
1176 info.queued && info.channelOwner == &hook.release &&
1177 workerA->getStatus() == Thread::Sleeping) {
1178 workerABlocked = true;
1179 break;
1180 }
1182 }
1183
1184 bool startedB = false;
1185 if (startedA) {
1186 workerB = new Thread(kernelProcess, retainTrackedFileWorker, &workerBContext, nullptr, false,
1187 true, true);
1188 workerB->setName("hosted VFS retain serializer B");
1189 startedB = workerB->start();
1190 }
1191 bool workerBQueued = false;
1192 for (size_t attempt = 0; attempt < HostedAttempts && startedB; ++attempt) {
1193 Thread::WaitDebugInfo info = {};
1194 if (workerB->getWaitDebugInfo(info) && info.queue && info.queued &&
1195 info.channelOwner == VFS::instance().trackedFilesLockAddressForHostedTest() &&
1196 workerB->getStatus() == Thread::Sleeping) {
1197 workerBQueued = true;
1198 break;
1199 }
1200 if (workerBContext.returned) {
1201 break;
1202 }
1204 }
1205
1206 bool passed = startedA && workerABlocked && startedB && workerBQueued && !workerBContext.returned;
1207
1208 hook.release.release();
1209 const bool joinedA = startedA ? workerA->joinForCompletion() : false;
1210 const bool joinedB = startedB ? workerB->joinForCompletion() : false;
1211 if (!startedA) {
1212 delete workerA;
1213 }
1214 if (workerB && !startedB) {
1215 delete workerB;
1216 }
1217 VFS::setRetainTrackedFileHookForHostedTest(nullptr);
1218 g_TrackedFileRetainHookContext = nullptr;
1219
1220 passed = passed && joinedA && joinedB && hook.returned == static_cast<size_t>(1) &&
1221 workerAContext.retained == static_cast<size_t>(1) &&
1222 workerBContext.retained == static_cast<size_t>(1);
1223
1224 const bool firstWasFinal = VFS::instance().untrackFile(file, false);
1225 bool secondWasFinal = false;
1226 if (!firstWasFinal) {
1227 secondWasFinal = VFS::instance().untrackFile(file, false);
1228 }
1229
1230 bool finalDestroyed = false;
1231 if (!firstWasFinal && !secondWasFinal) {
1232 finalDestroyed = VFS::instance().untrackFile(file);
1233 } else {
1234 delete file;
1235 }
1236
1237 passed = passed && !firstWasFinal && !secondWasFinal && finalDestroyed &&
1238 destructions == static_cast<size_t>(1);
1239 if (!destructions) {
1240 repairAliasFileProbe(file, destructions);
1241 }
1242
1243 if (!passed) {
1244 ERROR(
1245 "HOSTED-SYSCALL-TEST: FAIL vfs-established-alias-serialization: "
1246 "concurrent established-owner retains were not serialized by the tracker lock");
1247 return false;
1248 }
1249
1250 NOTICE("HOSTED-SYSCALL-TEST: PASS vfs-established-alias-serialization");
1251 return true;
1252}
1253
1254enum DescriptorAliasConstruction {
1255 DescriptorDirect,
1256 DescriptorCopy,
1257 DescriptorPointerCopy,
1258};
1259
1260bool descriptorEstablishedAliasLifetime(DescriptorAliasConstruction construction) {
1261 Atomic<size_t> destructions(0);
1262 EstablishedAliasFileProbe* file = new EstablishedAliasFileProbe(destructions);
1263 FileDescriptor* source = nullptr;
1264
1265 if (construction != DescriptorDirect) {
1266 source = new FileDescriptor(file);
1267 }
1268
1269 VFS::instance().trackFile(file);
1270 VFS::instance().trackFile(file);
1271
1272 FileDescriptor* alias = nullptr;
1273 if (construction == DescriptorDirect) {
1274 alias = new FileDescriptor(file);
1275 } else if (construction == DescriptorCopy) {
1276 alias = new FileDescriptor(*source);
1277 } else {
1278 alias = new FileDescriptor(source);
1279 }
1280
1281 delete source;
1282 VFS::instance().untrackFile(file);
1283 const bool emergencyWasFinal = VFS::instance().untrackFile(file, false);
1284 bool passed = !emergencyWasFinal && !destructions;
1285
1286 delete alias;
1287 passed = passed && destructions == static_cast<size_t>(1);
1288 repairAliasFileProbe(file, destructions);
1289 return passed;
1290}
1291
1292bool establishedFileAliasLifetime() {
1293 const bool directPassed = descriptorEstablishedAliasLifetime(DescriptorDirect);
1294 const bool copyPassed = descriptorEstablishedAliasLifetime(DescriptorCopy);
1295 const bool pointerCopyPassed = descriptorEstablishedAliasLifetime(DescriptorPointerCopy);
1296 bool passed = directPassed && copyPassed && pointerCopyPassed;
1297
1298 Atomic<size_t> destructions(0);
1299 EstablishedAliasFileProbe* untracked = new EstablishedAliasFileProbe(destructions);
1300 FileDescriptor* descriptor = new FileDescriptor(untracked);
1301 const bool descriptorPublishedFile = VFS::instance().untrackFile(untracked, false);
1302 delete descriptor;
1303 passed = passed && !descriptorPublishedFile && !destructions;
1304 repairAliasFileProbe(untracked, destructions);
1305
1306 if (!passed) {
1307 ERROR(
1308 "HOSTED-SYSCALL-TEST: FAIL file-established-alias-lifetime: "
1309 "tracked descriptors did not retain exactly one VFS owner, or an untracked descriptor "
1310 "published a new owner");
1311 return false;
1312 }
1313
1314 NOTICE("HOSTED-SYSCALL-TEST: PASS file-established-alias-lifetime");
1315 return true;
1316}
1317
1318class ContextLifetimePath final : public FilesystemPath {
1319 public:
1320 explicit ContextLifetimePath(File* file) : m_File(file), m_Tracked(file->retainVfsReference()) {}
1321 ~ContextLifetimePath() override {
1322 if (m_Tracked)
1323 m_File->releaseVfsReference();
1324 }
1325 File* node() const override {
1326 return m_File;
1327 }
1328 const void* provider() const override {
1329 return &Provider;
1330 }
1331
1332 private:
1333 static const char Provider;
1334 File* const m_File;
1335 const bool m_Tracked;
1336};
1337const char ContextLifetimePath::Provider = 0;
1338
1339class ContextLifetimeProvider final : public FilesystemContext {
1340 public:
1341 ContextLifetimeProvider(const FilesystemPathRef& root, const FilesystemPathRef& cwd)
1342 : m_Root(root), m_Cwd(cwd) {}
1343 bool snapshot(FilesystemContextSnapshot& result) const override {
1344 if (!m_Registered)
1345 return false;
1346 result.root = m_Root;
1347 result.cwd = m_Cwd;
1348 result.contextGeneration = m_Generation;
1349 return true;
1350 }
1351 bool forkForProcess(FilesystemContextOwner& result) const override {
1352 if (!m_Registered || result)
1353 return false;
1354 auto context = FilesystemContextRef::tryAdopt(new ContextLifetimeProvider(m_Root, m_Cwd));
1355 if (!context)
1356 return false;
1357 result = FilesystemContextOwner::adopt(pedigree_std::move(context));
1358 return true;
1359 }
1360 void retireProcessOwner() override {
1361 assert(m_Registered);
1362 m_Registered = false;
1363 m_Root.reset();
1364 m_Cwd.reset();
1365 }
1366 void replace(const FilesystemPathRef& root, const FilesystemPathRef& cwd) {
1367 m_Root = root;
1368 m_Cwd = cwd;
1369 ++m_Generation;
1370 }
1371
1372 private:
1373 FilesystemPathRef m_Root, m_Cwd;
1374 uint64_t m_Generation = 1;
1375 bool m_Registered = true;
1376};
1377
1378class ContextLifetimeProcess final : public Process {
1379 public:
1380 explicit ContextLifetimeProcess(Process* parent)
1381 : Process(DeferredPublication{}, parent, true, FilesystemContextMode::Deferred) {}
1382 void publishContext() {
1383 publish();
1384 }
1385};
1386
1387bool processFilesystemContextLifetime(Process* kernelProcess) {
1388 TerminationDeferral lifetime;
1389 Atomic<size_t> cwdDestructions(0);
1390 Atomic<size_t> rootDestructions(0);
1391 Atomic<size_t> borrowedCwdDestructions(0);
1392 Atomic<size_t> borrowedRootDestructions(0);
1393 RetainedLookupFilesystem borrowedCwdFilesystem;
1394 RetainedLookupFilesystem borrowedRootFilesystem;
1395 EstablishedAliasFileProbe* cwd = new EstablishedAliasFileProbe(cwdDestructions);
1396 EstablishedAliasFileProbe* root = new EstablishedAliasFileProbe(rootDestructions);
1397 EstablishedAliasFileProbe* borrowedCwd = new EstablishedAliasFileProbe(borrowedCwdDestructions);
1398 EstablishedAliasFileProbe* borrowedRoot = new EstablishedAliasFileProbe(borrowedRootDestructions);
1399 borrowedCwd->setFilesystem(&borrowedCwdFilesystem);
1400 borrowedRoot->setFilesystem(&borrowedRootFilesystem);
1401 borrowedCwdFilesystem.setRoot(borrowedCwd);
1402 borrowedRootFilesystem.setRoot(borrowedRoot);
1403 VFS::instance().trackFile(cwd);
1404 VFS::instance().trackFile(root);
1405
1406 auto* parent = new ContextLifetimeProcess(kernelProcess);
1407 auto context = FilesystemContextRef::tryAdopt(
1408 new ContextLifetimeProvider(FilesystemPathRef::tryAdopt(new ContextLifetimePath(root)),
1409 FilesystemPathRef::tryAdopt(new ContextLifetimePath(cwd))));
1410 FilesystemContextRef retainedContext = context;
1411 const bool installed =
1412 parent->installFilesystemContext(FilesystemContextOwner::adopt(pedigree_std::move(context)));
1413 if (!installed)
1414 FATAL("Hosted context fixture could not prepare its process owner");
1415 parent->publishContext();
1417 const bool snapshotted = installed && retainedContext->snapshot(snapshot);
1418 File* retainedCwd = snapshot.cwd ? snapshot.cwd->node() : nullptr;
1419 File* retainedRoot = snapshot.root ? snapshot.root->node() : nullptr;
1420 Process* child = new Process(parent);
1421 FilesystemContextSnapshot inherited;
1422 auto childContext = child->acquireFilesystemContext();
1423 const bool childInherited = childContext && childContext.get() != retainedContext.get() &&
1424 childContext->snapshot(inherited) && inherited.cwd == snapshot.cwd &&
1425 inherited.root == snapshot.root;
1426 inherited = FilesystemContextSnapshot();
1427 childContext.reset();
1428
1429 const bool cwdNamespaceWasFinal = VFS::instance().untrackFile(cwd, false);
1430 const bool rootNamespaceWasFinal = VFS::instance().untrackFile(root, false);
1431 static_cast<ContextLifetimeProvider*>(retainedContext.get())
1432 ->replace(FilesystemPathRef::tryAdopt(new ContextLifetimePath(borrowedRoot)),
1433 FilesystemPathRef::tryAdopt(new ContextLifetimePath(borrowedCwd)));
1434 const bool borrowedCwdWasPublished = VFS::instance().untrackFile(borrowedCwd, false);
1435 const bool borrowedRootWasPublished = VFS::instance().untrackFile(borrowedRoot, false);
1436 childContext = child->acquireFilesystemContext();
1437 const bool childUnchanged = childContext && childContext->snapshot(inherited) &&
1438 inherited.cwd == snapshot.cwd && inherited.root == snapshot.root;
1439 inherited = FilesystemContextSnapshot();
1440 childContext.reset();
1441 FilesystemContextSnapshot replacement;
1442 const bool parentReplaced = retainedContext->snapshot(replacement) && replacement.cwd &&
1443 replacement.root && replacement.cwd->node() == borrowedCwd &&
1444 replacement.root->node() == borrowedRoot &&
1445 replacement.contextGeneration > snapshot.contextGeneration;
1446 replacement = FilesystemContextSnapshot();
1447
1448 delete child;
1449 const bool snapshotsHeldAcrossReplacement =
1450 retainedCwd == cwd && retainedRoot == root && !cwdDestructions && !rootDestructions;
1451 snapshot = FilesystemContextSnapshot();
1452 const bool inheritedReferencesReleased =
1453 cwdDestructions == static_cast<size_t>(1) && rootDestructions == static_cast<size_t>(1);
1454 delete parent;
1455 const bool retired = !retainedContext->snapshot(snapshot);
1456 retainedContext.reset();
1457 const bool borrowedReferencesSurvived = !borrowedCwdDestructions && !borrowedRootDestructions;
1458 delete borrowedCwd;
1459 delete borrowedRoot;
1460
1461 const bool passed =
1462 installed && snapshotted && childInherited && childUnchanged && parentReplaced && retired &&
1463 !cwdNamespaceWasFinal && !rootNamespaceWasFinal && !borrowedCwdWasPublished &&
1464 !borrowedRootWasPublished && snapshotsHeldAcrossReplacement && inheritedReferencesReleased &&
1465 borrowedReferencesSurvived && borrowedCwdDestructions == static_cast<size_t>(1) &&
1466 borrowedRootDestructions == static_cast<size_t>(1);
1467 if (!passed) {
1468 ERROR(
1469 "HOSTED-SYSCALL-TEST: FAIL process-filesystem-context-lifetime: "
1470 "fork, replacement, or destruction mismanaged a cwd/root VFS owner");
1471 return false;
1472 }
1473
1474 NOTICE("HOSTED-SYSCALL-TEST: PASS process-filesystem-context-lifetime");
1475 return true;
1476}
1477
1478enum MappingAliasConstruction {
1479 MappingDirect,
1480 MappingClone,
1481 MappingSplit,
1482};
1483
1484bool mappingEstablishedAliasLifetime(MappingAliasConstruction construction) {
1485 const size_t pageSize = PhysicalMemoryManager::getPageSize();
1486 Atomic<size_t> destructions(0);
1487 EstablishedAliasFileProbe* file = new EstablishedAliasFileProbe(destructions);
1488 MemoryMappedObject* source = nullptr;
1489
1490 if (construction != MappingDirect) {
1491 source = new MemoryMappedFile(0x100000, pageSize * 2, 0, file, false, MemoryMappedObject::Read);
1492 }
1493
1494 VFS::instance().trackFile(file);
1495 VFS::instance().trackFile(file);
1496
1497 MemoryMappedObject* alias = nullptr;
1498 if (construction == MappingDirect) {
1499 alias = new MemoryMappedFile(0x100000, pageSize, 0, file, false, MemoryMappedObject::Read);
1500 } else if (construction == MappingClone) {
1501 alias = source->clone();
1502 } else {
1503 alias = source->split(0x100000 + pageSize);
1504 }
1505
1506 delete source;
1507 VFS::instance().untrackFile(file);
1508 const bool emergencyWasFinal = VFS::instance().untrackFile(file, false);
1509 bool passed = !emergencyWasFinal && !destructions;
1510
1511 delete alias;
1512 passed = passed && destructions == static_cast<size_t>(1);
1513 repairAliasFileProbe(file, destructions);
1514 return passed;
1515}
1516
1517bool establishedMappingAliasLifetime() {
1518 const bool directPassed = mappingEstablishedAliasLifetime(MappingDirect);
1519 const bool clonePassed = mappingEstablishedAliasLifetime(MappingClone);
1520 const bool splitPassed = mappingEstablishedAliasLifetime(MappingSplit);
1521 bool passed = directPassed && clonePassed && splitPassed;
1522
1523 const size_t pageSize = PhysicalMemoryManager::getPageSize();
1524 Atomic<size_t> destructions(0);
1525 EstablishedAliasFileProbe* untracked = new EstablishedAliasFileProbe(destructions);
1526 MemoryMappedObject* mapping =
1527 new MemoryMappedFile(0x100000, pageSize, 0, untracked, false, MemoryMappedObject::Read);
1528 const bool mappingPublishedFile = VFS::instance().untrackFile(untracked, false);
1529 delete mapping;
1530 passed = passed && !mappingPublishedFile && !destructions;
1531 repairAliasFileProbe(untracked, destructions);
1532
1533 if (!passed) {
1534 ERROR(
1535 "HOSTED-SYSCALL-TEST: FAIL mmap-established-alias-lifetime: "
1536 "tracked mappings did not retain a VFS owner, or an untracked mapping published a new "
1537 "owner");
1538 return false;
1539 }
1540
1541 NOTICE("HOSTED-SYSCALL-TEST: PASS mmap-established-alias-lifetime");
1542 return true;
1543}
1544
1545bool munmapUsesTargetPageGeometry(Thread* thread) {
1546 const size_t pageSize = PhysicalMemoryManager::getPageSize();
1547
1548 thread->setErrno(0);
1549 const int misalignedResult = posix_munmap(reinterpret_cast<void*>(pageSize / 2), pageSize);
1550 const bool rejectedMisaligned =
1551 misalignedResult == -1 && thread->getErrno() == Error::InvalidArgument;
1552
1553 thread->setErrno(0);
1554 const int alignedResult = posix_munmap(reinterpret_cast<void*>(pageSize), pageSize);
1555 const bool acceptedAligned = alignedResult == 0 && !thread->getErrno();
1556 thread->setErrno(0);
1557
1558 if (!rejectedMisaligned || !acceptedAligned) {
1559 ERROR(
1560 "HOSTED-SYSCALL-TEST: FAIL munmap-target-page-geometry: "
1561 "munmap did not validate addresses against the target page size");
1562 return false;
1563 }
1564
1565 NOTICE("HOSTED-SYSCALL-TEST: PASS munmap-target-page-geometry");
1566 return true;
1567}
1568
1569bool mappingManagerSplitLifetime(Process* process, bool exactSuffix) {
1570 const size_t pageSize = PhysicalMemoryManager::getPageSize();
1571 const size_t mappingLength = pageSize * 3;
1572 uintptr_t address = 0;
1573 if (!process->allocateUserRange(Process::UserRegion::Normal, mappingLength, address)) {
1574 return false;
1575 }
1576
1577 Atomic<size_t> destructions(0);
1578 EstablishedAliasFileProbe* file = new EstablishedAliasFileProbe(destructions);
1579 VFS::instance().trackFile(file);
1580 VFS::instance().trackFile(file);
1581
1582 uintptr_t mappedAddress = address;
1584 file, mappedAddress, mappingLength, MemoryMappedObject::Read);
1585 bool passed = mapping && mappedAddress == address;
1586 if (mapping) {
1587 const size_t removedMiddleOrSuffix = MemoryMapManager::instance().remove(
1588 address + pageSize, exactSuffix ? pageSize * 2 : pageSize);
1589 const size_t removedPrefix = MemoryMapManager::instance().remove(address, pageSize);
1590 size_t removedTail = 1;
1591 if (!exactSuffix) {
1592 removedTail = MemoryMapManager::instance().remove(address + pageSize * 2, pageSize);
1593 }
1594 passed = passed && removedMiddleOrSuffix == 1 && removedPrefix == 1 && removedTail == 1;
1595 }
1596
1597 MemoryMapManager::instance().remove(address, mappingLength);
1598 process->freeUserRange(Process::UserRegion::Normal, address, mappingLength);
1599 const bool namespaceWasFinal = VFS::instance().untrackFile(file);
1600 const bool emergencyWasFinal = VFS::instance().untrackFile(file);
1601 passed =
1602 passed && !namespaceWasFinal && emergencyWasFinal && destructions == static_cast<size_t>(1);
1603 repairAliasFileProbe(file, destructions);
1604 return passed;
1605}
1606
1607bool mappingManagerSplitLifetime(Process* process) {
1608 const bool exactSuffixPassed = mappingManagerSplitLifetime(process, true);
1609 const bool middlePassed = mappingManagerSplitLifetime(process, false);
1610 const bool passed = exactSuffixPassed && middlePassed;
1611 if (!passed) {
1612 ERROR(
1613 "HOSTED-SYSCALL-TEST: FAIL mmap-split-alias-lifetime: "
1614 "an exact-suffix or middle removal leaked a file-backed mapping owner");
1615 return false;
1616 }
1617
1618 NOTICE("HOSTED-SYSCALL-TEST: PASS mmap-split-alias-lifetime");
1619 return true;
1620}
1621
1622bool posixPathLookupLifetime(Process* kernelProcess) {
1624 auto* priorView = VFS::instance().mountView();
1625 VFS::HostedRootViewScope fixture;
1626 UnixFilesystem* testFilesystem = new UnixFilesystem;
1627 if (!fixture.open(testFilesystem)) {
1628 delete testFilesystem;
1629 return false;
1630 }
1631 FilesystemPathRef rootPath;
1632 const bool rootInstalled = fixture.view()->bootRootPath(rootPath);
1633 const String name("hosted-established-alias-path-cache");
1634 const String path("/hosted-established-alias-path-cache");
1635 const bool created = rootInstalled && fixture.view()->createFile(rootPath, name, 0600);
1636
1637 Process* process =
1638 new PosixProcess(kernelProcess, true, Process::FilesystemContextMode::Deferred);
1639 PosixSubsystem* subsystem = new PosixSubsystem;
1640 process->setSubsystem(subsystem);
1641 subsystem->setAbi(PosixSubsystem::LinuxAbi);
1642 const bool contextInstalled = fixture.installContext(*process);
1643 ResolvedPath originalLease, resolvedLease;
1644 File* original = created && contextInstalled
1645 ? subsystem->findFileRetained(path, originalLease, rootPath)
1646 : nullptr;
1647 const bool originalRemoved = original && fixture.view()->remove(rootPath, name, original);
1648 const bool recreated = originalRemoved && fixture.view()->createFile(rootPath, name, 0600);
1649 Directory::ChildLease replacementLease;
1650 File* replacement = nullptr;
1651 if (recreated && Directory::fromFile(rootPath->node())
1652 ->lookupChild(HashedStringView(name), replacementLease) ==
1653 Directory::LookupStatus::Found)
1654 replacement = replacementLease.get();
1655 File* resolved = recreated ? subsystem->findFileRetained(path, resolvedLease, rootPath) : nullptr;
1656 const bool resolvedReplacement =
1657 replacement && replacement != original && resolved == replacement;
1658 const bool pathCleaned = replacement && fixture.view()->remove(rootPath, name, replacement);
1659 replacementLease.reset();
1660 resolvedLease.reset();
1661 originalLease.reset();
1662 rootPath.reset();
1663 delete process;
1664 const bool rootRestored = fixture.close();
1665 if (!rootRestored)
1666 FATAL("Hosted pathname fixture retained owners after teardown");
1667 const bool previousRestored =
1668 VFS::instance().getRootFilesystem() == priorRoot && VFS::instance().mountView() == priorView;
1669 delete testFilesystem;
1670
1671 const bool passed = rootInstalled && contextInstalled && created && original && originalRemoved &&
1672 recreated && resolvedReplacement && pathCleaned && rootRestored &&
1673 previousRestored;
1674 if (!passed) {
1675 ERROR(
1676 "HOSTED-SYSCALL-TEST: FAIL posix-path-lookup-lifetime: "
1677 "a removed pathname resolved to its retired cached File object");
1678 return false;
1679 }
1680
1681 NOTICE("HOSTED-SYSCALL-TEST: PASS posix-path-lookup-lifetime");
1682 return true;
1683}
1684
1685class PollGenerationProbe : public NetworkSyscalls {
1686 public:
1687 PollGenerationProbe(Atomic<size_t>& queries, Atomic<size_t>& notifications,
1688 Atomic<size_t>& destructions)
1689 : NetworkSyscalls(AF_UNSPEC, SOCK_STREAM, 0),
1690 m_Queries(queries),
1691 m_Notifications(notifications),
1692 m_Destructions(destructions),
1693 m_Ready(0) {}
1694
1695 ~PollGenerationProbe() override {
1696 m_Destructions += 1;
1697 }
1698
1699 int connect(const struct sockaddr_storage*, socklen_t) override {
1700 return -1;
1701 }
1702
1703 ssize_t sendto_msg(const struct msghdr*, const SharedPointer<SocketRights>&) override {
1704 return -1;
1705 }
1706
1707 ssize_t recvfrom_msg(struct msghdr*, SharedPointer<SocketRights>*) override {
1708 return -1;
1709 }
1710
1711 int listen(int) override {
1712 return -1;
1713 }
1714
1715 int bind(const struct sockaddr_storage*, socklen_t) override {
1716 return -1;
1717 }
1718
1719 int accept(struct sockaddr_storage*, socklen_t*, int, DescriptorLease*) override {
1720 return -1;
1721 }
1722
1723 int getpeername(struct sockaddr_storage*, socklen_t*) override {
1724 return -1;
1725 }
1726
1727 int getsockname(struct sockaddr_storage*, socklen_t*) override {
1728 return -1;
1729 }
1730
1731 int setsockopt(int, int, const void*, socklen_t) override {
1732 return -1;
1733 }
1734
1735 int getsockopt(int, int, void*, socklen_t*) override {
1736 return -1;
1737 }
1738
1739 ReadyMask queryReady(bool reading, bool writing) override {
1740 m_Queries += 1;
1741 ReadyMask ready = ReadyNone;
1742 if (reading && m_Ready) {
1743 ready |= ReadyRead;
1744 }
1745 if (writing) {
1746 ready |= ReadyWrite;
1747 }
1748 return ready;
1749 }
1750
1751 void makeReadable() {
1752 m_Ready = 1;
1753 m_Notifications += 1;
1754 notifyReadiness(ReadyRead);
1755 }
1756
1757 private:
1758 Atomic<size_t>& m_Queries;
1759 Atomic<size_t>& m_Notifications;
1760 Atomic<size_t>& m_Destructions;
1761 Atomic<size_t> m_Ready;
1762};
1763
1764struct DescriptorCloseContext {
1765 DescriptorCloseContext(PosixSubsystem* subsystem, size_t fd)
1766 : subsystem(subsystem),
1767 fd(fd),
1768 release(0, false),
1769 entered(0),
1770 acquired(0),
1771 usedAfterClose(0),
1772 returned(0) {}
1773
1774 PosixSubsystem* subsystem;
1775 size_t fd;
1776 Semaphore release;
1777 Atomic<size_t> entered;
1778 Atomic<size_t> acquired;
1779 Atomic<size_t> usedAfterClose;
1780 Atomic<size_t> returned;
1781};
1782
1783int holdDescriptorAcrossBlock(void* parameter) {
1784 DescriptorCloseContext* context = reinterpret_cast<DescriptorCloseContext*>(parameter);
1785 DescriptorLease descriptor;
1786 context->acquired = context->subsystem->acquireFileDescriptor(context->fd, descriptor) ? 1 : 0;
1787 context->entered += 1;
1788
1789 if (!context->release.acquireForCompletion()) {
1790 context->returned += 1;
1791 return 1;
1792 }
1793
1794 if (descriptor && descriptor->fd == context->fd) {
1795 context->usedAfterClose += 1;
1796 }
1797 context->returned += 1;
1798 return 0;
1799}
1800
1801bool descriptorClosePinning(Process* kernelProcess) {
1802 constexpr size_t DescriptorNumber = 37;
1803 Process* process = new Process(kernelProcess);
1804 PosixSubsystem* subsystem = new PosixSubsystem;
1805 process->setSubsystem(subsystem);
1806
1807 Atomic<size_t> destructions(0);
1808 DescriptorRetirementProbe* probe = new DescriptorRetirementProbe(destructions);
1809 probe->fd = DescriptorNumber;
1810 subsystem->addFileDescriptor(DescriptorNumber, probe);
1811
1812 DescriptorCloseContext context(subsystem, DescriptorNumber);
1813 Thread* worker =
1814 new Thread(kernelProcess, holdDescriptorAcrossBlock, &context, nullptr, false, true, true);
1815 worker->setName("hosted descriptor pin holder");
1816
1817 bool passed = worker->start();
1818 bool blocked = false;
1819 for (size_t attempt = 0; attempt < HostedAttempts && passed; ++attempt) {
1820 Thread::WaitDebugInfo info = {};
1821 if (context.entered && worker->getWaitDebugInfo(info) && info.queue && info.queued &&
1822 info.channelOwner == &context.release && worker->getStatus() == Thread::Sleeping) {
1823 blocked = true;
1824 break;
1825 }
1827 }
1828
1829 passed = passed && blocked && context.acquired == 1;
1830 DescriptorLease closing;
1831 const bool closeAcquired = subsystem->acquireFileDescriptor(DescriptorNumber, closing);
1832 const bool closed = closeAcquired && subsystem->closeFileDescriptor(DescriptorNumber, closing);
1833 closing.reset();
1834 passed = passed && closed;
1835
1836 DescriptorLease unpublished;
1837 passed = passed && !subsystem->acquireFileDescriptor(DescriptorNumber, unpublished) &&
1838 destructions == 0;
1839
1840 context.release.release();
1841 passed = worker->join() && passed;
1842 passed = passed && context.returned == 1 && context.usedAfterClose == 1 && destructions == 1;
1843
1844 delete process;
1845
1846 if (!passed) {
1847 ERROR(
1848 "HOSTED-SYSCALL-TEST: FAIL descriptor-close-pinning: "
1849 "close did not unpublish immediately while retaining the active "
1850 "operation");
1851 return false;
1852 }
1853
1854 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-close-pinning");
1855 return true;
1856}
1857
1858bool descriptorCloseGeneration(Process* kernelProcess) {
1859 constexpr size_t DescriptorNumber = 38;
1860 Process* process = new Process(kernelProcess);
1861 PosixSubsystem* subsystem = new PosixSubsystem;
1862 process->setSubsystem(subsystem);
1863
1864 Atomic<size_t> oldDestructions(0);
1865 Atomic<size_t> replacementDestructions(0);
1866 DescriptorRetirementProbe* oldDescriptor = new DescriptorRetirementProbe(oldDestructions);
1867 oldDescriptor->fd = DescriptorNumber;
1868 oldDescriptor->setOffset(1);
1869 subsystem->addFileDescriptor(DescriptorNumber, oldDescriptor);
1870
1871 DescriptorLease oldLease;
1872 bool passed = subsystem->acquireFileDescriptor(DescriptorNumber, oldLease);
1873
1874 DescriptorRetirementProbe* replacement = new DescriptorRetirementProbe(replacementDestructions);
1875 replacement->fd = DescriptorNumber;
1876 replacement->setOffset(2);
1877 subsystem->addFileDescriptor(DescriptorNumber, replacement);
1878
1879 // An in-flight close of the old generation must not remove a descriptor
1880 // which has since reused the same numeric fd.
1881 passed = passed && !subsystem->closeFileDescriptor(DescriptorNumber, oldLease) &&
1882 oldDestructions == 0 && replacementDestructions == 0;
1883
1884 DescriptorLease replacementLease;
1885 passed = passed && subsystem->acquireFileDescriptor(DescriptorNumber, replacementLease) &&
1886 replacementLease->getOffset() == 2;
1887
1888 oldLease.reset();
1889 passed = passed && oldDestructions == 1 && replacementDestructions == 0;
1890
1891 const bool replacementClosed = subsystem->closeFileDescriptor(DescriptorNumber, replacementLease);
1892 DescriptorLease unpublished;
1893 passed = passed && replacementClosed &&
1894 !subsystem->acquireFileDescriptor(DescriptorNumber, unpublished) &&
1895 replacementDestructions == 0;
1896 replacementLease.reset();
1897 passed = passed && replacementDestructions == 1;
1898
1899 delete process;
1900
1901 if (!passed) {
1902 ERROR(
1903 "HOSTED-SYSCALL-TEST: FAIL descriptor-close-generation: "
1904 "an old close removed a reused descriptor generation");
1905 return false;
1906 }
1907
1908 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-close-generation");
1909 return true;
1910}
1911
1912struct DescriptorPositionContext {
1913 explicit DescriptorPositionContext(FileDescriptor* descriptor)
1914 : descriptor(descriptor), entered(0), acquired(0), observed(0) {}
1915
1916 FileDescriptor* descriptor;
1917 Atomic<size_t> entered;
1918 Atomic<size_t> acquired;
1919 Atomic<size_t> observed;
1920};
1921
1922class DescriptorPositionFile final : public File {
1923 public:
1924 explicit DescriptorPositionFile(bool seekable)
1925 : File(String("position-policy"), 0, 0, 0, 1, nullptr, 0, nullptr),
1926 m_Seekable(seekable),
1927 m_ReadOffset(~static_cast<uint64_t>(0)),
1928 m_WriteOffset(~static_cast<uint64_t>(0)),
1929 m_ReadCanBlock(true),
1930 m_WriteCanBlock(true) {}
1931
1932 bool isSeekable() const override {
1933 return m_Seekable;
1934 }
1935
1936 uint64_t readOffset() const {
1937 return m_ReadOffset;
1938 }
1939
1940 uint64_t writeOffset() const {
1941 return m_WriteOffset;
1942 }
1943
1944 bool readCanBlock() const {
1945 return m_ReadCanBlock;
1946 }
1947
1948 bool writeCanBlock() const {
1949 return m_WriteCanBlock;
1950 }
1951
1952 protected:
1953 bool isBytewise() const override {
1954 return true;
1955 }
1956
1957 uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t, bool canBlock) override {
1958 m_ReadOffset = location;
1959 m_ReadCanBlock = canBlock;
1960 return size;
1961 }
1962
1963 uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t, bool canBlock) override {
1964 m_WriteOffset = location;
1965 m_WriteCanBlock = canBlock;
1966 return size;
1967 }
1968
1969 private:
1970 bool m_Seekable;
1971 uint64_t m_ReadOffset;
1972 uint64_t m_WriteOffset;
1973 bool m_ReadCanBlock;
1974 bool m_WriteCanBlock;
1975};
1976
1977class DescriptorAppendFile final : public File {
1978 public:
1979 DescriptorAppendFile()
1980 : File(String("append-policy"), 0, 0, 0, 1, nullptr, 10, nullptr),
1981 m_WriteCount(0),
1982 m_WriteOffsets{0, 0, 0} {}
1983
1984 uint64_t writeOffset(size_t index) const {
1985 return m_WriteOffsets[index];
1986 }
1987
1988 size_t writeCount() const {
1989 return m_WriteCount;
1990 }
1991
1992 protected:
1993 bool isBytewise() const override {
1994 return true;
1995 }
1996
1997 uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t, bool) override {
1998 if (m_WriteCount < 3) {
1999 m_WriteOffsets[m_WriteCount] = location;
2000 }
2001 ++m_WriteCount;
2002 if (location + size > getSize()) {
2003 setSize(location + size);
2004 }
2005 return size;
2006 }
2007
2008 private:
2009 size_t m_WriteCount;
2010 uint64_t m_WriteOffsets[3];
2011};
2012
2013class ConcurrentDescriptorAppendFile final : public File {
2014 public:
2015 ConcurrentDescriptorAppendFile()
2016 : File(String("concurrent-append-policy"), 0, 0, 0, 1, nullptr, 10, nullptr),
2017 m_FirstWriteEntered(0, false),
2018 m_ReleaseFirstWrite(0, false),
2019 m_WriteCount(0),
2020 m_WriteOffsets{0, 0} {}
2021
2022 bool waitForFirstWrite() {
2023 return m_FirstWriteEntered.acquireForCompletion();
2024 }
2025
2026 void releaseFirstWrite() {
2027 m_ReleaseFirstWrite.release();
2028 }
2029
2030 size_t writeCount() const {
2031 return m_WriteCount;
2032 }
2033
2034 uint64_t writeOffset(size_t index) const {
2035 return m_WriteOffsets[index];
2036 }
2037
2038 protected:
2039 bool isBytewise() const override {
2040 return true;
2041 }
2042
2043 uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t, bool) override {
2044 const size_t slot = (m_WriteCount += 1) - 1;
2045 if (slot < 2) {
2046 m_WriteOffsets[slot] = location;
2047 }
2048 if (!slot) {
2049 m_FirstWriteEntered.release();
2050 if (!m_ReleaseFirstWrite.acquireForCompletion()) {
2051 return 0;
2052 }
2053 }
2054 if (location + size > getSize()) {
2055 setSize(location + size);
2056 }
2057 return size;
2058 }
2059
2060 private:
2061 Semaphore m_FirstWriteEntered;
2062 Semaphore m_ReleaseFirstWrite;
2063 Atomic<size_t> m_WriteCount;
2064 uint64_t m_WriteOffsets[2];
2065};
2066
2067struct ConcurrentDescriptorAppendContext {
2068 explicit ConcurrentDescriptorAppendContext(FileDescriptor* descriptor)
2069 : descriptor(descriptor), entered(0), returned(0), result(0) {}
2070
2071 FileDescriptor* descriptor;
2072 Atomic<size_t> entered;
2073 Atomic<size_t> returned;
2074 Atomic<uint64_t> result;
2075};
2076
2077int appendThroughDescriptor(void* parameter) {
2078 ConcurrentDescriptorAppendContext* context =
2079 reinterpret_cast<ConcurrentDescriptorAppendContext*>(parameter);
2080 char byte = 0;
2081 context->entered += 1;
2082 context->result = context->descriptor->write(1, reinterpret_cast<uintptr_t>(&byte));
2083 context->returned += 1;
2084 return context->result == 1 ? 0 : 1;
2085}
2086
2087bool concurrentDescriptorAppendSerialization(Process* kernelProcess, bool positionedSecond) {
2088 ConcurrentDescriptorAppendFile file;
2089 FileDescriptor first(&file, 0, 0, 0, O_WRONLY | O_APPEND);
2090 FileDescriptor second(&file, positionedSecond ? 20 : 0, 0, 0,
2091 positionedSecond ? O_WRONLY : O_WRONLY | O_APPEND);
2092 ConcurrentDescriptorAppendContext firstContext(&first);
2093 ConcurrentDescriptorAppendContext secondContext(&second);
2094
2095 Thread* firstWorker =
2096 new Thread(kernelProcess, appendThroughDescriptor, &firstContext, nullptr, false, true, true);
2097 Thread* secondWorker = new Thread(kernelProcess, appendThroughDescriptor, &secondContext, nullptr,
2098 false, true, true);
2099 firstWorker->setName("hosted first independent append");
2100 if (positionedSecond) {
2101 secondWorker->setName("hosted positioned append race");
2102 } else {
2103 secondWorker->setName("hosted second independent append");
2104 }
2105
2106 const bool firstStarted = firstWorker->start();
2107 const bool firstEntered = firstStarted && file.waitForFirstWrite();
2108 const bool secondStarted = firstEntered && secondWorker->start();
2109 bool secondBlocked = false;
2110 for (size_t attempt = 0; attempt < HostedAttempts && secondStarted; ++attempt) {
2111 Thread::WaitDebugInfo info = {};
2112 uintptr_t debugAddress = 0;
2113 if (secondContext.entered && !secondContext.returned && file.writeCount() == 1 &&
2114 secondWorker->getWaitDebugInfo(info) && info.queued &&
2115 secondWorker->getDebugState(debugAddress) == Thread::SemWait) {
2116 secondBlocked = true;
2117 break;
2118 }
2120 }
2121
2122 file.releaseFirstWrite();
2123 const bool firstJoined = firstStarted && firstWorker->joinForCompletion();
2124 const bool secondJoined = secondStarted && secondWorker->joinForCompletion();
2125 if (!firstStarted) {
2126 delete firstWorker;
2127 }
2128 if (!secondStarted) {
2129 delete secondWorker;
2130 }
2131
2132 const bool separateDescriptions =
2133 first.acquireOpenFileDescription().get() != second.acquireOpenFileDescription().get();
2134 const uint64_t expectedSecondOffset = positionedSecond ? 20 : 11;
2135 const uint64_t expectedSize = positionedSecond ? 21 : 12;
2136 const uint64_t expectedSecondPosition = positionedSecond ? 21 : 12;
2137 return firstStarted && firstEntered && secondStarted && secondBlocked && firstJoined &&
2138 secondJoined && firstContext.returned == 1 && secondContext.returned == 1 &&
2139 firstContext.result == 1 && secondContext.result == 1 && separateDescriptions &&
2140 file.writeCount() == 2 && file.writeOffset(0) == 10 &&
2141 file.writeOffset(1) == expectedSecondOffset && file.getSize() == expectedSize &&
2142 first.getOffset() == 11 && second.getOffset() == expectedSecondPosition;
2143}
2144
2145bool independentDescriptorAppendSerialization(Process* kernelProcess) {
2146 return concurrentDescriptorAppendSerialization(kernelProcess, false) &&
2147 concurrentDescriptorAppendSerialization(kernelProcess, true);
2148}
2149
2150bool descriptorOpenFileDescriptionState() {
2151 DescriptorPositionFile file(true);
2152 FileDescriptor source(&file, 0, 19, 0, O_RDWR | O_CLOEXEC);
2153 FileDescriptor alias(source);
2154 alias.setFlags(0);
2155 FileDescriptor::OpenFileDescriptionLease sourceDescription = source.acquireOpenFileDescription();
2156 FileDescriptor::OpenFileDescriptionLease aliasDescription = alias.acquireOpenFileDescription();
2157
2158 source.addStatusFlag(O_NONBLOCK | O_CLOEXEC);
2159 bool passed = source.getFlags() == FD_CLOEXEC && alias.getFlags() == 0 &&
2160 source.getStatusFlags() == (O_RDWR | O_NONBLOCK) &&
2161 alias.getStatusFlags() == (O_RDWR | O_NONBLOCK) &&
2162 sourceDescription == aliasDescription && sourceDescription->getFile() == &file &&
2163 !sourceDescription->getNetworkImpl() &&
2164 sourceDescription->descriptorOwnerCount() == 2;
2165
2166 {
2167 FileDescriptor third(alias);
2168 passed = passed && sourceDescription->descriptorOwnerCount() == 3;
2169 }
2170 passed = passed && sourceDescription->descriptorOwnerCount() == 2;
2171
2172 alias.setStatusFlags(O_APPEND | O_CLOEXEC);
2173 passed = passed && source.getStatusFlags() == (O_RDWR | O_APPEND) &&
2174 alias.getStatusFlags() == (O_RDWR | O_APPEND) && source.getFlags() == FD_CLOEXEC &&
2175 alias.getFlags() == 0;
2176
2177 source.removeStatusFlag(O_APPEND);
2178 passed = passed && source.getStatusFlags() == O_RDWR && alias.getStatusFlags() == O_RDWR;
2179
2180 if (!passed) {
2181 ERROR(
2182 "HOSTED-SYSCALL-TEST: FAIL descriptor-open-file-description-state: "
2183 "status flags were descriptor-local, access mode was lost, or CLOEXEC entered shared "
2184 "state");
2185 return false;
2186 }
2187
2188 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-open-file-description-state");
2189 return true;
2190}
2191
2192bool descriptorOpenFileDescriptionLifetime() {
2193 Atomic<size_t> fileDestructions(0);
2194 EstablishedAliasFileProbe* file = new EstablishedAliasFileProbe(fileDestructions);
2195 VFS::instance().trackFile(file);
2196 FileDescriptor* descriptor = new FileDescriptor(file, 0, 0, 0, O_RDWR);
2197 const bool baselineWasFinal = VFS::instance().untrackFile(file, false);
2199 descriptor->acquireOpenFileDescription();
2200
2201 delete descriptor;
2202 bool passed = !baselineWasFinal && !fileDestructions &&
2203 fileDescription->descriptorOwnerCount() == 0 && fileDescription->getFile() == file;
2204 fileDescription.reset();
2205 passed = passed && fileDestructions == 1;
2206 repairAliasFileProbe(file, fileDestructions);
2207
2208 Atomic<size_t> registrations(0);
2209 Atomic<size_t> unpolls(0);
2210 Atomic<size_t> networkDestructions(0);
2211 FileDescriptor* socketDescriptor = new FileDescriptor;
2213 new PollGenerationProbe(registrations, unpolls, networkDestructions));
2214 socketDescriptor->setNetworkImpl(network);
2215 network.reset();
2217 socketDescriptor->acquireOpenFileDescription();
2218
2219 delete socketDescriptor;
2220 passed = passed && !networkDestructions && socketDescription->descriptorOwnerCount() == 0 &&
2221 socketDescription->getNetworkImpl();
2222 socketDescription.reset();
2223 passed = passed && networkDestructions == 1;
2224
2225 if (!passed) {
2226 ERROR(
2227 "HOSTED-SYSCALL-TEST: FAIL descriptor-open-file-description-lifetime: "
2228 "an OFD lease did not retain its target independently of descriptor aliases");
2229 return false;
2230 }
2231
2232 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-open-file-description-lifetime");
2233 return true;
2234}
2235
2236bool descriptorAppendPolicy(Process* kernelProcess) {
2237 char byte = 0;
2238 DescriptorAppendFile file;
2239 FileDescriptor source(&file, 2, 0, 0, O_WRONLY | O_APPEND);
2240 FileDescriptor alias(source);
2241
2242 const bool firstWrite = source.write(3, reinterpret_cast<uintptr_t>(&byte)) == 3;
2243 source.setOffset(1);
2244 const bool secondWrite = alias.write(2, reinterpret_cast<uintptr_t>(&byte)) == 2;
2245
2246 alias.setStatusFlags(0);
2247 source.setOffset(5);
2248 const bool positionedWrite = source.write(1, reinterpret_cast<uintptr_t>(&byte)) == 1;
2249
2250 const bool independentSerialization = independentDescriptorAppendSerialization(kernelProcess);
2251 const bool passed = firstWrite && secondWrite && positionedWrite && independentSerialization &&
2252 file.writeCount() == 3 && file.writeOffset(0) == 10 &&
2253 file.writeOffset(1) == 13 && file.writeOffset(2) == 5 &&
2254 file.getSize() == 15 && source.getOffset() == 6 && alias.getOffset() == 6 &&
2255 source.getStatusFlags() == O_WRONLY;
2256 if (!passed) {
2257 ERROR(
2258 "HOSTED-SYSCALL-TEST: FAIL descriptor-append-policy: "
2259 "append did not serialize EOF selection across open descriptions or update the shared "
2260 "offset");
2261 return false;
2262 }
2263
2264 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-append-policy");
2265 return true;
2266}
2267
2268bool descriptorNonblockingPolicy() {
2269 char byte = 0;
2270 DescriptorPositionFile sequential(false);
2271 FileDescriptor source(&sequential, 0, 0, 0, O_RDWR | O_NONBLOCK);
2272 FileDescriptor alias(source);
2273
2274 const bool nonblockingRead = source.read(1, reinterpret_cast<uintptr_t>(&byte)) == 1;
2275 const bool nonblockingWrite = source.write(1, reinterpret_cast<uintptr_t>(&byte)) == 1;
2276 bool passed = nonblockingRead && nonblockingWrite && !sequential.readCanBlock() &&
2277 !sequential.writeCanBlock();
2278
2279 alias.removeStatusFlag(O_NONBLOCK);
2280 const bool blockingRead = source.read(1, reinterpret_cast<uintptr_t>(&byte)) == 1;
2281 const bool blockingWrite = source.write(1, reinterpret_cast<uintptr_t>(&byte)) == 1;
2282 passed = passed && blockingRead && blockingWrite && sequential.readCanBlock() &&
2283 sequential.writeCanBlock() && source.getStatusFlags() == O_RDWR &&
2284 alias.getStatusFlags() == O_RDWR;
2285
2286 if (!passed) {
2287 ERROR(
2288 "HOSTED-SYSCALL-TEST: FAIL descriptor-nonblocking-policy: "
2289 "O_NONBLOCK did not reach file I/O or did not propagate across aliases");
2290 return false;
2291 }
2292
2293 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-nonblocking-policy");
2294 return true;
2295}
2296
2297bool descriptorPositionPolicy() {
2298 char byte = 0;
2299 DescriptorPositionFile seekable(true);
2300 FileDescriptor positioned(&seekable, 40, 0, 0, O_RDWR);
2301 const bool positionedPassed = positioned.read(1, reinterpret_cast<uintptr_t>(&byte)) == 1 &&
2302 positioned.write(1, reinterpret_cast<uintptr_t>(&byte)) == 1 &&
2303 seekable.readOffset() == 40 && seekable.writeOffset() == 41 &&
2304 positioned.getOffset() == 42;
2305
2306 DescriptorPositionFile sequential(false);
2307 FileDescriptor unpositioned(&sequential, 40, 0, 0, O_RDWR);
2308 const bool unpositionedPassed = unpositioned.read(1, reinterpret_cast<uintptr_t>(&byte)) == 1 &&
2309 unpositioned.write(1, reinterpret_cast<uintptr_t>(&byte)) == 1 &&
2310 sequential.readOffset() == 0 && sequential.writeOffset() == 0 &&
2311 unpositioned.getOffset() == 40;
2312
2313 if (!positionedPassed || !unpositionedPassed) {
2314 ERROR(
2315 "HOSTED-SYSCALL-TEST: FAIL descriptor-position-policy: "
2316 "non-seekable I/O consumed a file position");
2317 return false;
2318 }
2319
2320 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-position-policy");
2321 return true;
2322}
2323
2324int advanceDescriptorPosition(void* parameter) {
2325 DescriptorPositionContext* context = reinterpret_cast<DescriptorPositionContext*>(parameter);
2326 context->entered += 1;
2327 FileDescriptor::PositionGuard position = context->descriptor->lockPosition();
2328 context->observed = position.offset();
2329 position.advanceOffset(1);
2330 context->acquired += 1;
2331 return 0;
2332}
2333
2334bool descriptorPositionAliasSerialization(Process* kernelProcess) {
2335 FileDescriptor source;
2336 source.setOffset(40);
2337 FileDescriptor alias(source);
2338 DescriptorPositionContext context(&alias);
2339
2340 Thread* worker =
2341 new Thread(kernelProcess, advanceDescriptorPosition, &context, nullptr, false, true, true);
2342 worker->setName("hosted descriptor position alias");
2343
2344 bool started = false;
2345 bool queued = false;
2346 {
2347 FileDescriptor::PositionGuard position = source.lockPosition();
2348 started = worker->start();
2349 for (size_t attempt = 0; attempt < HostedAttempts && started; ++attempt) {
2350 Thread::WaitDebugInfo info = {};
2351 uintptr_t debugAddress = 0;
2352 if (context.entered && !context.acquired && worker->getWaitDebugInfo(info) && info.queued &&
2353 worker->getDebugState(debugAddress) == Thread::SemWait) {
2354 queued = true;
2355 break;
2356 }
2358 }
2359 position.setOffset(41);
2360 }
2361
2362 const bool joined = started && worker->joinForCompletion();
2363 if (!started) {
2364 delete worker;
2365 }
2366 const bool passed = started && queued && joined && context.acquired == 1 &&
2367 context.observed == 41 && source.getOffset() == 42 && alias.getOffset() == 42;
2368 if (!passed) {
2369 ERROR(
2370 "HOSTED-SYSCALL-TEST: FAIL descriptor-position-alias-serialization: "
2371 "duplicated descriptors did not serialize access to their shared offset");
2372 return false;
2373 }
2374
2375 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-position-alias-serialization");
2376 return true;
2377}
2378
2379class VectorWriteFile final : public File {
2380 public:
2381 VectorWriteFile()
2382 : File(String("vector-write-policy"), 0, 0, 0, 1, nullptr, 0, nullptr),
2383 m_FirstWriteEntered(0, false),
2384 m_ReleaseFirstWrite(0, false),
2385 m_WriteCount(0),
2386 m_WriteOffsets{0, 0, 0},
2387 m_WriteValues{0, 0, 0} {}
2388
2389 bool waitForFirstWrite() {
2390 return m_FirstWriteEntered.acquireForCompletion();
2391 }
2392
2393 void releaseFirstWrite() {
2394 m_ReleaseFirstWrite.release();
2395 }
2396
2397 size_t writeCount() const {
2398 return m_WriteCount;
2399 }
2400
2401 uint64_t writeOffset(size_t index) const {
2402 return m_WriteOffsets[index];
2403 }
2404
2405 char writeValue(size_t index) const {
2406 return m_WriteValues[index];
2407 }
2408
2409 protected:
2410 bool isBytewise() const override {
2411 return true;
2412 }
2413
2414 uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool) override {
2415 const size_t slot = (m_WriteCount += 1) - 1;
2416 if (slot < 3) {
2417 m_WriteOffsets[slot] = location;
2418 m_WriteValues[slot] = size ? *reinterpret_cast<const char*>(buffer) : 0;
2419 }
2420 if (!slot) {
2421 m_FirstWriteEntered.release();
2422 if (!m_ReleaseFirstWrite.acquireForCompletion()) {
2423 return 0;
2424 }
2425 }
2426 if (location + size > getSize()) {
2427 setSize(location + size);
2428 }
2429 return size;
2430 }
2431
2432 private:
2433 Semaphore m_FirstWriteEntered;
2434 Semaphore m_ReleaseFirstWrite;
2435 Atomic<size_t> m_WriteCount;
2436 uint64_t m_WriteOffsets[3];
2437 char m_WriteValues[3];
2438};
2439
2440struct VectorWriteContext {
2441 explicit VectorWriteContext(size_t descriptor)
2442 : descriptor(descriptor), result(-2), error(0), returned(0) {}
2443
2444 size_t descriptor;
2445 int result;
2446 int error;
2447 Atomic<size_t> returned;
2448};
2449
2450int writeThroughVector(void* parameter) {
2451 VectorWriteContext* context = reinterpret_cast<VectorWriteContext*>(parameter);
2452 Thread* thread = Processor::information().getCurrentThread();
2453 char bytes[2] = {'a', 'b'};
2454 struct iovec vectors[2] = {{&bytes[0], 1}, {&bytes[1], 1}};
2455 thread->setErrno(0);
2456 context->result = posix_writev(static_cast<int>(context->descriptor), vectors, 2);
2457 context->error = thread->getErrno();
2458 context->returned += 1;
2459 return 0;
2460}
2461
2462int writeThroughAlias(void* parameter) {
2463 VectorWriteContext* context = reinterpret_cast<VectorWriteContext*>(parameter);
2464 Thread* thread = Processor::information().getCurrentThread();
2465 char byte = 'c';
2466 thread->setErrno(0);
2467 context->result = posix_write(static_cast<int>(context->descriptor), &byte, 1, false);
2468 context->error = thread->getErrno();
2469 context->returned += 1;
2470 return 0;
2471}
2472
2473bool descriptorVectorIoSerialization(Process* kernelProcess) {
2474 constexpr size_t SourceDescriptor = 74;
2475 constexpr size_t AliasDescriptor = 75;
2476 Process* process = new Process(kernelProcess);
2477 PosixSubsystem* subsystem = new PosixSubsystem;
2478 process->setSubsystem(subsystem);
2479
2480 VectorWriteFile original;
2481 DescriptorPositionFile replacement(true);
2482 FileDescriptor* source = new FileDescriptor(&original, 0, SourceDescriptor, 0, O_WRONLY);
2483 FileDescriptor* alias = new FileDescriptor(*source);
2484 alias->fd = AliasDescriptor;
2485 subsystem->addFileDescriptor(SourceDescriptor, source);
2486 subsystem->addFileDescriptor(AliasDescriptor, alias);
2487
2488 VectorWriteContext vectorContext(SourceDescriptor);
2489 VectorWriteContext aliasContext(AliasDescriptor);
2490 Thread* vectorWorker =
2491 new Thread(process, writeThroughVector, &vectorContext, nullptr, false, true, true);
2492 Thread* aliasWorker =
2493 new Thread(process, writeThroughAlias, &aliasContext, nullptr, false, true, true);
2494 vectorWorker->setName("hosted vector write generation");
2495 aliasWorker->setName("hosted vector write alias");
2496
2497 const bool vectorStarted = vectorWorker->start();
2498 const bool firstEntered = vectorStarted && original.waitForFirstWrite();
2499 const bool aliasStarted = firstEntered && aliasWorker->start();
2500 bool aliasBlocked = false;
2501 for (size_t attempt = 0; attempt < HostedAttempts && aliasStarted; ++attempt) {
2502 Thread::WaitDebugInfo info = {};
2503 uintptr_t debugAddress = 0;
2504 if (!aliasContext.returned && original.writeCount() == 1 &&
2505 aliasWorker->getWaitDebugInfo(info) && info.queued &&
2506 aliasWorker->getDebugState(debugAddress) == Thread::SemWait) {
2507 aliasBlocked = true;
2508 break;
2509 }
2511 }
2512
2513 DescriptorLease closingSource;
2514 const bool sourceAcquired = subsystem->acquireFileDescriptor(SourceDescriptor, closingSource);
2515 const bool sourceClosed =
2516 sourceAcquired && subsystem->closeFileDescriptor(SourceDescriptor, closingSource);
2517 closingSource.reset();
2518 FileDescriptor* replacementDescriptor =
2519 new FileDescriptor(&replacement, 0, SourceDescriptor, 0, O_WRONLY);
2520 subsystem->addFileDescriptor(SourceDescriptor, replacementDescriptor);
2521
2522 original.releaseFirstWrite();
2523 const bool vectorJoined = vectorStarted && vectorWorker->joinForCompletion();
2524 const bool aliasJoined = aliasStarted && aliasWorker->joinForCompletion();
2525 if (!vectorStarted) {
2526 delete vectorWorker;
2527 }
2528 if (!aliasStarted) {
2529 delete aliasWorker;
2530 }
2531
2532 bool passed = vectorStarted && firstEntered && aliasStarted && aliasBlocked && sourceClosed &&
2533 vectorJoined && aliasJoined && vectorContext.returned == 1 &&
2534 vectorContext.result == 2 && vectorContext.error == 0 &&
2535 aliasContext.returned == 1 && aliasContext.result == 1 && aliasContext.error == 0 &&
2536 original.writeCount() == 2 && original.writeOffset(0) == 0 &&
2537 original.writeOffset(1) == 2 && original.writeValue(0) == 'a' &&
2538 original.writeValue(1) == 'c' && original.getSize() == 3 &&
2539 replacement.writeOffset() == ~static_cast<uint64_t>(0);
2540
2541 DescriptorLease closingAlias;
2542 const bool aliasAcquired = subsystem->acquireFileDescriptor(AliasDescriptor, closingAlias);
2543 const bool aliasClosed =
2544 aliasAcquired && subsystem->closeFileDescriptor(AliasDescriptor, closingAlias);
2545 closingAlias.reset();
2546 DescriptorLease closingReplacement;
2547 const bool replacementAcquired =
2548 subsystem->acquireFileDescriptor(SourceDescriptor, closingReplacement);
2549 const bool replacementClosed =
2550 replacementAcquired && subsystem->closeFileDescriptor(SourceDescriptor, closingReplacement);
2551 closingReplacement.reset();
2552 passed = passed && aliasClosed && replacementClosed;
2553 delete process;
2554
2555 if (!passed) {
2556 ERROR(
2557 "HOSTED-SYSCALL-TEST: FAIL descriptor-vector-io-serialization: "
2558 "writev switched descriptor generations or released the shared offset between vectors");
2559 return false;
2560 }
2561
2562 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-vector-io-serialization");
2563 return true;
2564}
2565
2566struct DescriptorDupContractContext {
2567 DescriptorDupContractContext(PosixSubsystem* subsystem, size_t sourceFd, size_t occupiedFd,
2568 size_t minimum,
2569 const FileDescriptor::OpenFileDescriptionLease& sourceDescription,
2570 const FileDescriptor::OpenFileDescriptionLease& occupiedDescription)
2571 : subsystem(subsystem),
2572 sourceFd(sourceFd),
2573 occupiedFd(occupiedFd),
2574 minimum(minimum),
2575 sourceDescription(sourceDescription),
2576 occupiedDescription(occupiedDescription),
2577 duplicateResult(-2),
2578 duplicateError(0),
2579 occupiedIntact(false),
2580 duplicateAliasesSource(false),
2581 duplicateFlags(-1),
2582 duplicateClosed(false),
2583 ordinaryAllocation(static_cast<size_t>(-1)),
2584 sameInvalidResult(-2),
2585 sameInvalidError(0),
2586 returned(0) {}
2587
2588 PosixSubsystem* subsystem;
2589 size_t sourceFd;
2590 size_t occupiedFd;
2591 size_t minimum;
2593 FileDescriptor::OpenFileDescriptionLease occupiedDescription;
2594 int duplicateResult;
2595 int duplicateError;
2596 bool occupiedIntact;
2597 bool duplicateAliasesSource;
2598 int duplicateFlags;
2599 bool duplicateClosed;
2600 size_t ordinaryAllocation;
2601 int sameInvalidResult;
2602 int sameInvalidError;
2603 Atomic<size_t> returned;
2604};
2605
2606int exerciseDescriptorDupContract(void* parameter) {
2607 DescriptorDupContractContext* context =
2608 reinterpret_cast<DescriptorDupContractContext*>(parameter);
2609 Thread* thread = Processor::information().getCurrentThread();
2610
2611 thread->setErrno(0);
2612 context->duplicateResult = posix_fcntl(static_cast<int>(context->sourceFd), F_DUPFD,
2613 reinterpret_cast<void*>(context->minimum));
2614 context->duplicateError = thread->getErrno();
2615
2616 DescriptorLease occupied;
2617 if (context->subsystem->acquireFileDescriptor(context->occupiedFd, occupied)) {
2618 context->occupiedIntact =
2619 occupied->acquireOpenFileDescription().get() == context->occupiedDescription.get();
2620 }
2621 occupied.reset();
2622
2623 DescriptorLease duplicate;
2624 if (context->duplicateResult >= 0 &&
2625 context->subsystem->acquireFileDescriptor(context->duplicateResult, duplicate)) {
2626 context->duplicateAliasesSource =
2627 duplicate->acquireOpenFileDescription().get() == context->sourceDescription.get();
2628 context->duplicateFlags = duplicate->getFlags();
2629 context->duplicateClosed = context->subsystem->closeFileDescriptor(
2630 static_cast<size_t>(context->duplicateResult), duplicate);
2631 }
2632 duplicate.reset();
2633
2634 context->ordinaryAllocation = context->subsystem->getFd();
2635
2636 constexpr int InvalidDescriptor = 80;
2637 thread->setErrno(0);
2638 context->sameInvalidResult = posix_dup2(InvalidDescriptor, InvalidDescriptor);
2639 context->sameInvalidError = thread->getErrno();
2640 context->returned += 1;
2641 return 0;
2642}
2643
2644bool descriptorDupContract(Process* kernelProcess) {
2645 constexpr size_t SourceDescriptor = 70;
2646 constexpr size_t MinimumDescriptor = 72;
2647 constexpr size_t ExpectedDescriptor = 73;
2648
2649 Process* process = new Process(kernelProcess);
2650 PosixSubsystem* subsystem = new PosixSubsystem;
2651 process->setSubsystem(subsystem);
2652 File* sourceFile = new File;
2653 File* occupiedFile = new File;
2654 FileDescriptor* source =
2655 new FileDescriptor(sourceFile, 0, SourceDescriptor, FD_CLOEXEC, O_RDONLY);
2656 FileDescriptor* occupied = new FileDescriptor(occupiedFile, 0, MinimumDescriptor, 0, O_RDONLY);
2657 subsystem->addFileDescriptor(SourceDescriptor, source);
2658 subsystem->addFileDescriptor(MinimumDescriptor, occupied);
2659
2660 DescriptorDupContractContext context(subsystem, SourceDescriptor, MinimumDescriptor,
2661 MinimumDescriptor, source->acquireOpenFileDescription(),
2662 occupied->acquireOpenFileDescription());
2663 Thread* worker =
2664 new Thread(process, exerciseDescriptorDupContract, &context, nullptr, false, true, true);
2665 worker->setName("hosted descriptor duplication contract");
2666 const bool started = worker->start();
2667 const bool joined = started && worker->joinForCompletion();
2668 if (!started) {
2669 delete worker;
2670 }
2671
2672 const bool passed =
2673 started && joined && context.returned == 1 &&
2674 context.duplicateResult == static_cast<int>(ExpectedDescriptor) &&
2675 context.duplicateError == 0 && context.occupiedIntact && context.duplicateAliasesSource &&
2676 context.duplicateFlags == 0 && context.duplicateClosed && context.ordinaryAllocation == 0 &&
2677 context.sameInvalidResult == -1 && context.sameInvalidError == Error::BadFileDescriptor;
2678
2679 delete process;
2680 context.sourceDescription.reset();
2681 context.occupiedDescription.reset();
2682 delete sourceFile;
2683 delete occupiedFile;
2684
2685 if (!passed) {
2686 ERROR(
2687 "HOSTED-SYSCALL-TEST: FAIL descriptor-dup-contract: "
2688 "F_DUPFD replaced an occupied descriptor, hid a lower hole, or dup2 accepted an invalid "
2689 "same-fd source");
2690 return false;
2691 }
2692
2693 NOTICE("HOSTED-SYSCALL-TEST: PASS descriptor-dup-contract");
2694 return true;
2695}
2696
2697struct PollCloseReuseContext {
2698 explicit PollCloseReuseContext(size_t fd)
2699 : descriptor{static_cast<int>(fd), POLLIN, 0}, result(-2), entered(0), returned(0) {}
2700
2701 struct pollfd descriptor;
2702 Atomic<int> result;
2703 Atomic<size_t> entered;
2704 Atomic<size_t> returned;
2705};
2706
2707bool selectProjectionContract() {
2708 constexpr unsigned int ReadResult = 1U;
2709 constexpr unsigned int WriteResult = 1U << 1;
2710 constexpr unsigned int ExceptionalResult = 1U << 2;
2711 constexpr unsigned int OneReady = 1U << 8;
2712 constexpr unsigned int TwoReady = 2U << 8;
2713 constexpr unsigned int ThreeReady = 3U << 8;
2714
2715 const unsigned int hangup = posixSelectProjectionForTest(POLLHUP, true, true, true);
2716 const unsigned int error = posixSelectProjectionForTest(POLLERR, true, true, true);
2717 const unsigned int priority = posixSelectProjectionForTest(POLLPRI, true, true, true);
2718 const unsigned int all =
2719 posixSelectProjectionForTest(POLLIN | POLLOUT | POLLPRI, true, true, true);
2720 const unsigned int writeOnlyError = posixSelectProjectionForTest(POLLERR, false, true, false);
2721
2722 const bool projectionsPassed =
2723 hangup == (OneReady | ReadResult) && error == (TwoReady | ReadResult | WriteResult) &&
2724 priority == (OneReady | ExceptionalResult) &&
2725 all == (ThreeReady | ReadResult | WriteResult | ExceptionalResult) &&
2726 writeOnlyError == (OneReady | WriteResult);
2727
2728 if (!projectionsPassed) {
2729 ERROR(
2730 "HOSTED-SYSCALL-TEST: FAIL select-projection: "
2731 "readiness projection or return-bit counting was incorrect");
2732 return false;
2733 }
2734
2735 NOTICE("HOSTED-SYSCALL-TEST: PASS select-projection");
2736 return true;
2737}
2738
2739struct PipePollReadinessContext {
2740 PipePollReadinessContext(size_t readFd, size_t writeFd)
2741 : readFd(readFd),
2742 writeFd(writeFd),
2743 pollEntryGate(0, false),
2744 firstPhaseGate(0, false),
2745 eofGate(0, false),
2746 pollEntered(0),
2747 firstPhaseReturned(0),
2748 returned(0),
2749 emptyReadResult(-2),
2750 emptyReadError(0),
2751 firstPollResult(-2),
2752 firstPollEvents(0),
2753 firstReadResult(-2),
2754 fillWriteResult(-2),
2755 fullWriteResult(-2),
2756 fullWriteError(0),
2757 atomicSetupDrainResult(-2),
2758 atomicVectorResult(-2),
2759 atomicVectorError(0),
2760 drainResult(-2),
2761 overBoundaryWriteResult(-2),
2762 overBoundaryDrainResult(-2),
2763 eofPollResult(-2),
2764 eofPollEvents(0),
2765 eofReadResult(-2) {}
2766
2767 size_t readFd;
2768 size_t writeFd;
2769 Semaphore pollEntryGate;
2770 Semaphore firstPhaseGate;
2771 Semaphore eofGate;
2772 Atomic<size_t> pollEntered;
2773 Atomic<size_t> firstPhaseReturned;
2774 Atomic<size_t> returned;
2775 int emptyReadResult;
2776 int emptyReadError;
2777 int firstPollResult;
2778 short firstPollEvents;
2779 int firstReadResult;
2780 int fillWriteResult;
2781 int fullWriteResult;
2782 int fullWriteError;
2783 int atomicSetupDrainResult;
2784 int atomicVectorResult;
2785 int atomicVectorError;
2786 int drainResult;
2787 int overBoundaryWriteResult;
2788 int overBoundaryDrainResult;
2789 int eofPollResult;
2790 short eofPollEvents;
2791 int eofReadResult;
2792};
2793
2794struct EpollReadinessContext {
2795 EpollReadinessContext(PosixSubsystem* subsystem, const SharedPointer<EpollInstance>& instance,
2796 size_t readFd, size_t writeFd, size_t aliasFd, size_t regularFd)
2797 : subsystem(subsystem),
2798 instance(instance),
2799 readDescription(),
2800 readFd(readFd),
2801 writeFd(writeFd),
2802 aliasFd(aliasFd),
2803 regularFd(regularFd),
2804 waitEntryGate(0, false),
2805 waitEntered(0),
2806 returned(0),
2807 addResult(-2),
2808 regularAddResult(-2),
2809 regularAddError(0),
2810 createdFd(-2),
2811 createdDescriptorAcquired(false),
2812 createdStatusFlags(-1),
2813 createdDescriptorFlags(-1),
2814 createdCloseResult(false),
2815 duplicateAddResult(-2),
2816 duplicateAddError(0),
2817 exclusiveModifyResult(-2),
2818 exclusiveModifyError(0),
2819 firstWaitResult(-2),
2820 firstWaitEvents(0),
2821 firstWaitData(0),
2822 repeatedWaitResult(-2),
2823 repeatedWaitEvents(0),
2824 repeatedWaitData(0),
2825 sameReadyWriteResult(-2),
2826 sameReadyWaitResult(-2),
2827 firstDrainResult(-2),
2828 drainedWaitResult(-2),
2829 transitionWriteResult(-2),
2830 transitionWaitResult(-2),
2831 transitionWaitEvents(0),
2832 transitionWaitData(0),
2833 transitionDrainResult(-2),
2834 levelModifyResult(-2),
2835 levelWriteResult(-2),
2836 levelWaitResult(-2),
2837 levelWaitEvents(0),
2838 levelWaitData(0),
2839 levelRepeatedWaitResult(-2),
2840 levelDrainResult(-2),
2841 oneShotModifyResult(-2),
2842 oneShotWriteResult(-2),
2843 oneShotWaitResult(-2),
2844 oneShotWaitEvents(0),
2845 oneShotWaitData(0),
2846 oneShotSuppressedResult(-2),
2847 rearmResult(-2),
2848 rearmedWaitResult(-2),
2849 rearmedWaitEvents(0),
2850 rearmedWaitData(0),
2851 oneShotDrainResult(-2),
2852 deleteResult(-2),
2853 postDeleteWriteResult(-2),
2854 postDeleteWaitResult(-2),
2855 postDeleteDrainResult(-2),
2856 eventFd(-2),
2857 eventFdAddResult(-2),
2858 eventFdFirstWriteResult(-2),
2859 eventFdFirstWaitResult(-2),
2860 eventFdFirstWaitEvents(0),
2861 eventFdFirstWaitData(0),
2862 eventFdSecondWriteResult(-2),
2863 eventFdSecondWaitResult(-2),
2864 eventFdSecondWaitEvents(0),
2865 eventFdSecondWaitData(0),
2866 eventFdReadResult(-2),
2867 eventFdReadValue(0),
2868 eventFdDeleteResult(-2),
2869 eventFdCloseResult(false),
2870 aliasAddResult(-2),
2871 originalCloseResult(false),
2872 ownersAfterOriginalClose(static_cast<size_t>(-1)),
2873 aliasWriteResult(-2),
2874 aliasWaitResult(-2),
2875 aliasWaitEvents(0),
2876 aliasWaitData(0),
2877 aliasDrainResult(-2),
2878 aliasCloseResult(false),
2879 ownersAfterAliasClose(static_cast<size_t>(-1)),
2880 prunedWaitResult(-2),
2881 descriptionRefsAfterPrune(static_cast<size_t>(-1)) {}
2882
2883 PosixSubsystem* subsystem;
2886 size_t readFd;
2887 size_t writeFd;
2888 size_t aliasFd;
2889 size_t regularFd;
2890 Semaphore waitEntryGate;
2891 Atomic<size_t> waitEntered;
2892 Atomic<size_t> returned;
2893 int addResult;
2894 int regularAddResult;
2895 int regularAddError;
2896 int createdFd;
2897 bool createdDescriptorAcquired;
2898 int createdStatusFlags;
2899 int createdDescriptorFlags;
2900 bool createdCloseResult;
2901 int duplicateAddResult;
2902 int duplicateAddError;
2903 int exclusiveModifyResult;
2904 int exclusiveModifyError;
2905 int firstWaitResult;
2906 uint32_t firstWaitEvents;
2907 uint64_t firstWaitData;
2908 int repeatedWaitResult;
2909 uint32_t repeatedWaitEvents;
2910 uint64_t repeatedWaitData;
2911 int sameReadyWriteResult;
2912 int sameReadyWaitResult;
2913 int firstDrainResult;
2914 int drainedWaitResult;
2915 int transitionWriteResult;
2916 int transitionWaitResult;
2917 uint32_t transitionWaitEvents;
2918 uint64_t transitionWaitData;
2919 int transitionDrainResult;
2920 int levelModifyResult;
2921 int levelWriteResult;
2922 int levelWaitResult;
2923 uint32_t levelWaitEvents;
2924 uint64_t levelWaitData;
2925 int levelRepeatedWaitResult;
2926 int levelDrainResult;
2927 int oneShotModifyResult;
2928 int oneShotWriteResult;
2929 int oneShotWaitResult;
2930 uint32_t oneShotWaitEvents;
2931 uint64_t oneShotWaitData;
2932 int oneShotSuppressedResult;
2933 int rearmResult;
2934 int rearmedWaitResult;
2935 uint32_t rearmedWaitEvents;
2936 uint64_t rearmedWaitData;
2937 int oneShotDrainResult;
2938 int deleteResult;
2939 int postDeleteWriteResult;
2940 int postDeleteWaitResult;
2941 int postDeleteDrainResult;
2942 int eventFd;
2943 int eventFdAddResult;
2944 int eventFdFirstWriteResult;
2945 int eventFdFirstWaitResult;
2946 uint32_t eventFdFirstWaitEvents;
2947 uint64_t eventFdFirstWaitData;
2948 int eventFdSecondWriteResult;
2949 int eventFdSecondWaitResult;
2950 uint32_t eventFdSecondWaitEvents;
2951 uint64_t eventFdSecondWaitData;
2952 int eventFdReadResult;
2953 uint64_t eventFdReadValue;
2954 int eventFdDeleteResult;
2955 bool eventFdCloseResult;
2956 int aliasAddResult;
2957 bool originalCloseResult;
2958 size_t ownersAfterOriginalClose;
2959 int aliasWriteResult;
2960 int aliasWaitResult;
2961 uint32_t aliasWaitEvents;
2962 uint64_t aliasWaitData;
2963 int aliasDrainResult;
2964 bool aliasCloseResult;
2965 size_t ownersAfterAliasClose;
2966 int prunedWaitResult;
2967 size_t descriptionRefsAfterPrune;
2968};
2969
2970class ReorderedReadinessFile final : public File {
2971 public:
2972 ReorderedReadinessFile()
2973 : File(), m_ReadinessLock(), m_Readable(true), m_Writable(true), m_Generations() {
2974 m_Generations.read = 1;
2975 m_Generations.write = 1;
2976 }
2977
2978 ReadyMask queryReady(bool reading, bool writing) override {
2979 LockGuard<Mutex> guard(m_ReadinessLock);
2980 ReadyMask ready = ReadyNone;
2981 if (reading && m_Readable) {
2982 ready |= ReadyRead;
2983 }
2984 if (writing && m_Writable) {
2985 ready |= ReadyWrite;
2986 }
2987 return ready;
2988 }
2989
2991 LockGuard<Mutex> guard(m_ReadinessLock);
2992 return m_Generations;
2993 }
2994
2995 bool supportsReadinessNotifications() const override {
2996 return true;
2997 }
2998
2999 void setReady(bool readable, bool writable) {
3000 LockGuard<Mutex> guard(m_ReadinessLock);
3001 if (!m_Readable && readable) {
3002 ++m_Generations.read;
3003 }
3004 if (!m_Writable && writable) {
3005 ++m_Generations.write;
3006 }
3007 m_Readable = readable;
3008 m_Writable = writable;
3009 }
3010
3011 void publishReadiness() {
3012 dataChanged();
3013 }
3014
3015 private:
3016 Mutex m_ReadinessLock;
3017 bool m_Readable;
3018 bool m_Writable;
3019 ReadinessGenerations m_Generations;
3020};
3021
3022struct ReorderedEpollContext {
3023 ReorderedEpollContext(const SharedPointer<EpollInstance>& instance, size_t fd,
3024 ReorderedReadinessFile* file)
3025 : instance(instance),
3026 fd(fd),
3027 file(file),
3028 initialComplete(0, false),
3029 drainMutated(0, false),
3030 publishDrain(0, false),
3031 collectFinal(0, false),
3032 initialWaitResult(-2),
3033 initialWaitEvents(0),
3034 initialWaitData(0),
3035 finalWaitResult(-2),
3036 finalWaitEvents(0),
3037 finalWaitData(0),
3038 addResult(-2),
3039 deleteResult(-2),
3040 waiterReturned(0),
3041 drainReturned(0) {}
3042
3044 size_t fd;
3045 ReorderedReadinessFile* file;
3046 Semaphore initialComplete;
3047 Semaphore drainMutated;
3048 Semaphore publishDrain;
3049 Semaphore collectFinal;
3050 int initialWaitResult;
3051 uint32_t initialWaitEvents;
3052 uint64_t initialWaitData;
3053 int finalWaitResult;
3054 uint32_t finalWaitEvents;
3055 uint64_t finalWaitData;
3056 int addResult;
3057 int deleteResult;
3058 Atomic<size_t> waiterReturned;
3059 Atomic<size_t> drainReturned;
3060};
3061
3062int waitAcrossReorderedReadiness(void* parameter) {
3063 ReorderedEpollContext* context = reinterpret_cast<ReorderedEpollContext*>(parameter);
3064 LinuxEpollEvent event = {LinuxEpoll::In | LinuxEpoll::Out | LinuxEpoll::EdgeTriggered,
3065 EpollReorderedData};
3066 context->addResult =
3067 context->instance->control(LinuxEpoll::ControlAdd, static_cast<int>(context->fd), &event);
3068
3069 LinuxEpollEvent result = {};
3070 context->initialWaitResult = context->instance->wait(&result, 1, 0);
3071 context->initialWaitEvents = result.events;
3072 context->initialWaitData = result.data;
3073 context->initialComplete.release();
3074
3075 if (context->collectFinal.acquireForCompletion()) {
3076 result = {};
3077 context->finalWaitResult = context->instance->wait(&result, 1, 0);
3078 context->finalWaitEvents = result.events;
3079 context->finalWaitData = result.data;
3080 context->deleteResult = context->instance->control(LinuxEpoll::ControlDelete,
3081 static_cast<int>(context->fd), nullptr);
3082 }
3083
3084 context->waiterReturned += 1;
3085 return 0;
3086}
3087
3088int publishDelayedDrain(void* parameter) {
3089 ReorderedEpollContext* context = reinterpret_cast<ReorderedEpollContext*>(parameter);
3090 context->file->setReady(false, false);
3091 context->drainMutated.release();
3092 if (context->publishDrain.acquireForCompletion()) {
3093 context->file->publishReadiness();
3094 }
3095 context->drainReturned += 1;
3096 return 0;
3097}
3098
3099class ReorderedFifo final : public Pipe {
3100 public:
3101 ReorderedFifo() : Pipe(String("hosted-reordered-fifo"), 0, 0, 0, 0, nullptr, 0, nullptr, false) {}
3102
3103 void reopenReaderWithoutPublishing() {
3104 LockGuard<Mutex> guard(m_Lock);
3105 m_Buffer.enableReads();
3106 ++m_nReaders;
3107 m_ReaderCondition.broadcast();
3108 }
3109
3110 void publishReopen() {
3111 dataChanged();
3112 }
3113};
3114
3115struct ReorderedFifoEpollContext {
3116 ReorderedFifoEpollContext(const SharedPointer<EpollInstance>& instance, size_t fd)
3117 : instance(instance),
3118 fd(fd),
3119 initialComplete(0, false),
3120 collectFinal(0, false),
3121 addResult(-2),
3122 initialWaitResult(-2),
3123 initialWaitEvents(0),
3124 initialWaitData(0),
3125 finalWaitResult(-2),
3126 finalWaitEvents(0),
3127 finalWaitData(0),
3128 deleteResult(-2),
3129 returned(0) {}
3130
3132 size_t fd;
3133 Semaphore initialComplete;
3134 Semaphore collectFinal;
3135 int addResult;
3136 int initialWaitResult;
3137 uint32_t initialWaitEvents;
3138 uint64_t initialWaitData;
3139 int finalWaitResult;
3140 uint32_t finalWaitEvents;
3141 uint64_t finalWaitData;
3142 int deleteResult;
3143 Atomic<size_t> returned;
3144};
3145
3146int waitAcrossReorderedFifoReopen(void* parameter) {
3147 ReorderedFifoEpollContext* context = reinterpret_cast<ReorderedFifoEpollContext*>(parameter);
3148 LinuxEpollEvent interest = {LinuxEpoll::Out | LinuxEpoll::EdgeTriggered, EpollFifoData};
3149 context->addResult =
3150 context->instance->control(LinuxEpoll::ControlAdd, static_cast<int>(context->fd), &interest);
3151
3152 LinuxEpollEvent event = {};
3153 context->initialWaitResult = context->instance->wait(&event, 1, 0);
3154 context->initialWaitEvents = event.events;
3155 context->initialWaitData = event.data;
3156 context->initialComplete.release();
3157
3158 if (context->collectFinal.acquireForCompletion()) {
3159 event = {};
3160 context->finalWaitResult = context->instance->wait(&event, 1, 0);
3161 context->finalWaitEvents = event.events;
3162 context->finalWaitData = event.data;
3163 context->deleteResult = context->instance->control(LinuxEpoll::ControlDelete,
3164 static_cast<int>(context->fd), nullptr);
3165 }
3166
3167 context->returned += 1;
3168 return 0;
3169}
3170
3171int pollPipeReadiness(void* parameter) {
3172 PipePollReadinessContext* context = reinterpret_cast<PipePollReadinessContext*>(parameter);
3173 Thread* thread = Processor::information().getCurrentThread();
3174 char byte = 0;
3175
3176 thread->setErrno(0);
3177 context->emptyReadResult = posix_read(context->readFd, &byte, 1);
3178 context->emptyReadError = thread->getErrno();
3179
3180 struct pollfd readable = {static_cast<int>(context->readFd), POLLIN, 0};
3181 context->pollEntered += 1;
3182 context->pollEntryGate.release();
3183 context->firstPollResult = posix_poll_safe(&readable, 1, PollCloseReuseTimeoutMilliseconds);
3184 context->firstPollEvents = readable.revents;
3185 context->firstReadResult = posix_read(context->readFd, &byte, 1);
3186
3187 char fill[PIPE_BUF_MAX] = {};
3188 context->fillWriteResult = posix_write(context->writeFd, fill, sizeof(fill), false);
3189 thread->setErrno(0);
3190 context->fullWriteResult = posix_write(context->writeFd, &byte, 1, false);
3191 context->fullWriteError = thread->getErrno();
3192 context->atomicSetupDrainResult = posix_read(context->readFd, &byte, 1);
3193 char vectorBytes[2] = {1, 2};
3194 struct iovec vectors[2] = {{&vectorBytes[0], 1}, {&vectorBytes[1], 1}};
3195 thread->setErrno(0);
3196 context->atomicVectorResult = posix_writev(context->writeFd, vectors, 2);
3197 context->atomicVectorError = thread->getErrno();
3198 context->drainResult = posix_read(context->readFd, fill, sizeof(fill));
3199 char overBoundary[PIPE_BUF_MAX + 1] = {};
3200 context->overBoundaryWriteResult =
3201 posix_write(context->writeFd, overBoundary, sizeof(overBoundary), false);
3202 context->overBoundaryDrainResult = posix_read(context->readFd, fill, sizeof(fill));
3203 context->firstPhaseReturned += 1;
3204 context->firstPhaseGate.release();
3205
3206 if (!context->eofGate.acquireForCompletion()) {
3207 context->returned += 1;
3208 return 1;
3209 }
3210
3211 struct pollfd eof = {static_cast<int>(context->readFd), POLLIN, 0};
3212 context->eofPollResult = posix_poll_safe(&eof, 1, PollCloseReuseTimeoutMilliseconds);
3213 context->eofPollEvents = eof.revents;
3214 context->eofReadResult = posix_read(context->readFd, &byte, 1);
3215 context->returned += 1;
3216 return 0;
3217}
3218
3219bool pipePollReadiness(Process* kernelProcess) {
3220 constexpr size_t ReadDescriptor = 45;
3221 constexpr size_t WriteDescriptor = 46;
3222 Process* process = new Process(kernelProcess);
3223 PosixSubsystem* subsystem = new PosixSubsystem;
3224 process->setSubsystem(subsystem);
3225
3226 Pipe* pipe = new Pipe(String(""), 0, 0, 0, 0, nullptr, 0, nullptr, true);
3227 FileDescriptor* reader = new FileDescriptor(pipe, 0, ReadDescriptor, 0, O_RDONLY | O_NONBLOCK);
3228 FileDescriptor* writer = new FileDescriptor(pipe, 0, WriteDescriptor, 0, O_WRONLY | O_NONBLOCK);
3229 subsystem->addFileDescriptor(ReadDescriptor, reader);
3230 subsystem->addFileDescriptor(WriteDescriptor, writer);
3231
3232 PipePollReadinessContext context(ReadDescriptor, WriteDescriptor);
3233 Thread* worker = new Thread(process, pollPipeReadiness, &context, nullptr, false, true, true);
3234 worker->setName("hosted pipe poll readiness worker");
3235 const bool started = worker->start();
3236
3237 const bool pollEntered = started && context.pollEntryGate.acquireForCompletion();
3238 bool pollBlocked = false;
3239 for (size_t attempt = 0; attempt < HostedAttempts && pollEntered; ++attempt) {
3240 Thread::WaitDebugInfo info = {};
3241 if (worker->getWaitDebugInfo(info) && info.queue && info.queued &&
3242 worker->getStatus() == Thread::Sleeping) {
3243 pollBlocked = true;
3244 break;
3245 }
3247 }
3248
3249 char byte = 1;
3250 const bool initialWrite =
3251 pollBlocked && writer->write(1, reinterpret_cast<uintptr_t>(&byte)) == 1;
3252 const bool firstPhaseCompleted = started && context.firstPhaseGate.acquireForCompletion();
3253
3254 DescriptorLease closingWriter;
3255 const bool writerAcquired = subsystem->acquireFileDescriptor(WriteDescriptor, closingWriter);
3256 const bool writerClosed =
3257 writerAcquired && subsystem->closeFileDescriptor(WriteDescriptor, closingWriter);
3258 closingWriter.reset();
3259 context.eofGate.release();
3260
3261 const bool joined = started && worker->joinForCompletion();
3262 if (!started) {
3263 delete worker;
3264 }
3265
3266 const bool nonblockingPassed =
3267 context.emptyReadResult == -1 && context.emptyReadError == Error::NoMoreProcesses &&
3268 context.fillWriteResult == PIPE_BUF_MAX && context.fullWriteResult == -1 &&
3269 context.fullWriteError == Error::NoMoreProcesses && context.atomicSetupDrainResult == 1 &&
3270 context.atomicVectorResult == -1 && context.atomicVectorError == Error::NoMoreProcesses &&
3271 context.drainResult == PIPE_BUF_MAX - 1 && context.overBoundaryWriteResult == PIPE_BUF_MAX &&
3272 context.overBoundaryDrainResult == PIPE_BUF_MAX && context.eofReadResult == 0;
3273 const bool readinessPassed =
3274 initialWrite && context.firstPollResult == 1 && (context.firstPollEvents & POLLIN) &&
3275 !(context.firstPollEvents & POLLHUP) && context.firstReadResult == 1 &&
3276 context.eofPollResult == 1 && (context.eofPollEvents & POLLHUP);
3277 bool passed = started && pollBlocked && firstPhaseCompleted && joined && writerClosed &&
3278 context.firstPhaseReturned == 1 && context.returned == 1 && nonblockingPassed &&
3279 readinessPassed;
3280
3281 FileDescriptor::OpenFileDescriptionLease readerDescription = reader->acquireOpenFileDescription();
3282 DescriptorLease closingReader;
3283 const bool readerAcquired = subsystem->acquireFileDescriptor(ReadDescriptor, closingReader);
3284 const bool readerClosed =
3285 readerAcquired && subsystem->closeFileDescriptor(ReadDescriptor, closingReader);
3286 closingReader.reset();
3287 passed = passed && readerClosed && readerDescription->descriptorOwnerCount() == 0 &&
3288 readerDescription->getFile() == pipe && pipe->getReaderCount() == 0;
3289 readerDescription.reset();
3290 delete process;
3291
3292 if (!passed) {
3293 ERROR(
3294 "HOSTED-SYSCALL-TEST: FAIL pipe-poll-readiness: "
3295 "POLLIN wakeup, EOF hangup, or atomic nonblocking pipe I/O was incorrect");
3296 return false;
3297 }
3298
3299 NOTICE("HOSTED-SYSCALL-TEST: PASS pipe-poll-readiness");
3300 return true;
3301}
3302
3303int exerciseEpollReadiness(void* parameter) {
3304 EpollReadinessContext* context = reinterpret_cast<EpollReadinessContext*>(parameter);
3305 Thread* thread = Processor::information().getCurrentThread();
3306 LinuxEpollEvent events[2] = {};
3307 char byte = 1;
3308
3309 LinuxEpollEvent interest = {LinuxEpoll::In | LinuxEpoll::ReadNormal | LinuxEpoll::EdgeTriggered,
3310 EpollInitialData};
3311 context->addResult =
3312 context->instance->control(LinuxEpoll::ControlAdd, context->readFd, &interest);
3313
3314 thread->setErrno(0);
3315 context->regularAddResult =
3316 context->instance->control(LinuxEpoll::ControlAdd, context->regularFd, &interest);
3317 context->regularAddError = thread->getErrno();
3318
3319 context->createdFd = posix_epoll_create1(LinuxEpoll::CloseOnExec);
3320 DescriptorLease createdDescriptor;
3321 if (context->createdFd >= 0) {
3322 context->createdDescriptorAcquired = context->subsystem->acquireFileDescriptor(
3323 static_cast<size_t>(context->createdFd), createdDescriptor);
3324 if (context->createdDescriptorAcquired) {
3325 context->createdStatusFlags = createdDescriptor->getStatusFlags();
3326 context->createdDescriptorFlags = createdDescriptor->getFlags();
3327 context->createdCloseResult = context->subsystem->closeFileDescriptor(
3328 static_cast<size_t>(context->createdFd), createdDescriptor);
3329 }
3330 }
3331 createdDescriptor.reset();
3332
3333 thread->setErrno(0);
3334 context->duplicateAddResult =
3335 context->instance->control(LinuxEpoll::ControlAdd, context->readFd, &interest);
3336 context->duplicateAddError = thread->getErrno();
3337
3338 LinuxEpollEvent exclusive = {LinuxEpoll::In | LinuxEpoll::Exclusive, EpollInitialData};
3339 thread->setErrno(0);
3340 context->exclusiveModifyResult =
3341 context->instance->control(LinuxEpoll::ControlModify, context->readFd, &exclusive);
3342 context->exclusiveModifyError = thread->getErrno();
3343
3344 context->waitEntered += 1;
3345 context->waitEntryGate.release();
3346 context->firstWaitResult = context->instance->wait(events, 2, 5000);
3347 context->firstWaitEvents = events[0].events;
3348 context->firstWaitData = events[0].data;
3349
3350 events[0] = {};
3351 context->repeatedWaitResult = context->instance->wait(events, 2, 0);
3352 context->repeatedWaitEvents = events[0].events;
3353 context->repeatedWaitData = events[0].data;
3354 context->sameReadyWriteResult = posix_write(context->writeFd, &byte, 1, false);
3355 context->sameReadyWaitResult = context->instance->wait(events, 2, 0);
3356 char edgeBytes[2] = {};
3357 context->firstDrainResult = posix_read(context->readFd, edgeBytes, sizeof(edgeBytes));
3358 context->drainedWaitResult = context->instance->wait(events, 2, 0);
3359
3360 context->transitionWriteResult = posix_write(context->writeFd, &byte, 1, false);
3361 events[0] = {};
3362 context->transitionWaitResult = context->instance->wait(events, 2, 0);
3363 context->transitionWaitEvents = events[0].events;
3364 context->transitionWaitData = events[0].data;
3365 context->transitionDrainResult = posix_read(context->readFd, &byte, 1);
3366
3367 LinuxEpollEvent level = {LinuxEpoll::In, EpollLevelData};
3368 context->levelModifyResult =
3369 context->instance->control(LinuxEpoll::ControlModify, context->readFd, &level);
3370 context->levelWriteResult = posix_write(context->writeFd, &byte, 1, false);
3371 events[0] = {};
3372 context->levelWaitResult = context->instance->wait(events, 2, 0);
3373 context->levelWaitEvents = events[0].events;
3374 context->levelWaitData = events[0].data;
3375 context->levelRepeatedWaitResult = context->instance->wait(events, 2, 0);
3376 context->levelDrainResult = posix_read(context->readFd, &byte, 1);
3377
3378 LinuxEpollEvent oneShot = {LinuxEpoll::In | LinuxEpoll::EdgeTriggered | LinuxEpoll::OneShot,
3379 EpollOneShotData};
3380 context->oneShotModifyResult =
3381 context->instance->control(LinuxEpoll::ControlModify, context->readFd, &oneShot);
3382 context->oneShotWriteResult = posix_write(context->writeFd, &byte, 1, false);
3383 events[0] = {};
3384 context->oneShotWaitResult = context->instance->wait(events, 2, 0);
3385 context->oneShotWaitEvents = events[0].events;
3386 context->oneShotWaitData = events[0].data;
3387 context->oneShotSuppressedResult = context->instance->wait(events, 2, 0);
3388
3389 LinuxEpollEvent rearmed = {LinuxEpoll::In | LinuxEpoll::EdgeTriggered | LinuxEpoll::OneShot,
3390 EpollRearmedData};
3391 context->rearmResult =
3392 context->instance->control(LinuxEpoll::ControlModify, context->readFd, &rearmed);
3393 events[0] = {};
3394 context->rearmedWaitResult = context->instance->wait(events, 2, 0);
3395 context->rearmedWaitEvents = events[0].events;
3396 context->rearmedWaitData = events[0].data;
3397 context->oneShotDrainResult = posix_read(context->readFd, &byte, 1);
3398
3399 context->deleteResult =
3400 context->instance->control(LinuxEpoll::ControlDelete, context->readFd, nullptr);
3401 context->postDeleteWriteResult = posix_write(context->writeFd, &byte, 1, false);
3402 context->postDeleteWaitResult = context->instance->wait(events, 2, 0);
3403 context->postDeleteDrainResult = posix_read(context->readFd, &byte, 1);
3404
3405 context->eventFd = posix_eventfd2(0, LinuxEventFd::NonBlock);
3406 LinuxEpollEvent eventFdInterest = {LinuxEpoll::In | LinuxEpoll::EdgeTriggered, EpollEventFdData};
3407 if (context->eventFd >= 0) {
3408 context->eventFdAddResult =
3409 context->instance->control(LinuxEpoll::ControlAdd, context->eventFd, &eventFdInterest);
3410 uint64_t eventValue = 1;
3411 context->eventFdFirstWriteResult = posix_write(
3412 context->eventFd, reinterpret_cast<char*>(&eventValue), sizeof(eventValue), false);
3413 events[0] = {};
3414 context->eventFdFirstWaitResult = context->instance->wait(events, 2, 0);
3415 context->eventFdFirstWaitEvents = events[0].events;
3416 context->eventFdFirstWaitData = events[0].data;
3417
3418 // libuv deliberately leaves its Linux async eventfd readable. A second
3419 // producer write must therefore generate another edge without a read in
3420 // between, even though the counter's readable level never went false.
3421 context->eventFdSecondWriteResult = posix_write(
3422 context->eventFd, reinterpret_cast<char*>(&eventValue), sizeof(eventValue), false);
3423 events[0] = {};
3424 context->eventFdSecondWaitResult = context->instance->wait(events, 2, 0);
3425 context->eventFdSecondWaitEvents = events[0].events;
3426 context->eventFdSecondWaitData = events[0].data;
3427 context->eventFdReadResult =
3428 posix_read(context->eventFd, reinterpret_cast<char*>(&context->eventFdReadValue),
3429 sizeof(context->eventFdReadValue));
3430 context->eventFdDeleteResult =
3431 context->instance->control(LinuxEpoll::ControlDelete, context->eventFd, nullptr);
3432
3433 DescriptorLease closingEventFd;
3434 const bool eventFdAcquired = context->subsystem->acquireFileDescriptor(
3435 static_cast<size_t>(context->eventFd), closingEventFd);
3436 context->eventFdCloseResult =
3437 eventFdAcquired && context->subsystem->closeFileDescriptor(
3438 static_cast<size_t>(context->eventFd), closingEventFd);
3439 closingEventFd.reset();
3440 }
3441
3442 LinuxEpollEvent aliasLifetime = {LinuxEpoll::In, EpollAliasData};
3443 context->aliasAddResult =
3444 context->instance->control(LinuxEpoll::ControlAdd, context->readFd, &aliasLifetime);
3445
3446 DescriptorLease closingOriginal;
3447 const bool originalAcquired =
3448 context->subsystem->acquireFileDescriptor(context->readFd, closingOriginal);
3449 context->originalCloseResult =
3450 originalAcquired && context->subsystem->closeFileDescriptor(context->readFd, closingOriginal);
3451 closingOriginal.reset();
3452 context->ownersAfterOriginalClose = context->readDescription->descriptorOwnerCount();
3453
3454 context->aliasWriteResult = posix_write(context->writeFd, &byte, 1, false);
3455 events[0] = {};
3456 context->aliasWaitResult = context->instance->wait(events, 2, 0);
3457 context->aliasWaitEvents = events[0].events;
3458 context->aliasWaitData = events[0].data;
3459 context->aliasDrainResult = posix_read(context->aliasFd, &byte, 1);
3460
3461 DescriptorLease closingAlias;
3462 const bool aliasAcquired =
3463 context->subsystem->acquireFileDescriptor(context->aliasFd, closingAlias);
3464 context->aliasCloseResult =
3465 aliasAcquired && context->subsystem->closeFileDescriptor(context->aliasFd, closingAlias);
3466 closingAlias.reset();
3467 context->ownersAfterAliasClose = context->readDescription->descriptorOwnerCount();
3468 context->prunedWaitResult = context->instance->wait(events, 2, 0);
3469 context->descriptionRefsAfterPrune = context->readDescription.refcount();
3470
3471 context->returned += 1;
3472 return 0;
3473}
3474
3475bool epollLevelOneShotAndOfdLifetime(Process* kernelProcess) {
3476 constexpr size_t ReadDescriptor = 47;
3477 constexpr size_t WriteDescriptor = 48;
3478 constexpr size_t AliasDescriptor = 49;
3479 constexpr size_t RegularDescriptor = 50;
3480
3481 Process* process = new Process(kernelProcess);
3482 PosixSubsystem* subsystem = new PosixSubsystem;
3483 process->setSubsystem(subsystem);
3484
3485 Pipe* pipe = new Pipe(String(""), 0, 0, 0, 0, nullptr, 0, nullptr, true);
3486 FileDescriptor* reader = new FileDescriptor(pipe, 0, ReadDescriptor, 0, O_RDONLY | O_NONBLOCK);
3487 FileDescriptor* writer = new FileDescriptor(pipe, 0, WriteDescriptor, 0, O_WRONLY | O_NONBLOCK);
3488 FileDescriptor* alias = new FileDescriptor(*reader);
3489 alias->fd = AliasDescriptor;
3490 File* regularFile = new File;
3491 FileDescriptor* regular = new FileDescriptor(regularFile, 0, RegularDescriptor, 0, O_RDONLY);
3492 subsystem->addFileDescriptor(ReadDescriptor, reader);
3493 subsystem->addFileDescriptor(WriteDescriptor, writer);
3494 subsystem->addFileDescriptor(AliasDescriptor, alias);
3495 subsystem->addFileDescriptor(RegularDescriptor, regular);
3496
3498 EpollReadinessContext context(subsystem, instance, ReadDescriptor, WriteDescriptor,
3499 AliasDescriptor, RegularDescriptor);
3500 context.readDescription = reader->acquireOpenFileDescription();
3501
3502 Thread* worker =
3503 new Thread(process, exerciseEpollReadiness, &context, nullptr, false, true, true);
3504 worker->setName("hosted epoll readiness worker");
3505 const bool started = worker->start();
3506 const bool waitEntered = started && context.waitEntryGate.acquireForCompletion();
3507
3508 bool waitBlocked = false;
3509 for (size_t attempt = 0; attempt < HostedAttempts && waitEntered; ++attempt) {
3510 Thread::WaitDebugInfo info = {};
3511 if (worker->getWaitDebugInfo(info) && info.queue && info.queued &&
3512 worker->getStatus() == Thread::Sleeping) {
3513 waitBlocked = true;
3514 break;
3515 }
3517 }
3518
3519 char byte = 1;
3520 const int wakeWriteResult = writer->write(1, reinterpret_cast<uintptr_t>(&byte));
3521 const bool joined = started && worker->joinForCompletion();
3522 if (!started) {
3523 delete worker;
3524 }
3525
3526 const bool edgePassed =
3527 waitBlocked && wakeWriteResult == 1 && context.firstWaitResult == 1 &&
3528 context.firstWaitEvents == (LinuxEpoll::In | LinuxEpoll::ReadNormal) &&
3529 context.firstWaitData == EpollInitialData && context.repeatedWaitResult == 0 &&
3530 context.sameReadyWriteResult == 1 && context.sameReadyWaitResult == 0 &&
3531 context.firstDrainResult == 2 && context.drainedWaitResult == 0 &&
3532 context.transitionWriteResult == 1 && context.transitionWaitResult == 1 &&
3533 context.transitionWaitEvents == (LinuxEpoll::In | LinuxEpoll::ReadNormal) &&
3534 context.transitionWaitData == EpollInitialData && context.transitionDrainResult == 1;
3535 const bool levelPassed = context.levelModifyResult == 0 && context.levelWriteResult == 1 &&
3536 context.levelWaitResult == 1 &&
3537 context.levelWaitEvents == LinuxEpoll::In &&
3538 context.levelWaitData == EpollLevelData &&
3539 context.levelRepeatedWaitResult == 1 && context.levelDrainResult == 1;
3540 const bool controlPassed =
3541 context.addResult == 0 && context.regularAddResult == -1 &&
3542 context.regularAddError == Error::NotEnoughPermissions && context.createdFd >= 0 &&
3543 context.createdDescriptorAcquired && context.createdStatusFlags == O_RDWR &&
3544 context.createdDescriptorFlags == FD_CLOEXEC && context.createdCloseResult &&
3545 context.duplicateAddResult == -1 && context.duplicateAddError == Error::FileExists &&
3546 context.exclusiveModifyResult == -1 &&
3547 context.exclusiveModifyError == Error::OperationNotSupported &&
3548 context.oneShotModifyResult == 0 && context.oneShotWriteResult == 1 &&
3549 context.oneShotWaitResult == 1 && context.oneShotWaitEvents == LinuxEpoll::In &&
3550 context.oneShotWaitData == EpollOneShotData && context.oneShotSuppressedResult == 0 &&
3551 context.rearmResult == 0 && context.rearmedWaitResult == 1 &&
3552 context.rearmedWaitEvents == LinuxEpoll::In && context.rearmedWaitData == EpollRearmedData &&
3553 context.oneShotDrainResult == 1 && context.deleteResult == 0 &&
3554 context.postDeleteWriteResult == 1 && context.postDeleteWaitResult == 0 &&
3555 context.postDeleteDrainResult == 1;
3556 const bool lifetimePassed =
3557 context.aliasAddResult == 0 && context.originalCloseResult &&
3558 context.ownersAfterOriginalClose == 1 && context.aliasWriteResult == 1 &&
3559 context.aliasWaitResult == 1 && context.aliasWaitEvents == LinuxEpoll::In &&
3560 context.aliasWaitData == EpollAliasData && context.aliasDrainResult == 1 &&
3561 context.aliasCloseResult && context.ownersAfterAliasClose == 0 &&
3562 context.prunedWaitResult == 0 && context.descriptionRefsAfterPrune == 1;
3563 const bool eventFdPassed =
3564 context.eventFd >= 0 && context.eventFdAddResult == 0 &&
3565 context.eventFdFirstWriteResult == static_cast<int>(sizeof(uint64_t)) &&
3566 context.eventFdFirstWaitResult == 1 && context.eventFdFirstWaitEvents == LinuxEpoll::In &&
3567 context.eventFdFirstWaitData == EpollEventFdData &&
3568 context.eventFdSecondWriteResult == static_cast<int>(sizeof(uint64_t)) &&
3569 context.eventFdSecondWaitResult == 1 && context.eventFdSecondWaitEvents == LinuxEpoll::In &&
3570 context.eventFdSecondWaitData == EpollEventFdData &&
3571 context.eventFdReadResult == static_cast<int>(sizeof(uint64_t)) &&
3572 context.eventFdReadValue == 2 && context.eventFdDeleteResult == 0 &&
3573 context.eventFdCloseResult;
3574 bool passed = started && waitEntered && joined && context.waitEntered == 1 &&
3575 context.returned == 1 && edgePassed && levelPassed && controlPassed &&
3576 lifetimePassed && eventFdPassed;
3577
3578 DescriptorLease closingWriter;
3579 const bool writerAcquired = subsystem->acquireFileDescriptor(WriteDescriptor, closingWriter);
3580 const bool writerClosed =
3581 writerAcquired && subsystem->closeFileDescriptor(WriteDescriptor, closingWriter);
3582 closingWriter.reset();
3583 passed = passed && writerClosed;
3584
3585 instance.reset();
3586 context.instance.reset();
3587 context.readDescription.reset();
3588 delete process;
3589 delete regularFile;
3590
3591 if (!passed) {
3592 ERROR(
3593 "HOSTED-SYSCALL-TEST: FAIL epoll-level-oneshot-ofd-lifetime: "
3594 "target admission, descriptor flags, edge and level delivery, one-shot rearm, control "
3595 "errors, eventfd generations, or OFD retirement was incorrect");
3596 return false;
3597 }
3598
3599 NOTICE("HOSTED-SYSCALL-TEST: PASS epoll-level-oneshot-ofd-lifetime");
3600 return true;
3601}
3602
3603bool epollReorderedTransitionPublication(Process* kernelProcess) {
3604 constexpr size_t DescriptorNumber = 51;
3605 Process* process = new Process(kernelProcess);
3606 PosixSubsystem* subsystem = new PosixSubsystem;
3607 process->setSubsystem(subsystem);
3608
3609 ReorderedReadinessFile* source = new ReorderedReadinessFile;
3610 FileDescriptor* descriptor =
3611 new FileDescriptor(source, 0, DescriptorNumber, 0, O_RDWR | O_NONBLOCK);
3612 subsystem->addFileDescriptor(DescriptorNumber, descriptor);
3613
3615 ReorderedEpollContext context(instance, DescriptorNumber, source);
3616 Thread* waiter =
3617 new Thread(process, waitAcrossReorderedReadiness, &context, nullptr, false, true, true);
3618 waiter->setName("hosted reordered epoll waiter");
3619 const bool waiterStarted = waiter->start();
3620 const bool initialComplete = waiterStarted && context.initialComplete.acquireForCompletion();
3621
3622 Thread* drain = nullptr;
3623 bool drainStarted = false;
3624 bool drainMutated = false;
3625 if (initialComplete) {
3626 drain = new Thread(process, publishDelayedDrain, &context, nullptr, false, true, true);
3627 drain->setName("hosted delayed epoll drain publisher");
3628 drainStarted = drain->start();
3629 drainMutated = drainStarted && context.drainMutated.acquireForCompletion();
3630 }
3631
3632 // The refill callback deliberately overtakes the callback for the drain.
3633 // Both callbacks therefore sample the final readable level; only the
3634 // source-side generation proves that a reusable edge occurred between them.
3635 if (drainMutated) {
3636 source->setReady(true, true);
3637 source->publishReadiness();
3638 }
3639 context.publishDrain.release();
3640 const bool drainJoined = drainStarted && drain->joinForCompletion();
3641 if (drain && !drainStarted) {
3642 delete drain;
3643 }
3644
3645 context.collectFinal.release();
3646 const bool waiterJoined = waiterStarted && waiter->joinForCompletion();
3647 if (!waiterStarted) {
3648 delete waiter;
3649 }
3650
3651 const bool transitionPassed =
3652 context.addResult == 0 && context.initialWaitResult == 1 &&
3653 context.initialWaitEvents == (LinuxEpoll::In | LinuxEpoll::Out) &&
3654 context.initialWaitData == EpollReorderedData && context.finalWaitResult == 1 &&
3655 context.finalWaitEvents == (LinuxEpoll::In | LinuxEpoll::Out) &&
3656 context.finalWaitData == EpollReorderedData && context.deleteResult == 0 &&
3657 source->readinessGenerations().read == 2 && source->readinessGenerations().write == 2;
3658 bool passed = waiterStarted && initialComplete && drainStarted && drainMutated && drainJoined &&
3659 waiterJoined && context.waiterReturned == 1 && context.drainReturned == 1 &&
3660 transitionPassed;
3661
3662 DescriptorLease closing;
3663 const bool descriptorAcquired = subsystem->acquireFileDescriptor(DescriptorNumber, closing);
3664 const bool descriptorClosed =
3665 descriptorAcquired && subsystem->closeFileDescriptor(DescriptorNumber, closing);
3666 closing.reset();
3667 passed = passed && descriptorClosed;
3668
3669 instance.reset();
3670 context.instance.reset();
3671 delete process;
3672 delete source;
3673
3674 if (!passed) {
3675 ERROR(
3676 "HOSTED-SYSCALL-TEST: FAIL epoll-reordered-transition-publication: "
3677 "a refill edge was lost when its callback overtook the drain callback");
3678 return false;
3679 }
3680
3681 NOTICE("HOSTED-SYSCALL-TEST: PASS epoll-reordered-transition-publication");
3682 return true;
3683}
3684
3685bool epollPersistentFifoReopenReclose(Process* kernelProcess) {
3686 constexpr size_t DescriptorNumber = 52;
3687 Process* process = new Process(kernelProcess);
3688 PosixSubsystem* subsystem = new PosixSubsystem;
3689 process->setSubsystem(subsystem);
3690
3691 ReorderedFifo* fifo = new ReorderedFifo;
3692 FileDescriptor* writer = new FileDescriptor(fifo, 0, DescriptorNumber, 0, O_WRONLY | O_NONBLOCK);
3693 subsystem->addFileDescriptor(DescriptorNumber, writer);
3694
3695 fifo->increaseRefCount(false);
3696 char fill[PIPE_BUF_MAX] = {};
3697 const int fillResult = writer->write(sizeof(fill), reinterpret_cast<uintptr_t>(fill));
3698 fifo->decreaseRefCount(false);
3699
3701 ReorderedFifoEpollContext context(instance, DescriptorNumber);
3702 Thread* waiter =
3703 new Thread(process, waitAcrossReorderedFifoReopen, &context, nullptr, false, true, true);
3704 waiter->setName("hosted reordered FIFO epoll waiter");
3705 const bool waiterStarted = waiter->start();
3706 const bool initialComplete = waiterStarted && context.initialComplete.acquireForCompletion();
3707
3708 const ReadinessGenerations initialGenerations = fifo->readinessGenerations();
3709 bool reopenMadeNotReady = false;
3710 if (initialComplete) {
3711 // Delay the notification for the falling reader-reopen transition. The
3712 // re-close publishes first, so both callbacks sample the final OUT|ERR
3713 // level and the Pipe generation is the only evidence of the OUT rise.
3714 fifo->reopenReaderWithoutPublishing();
3715 reopenMadeNotReady = fifo->queryReady(false, true) == ReadyNone;
3716 fifo->decreaseRefCount(false);
3717 fifo->publishReopen();
3718 }
3719 const ReadinessGenerations finalGenerations = fifo->readinessGenerations();
3720
3721 context.collectFinal.release();
3722 const bool waiterJoined = waiterStarted && waiter->joinForCompletion();
3723 if (!waiterStarted) {
3724 delete waiter;
3725 }
3726
3727 const uint32_t expectedEvents = LinuxEpoll::Out | LinuxEpoll::Error;
3728 const bool edgePassed =
3729 fillResult == PIPE_BUF_MAX && context.addResult == 0 && context.initialWaitResult == 1 &&
3730 context.initialWaitEvents == expectedEvents && context.initialWaitData == EpollFifoData &&
3731 reopenMadeNotReady && context.finalWaitResult == 1 &&
3732 context.finalWaitEvents == expectedEvents && context.finalWaitData == EpollFifoData &&
3733 context.deleteResult == 0 && finalGenerations.write != initialGenerations.write &&
3734 finalGenerations.error != initialGenerations.error;
3735 bool passed =
3736 waiterStarted && initialComplete && waiterJoined && context.returned == 1 && edgePassed;
3737
3738 DescriptorLease closing;
3739 const bool descriptorAcquired = subsystem->acquireFileDescriptor(DescriptorNumber, closing);
3740 const bool descriptorClosed =
3741 descriptorAcquired && subsystem->closeFileDescriptor(DescriptorNumber, closing);
3742 closing.reset();
3743 passed = passed && descriptorClosed;
3744
3745 instance.reset();
3746 context.instance.reset();
3747 delete process;
3748 delete fifo;
3749
3750 if (!passed) {
3751 ERROR(
3752 "HOSTED-SYSCALL-TEST: FAIL epoll-persistent-fifo-reopen-reclose: "
3753 "a full FIFO lost EPOLLOUT when reader re-close overtook reopen publication");
3754 return false;
3755 }
3756
3757 NOTICE("HOSTED-SYSCALL-TEST: PASS epoll-persistent-fifo-reopen-reclose");
3758 return true;
3759}
3760
3761int pollAcrossCloseReuse(void* parameter) {
3762 PollCloseReuseContext* context = reinterpret_cast<PollCloseReuseContext*>(parameter);
3763 context->entered += 1;
3764 // A missed wakeup must report a bounded regression failure. The old
3765 // infinite poll made the outer harness timeout the only evidence.
3766 context->result = posix_poll_safe(&context->descriptor, 1, PollCloseReuseTimeoutMilliseconds);
3767 context->returned += 1;
3768 return context->result == 1 ? 0 : 1;
3769}
3770
3771bool pollCloseReuseCleanup(Process* kernelProcess) {
3772 constexpr size_t DescriptorNumber = 39;
3773 Process* process = new Process(kernelProcess);
3774 PosixSubsystem* subsystem = new PosixSubsystem;
3775 process->setSubsystem(subsystem);
3776
3777 Atomic<size_t> aQueries(0);
3778 Atomic<size_t> aNotifications(0);
3779 Atomic<size_t> aNetworkDestructions(0);
3780 Atomic<size_t> aDescriptorDestructions(0);
3781 PollGenerationProbe* aNetwork =
3782 new PollGenerationProbe(aQueries, aNotifications, aNetworkDestructions);
3783 SharedPointer<NetworkSyscalls> aNetworkKeepalive(aNetwork);
3784 DescriptorRetirementProbe* aDescriptor = new DescriptorRetirementProbe(aDescriptorDestructions);
3785 aDescriptor->fd = DescriptorNumber;
3786 aDescriptor->setOffset(1);
3787 aDescriptor->setNetworkImpl(aNetworkKeepalive);
3788 subsystem->addFileDescriptor(DescriptorNumber, aDescriptor);
3789
3790 Atomic<size_t> bQueries(0);
3791 Atomic<size_t> bNotifications(0);
3792 Atomic<size_t> bNetworkDestructions(0);
3793 Atomic<size_t> bDescriptorDestructions(0);
3794 PollGenerationProbe* bNetwork =
3795 new PollGenerationProbe(bQueries, bNotifications, bNetworkDestructions);
3796 SharedPointer<NetworkSyscalls> bNetworkKeepalive(bNetwork);
3797 DescriptorRetirementProbe* bDescriptor = new DescriptorRetirementProbe(bDescriptorDestructions);
3798 bDescriptor->fd = DescriptorNumber;
3799 bDescriptor->setOffset(2);
3800 bDescriptor->setNetworkImpl(bNetworkKeepalive);
3801
3802 PollCloseReuseContext context(DescriptorNumber);
3803 Thread* worker = new Thread(process, pollAcrossCloseReuse, &context, nullptr, false, true, true);
3804 worker->setName("hosted poll close-reuse worker");
3805 const bool started = worker->start();
3806 NOTICE(
3807 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3808 "worker-started");
3809 bool blockedOnA = false;
3810 for (size_t attempt = 0; attempt < HostedAttempts && started; ++attempt) {
3811 Thread::WaitDebugInfo info = {};
3812 if (context.entered && aQueries >= 2 && worker->getWaitDebugInfo(info) && info.queue &&
3813 info.queued && worker->getStatus() == Thread::Sleeping) {
3814 blockedOnA = true;
3815 break;
3816 }
3818 }
3819
3820 bool passed = started && blockedOnA && aQueries >= 2;
3821 NOTICE(
3822 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3823 "waiter-published-a blocked="
3824 << blockedOnA << " queries=" << aQueries.value());
3825 DescriptorLease closingA;
3826 const bool acquiredA = subsystem->acquireFileDescriptor(DescriptorNumber, closingA);
3827 const bool closedA = acquiredA && subsystem->closeFileDescriptor(DescriptorNumber, closingA);
3828 closingA.reset();
3829 subsystem->addFileDescriptor(DescriptorNumber, bDescriptor);
3830 passed = passed && closedA && aDescriptorDestructions == 0 && aNetworkDestructions == 0;
3831 NOTICE(
3832 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3833 "closed-a-published-b");
3834
3835 if (aQueries >= 2) {
3836 aNetwork->makeReadable();
3837 } else {
3838 // Failure cleanup: if the worker did not pin A, allow any lookup of B
3839 // to finish rather than leaving the hosted smoke run blocked.
3840 bNetwork->makeReadable();
3841 }
3842 NOTICE(
3843 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3844 "release-published");
3845
3846 const bool joined = started && worker->joinForCompletion();
3847 NOTICE(
3848 "HOSTED-SYSCALL-TEST: PHASE poll-close-reuse-cleanup "
3849 "worker-returned joined="
3850 << joined << " returned=" << context.returned.value()
3851 << " result=" << context.result.value());
3852 passed = passed && joined && context.returned == 1 && context.result == 1 &&
3853 (context.descriptor.revents & POLLIN) && aNotifications == 1 && bNotifications == 0 &&
3854 bQueries == 0 && aDescriptorDestructions == 1 && aNetworkDestructions == 0;
3855 aNetworkKeepalive.reset();
3856 passed = passed && aNetworkDestructions == 1;
3857
3858 DescriptorLease closingB;
3859 const bool acquiredB = subsystem->acquireFileDescriptor(DescriptorNumber, closingB);
3860 const bool closedB = acquiredB && subsystem->closeFileDescriptor(DescriptorNumber, closingB);
3861 closingB.reset();
3862 passed = passed && closedB && bDescriptorDestructions == 1 && bNetworkDestructions == 0;
3863 bNetworkKeepalive.reset();
3864 passed = passed && bNetworkDestructions == 1;
3865
3866 delete process;
3867
3868 if (!passed) {
3869 ERROR(
3870 "HOSTED-SYSCALL-TEST: FAIL poll-close-reuse-cleanup: "
3871 "poll cleanup followed the reused fd instead of its registered "
3872 "descriptor generation");
3873 return false;
3874 }
3875
3876 NOTICE("HOSTED-SYSCALL-TEST: PASS poll-close-reuse-cleanup");
3877 return true;
3878}
3879
3880struct PosixTeardownContext {
3881 explicit PosixTeardownContext(Process* process)
3882 : process(process),
3883 releaseGate(0, false),
3884 holderEntered(0),
3885 holderReturned(0),
3886 reaperEntered(0),
3887 processDeleted(0) {}
3888
3889 Process* process;
3890 Semaphore releaseGate;
3891 Atomic<size_t> holderEntered;
3892 Atomic<size_t> holderReturned;
3893 Atomic<size_t> reaperEntered;
3894 Atomic<size_t> processDeleted;
3895};
3896
3897int holdMemoryMapLifecycleGate(void* parameter) {
3898 PosixTeardownContext* context = reinterpret_cast<PosixTeardownContext*>(parameter);
3899 MemoryMapManager::instance().acquireLifecycleGateForHostedTest();
3900 context->holderEntered += 1;
3901 const bool released = context->releaseGate.acquireForCompletion();
3902 MemoryMapManager::instance().releaseLifecycleGateForHostedTest();
3903 context->holderReturned += 1;
3904 return released ? 0 : 1;
3905}
3906
3907int deletePosixProcess(void* parameter) {
3908 PosixTeardownContext* context = reinterpret_cast<PosixTeardownContext*>(parameter);
3909 context->reaperEntered += 1;
3910 delete context->process;
3911 context->processDeleted += 1;
3912 return 0;
3913}
3914
3915bool posixTeardownContention(Process* kernelProcess) {
3916 Process* process = new Process(kernelProcess);
3917 process->setSubsystem(new PosixSubsystem);
3918 PosixTeardownContext context(process);
3919
3920 Thread* holder =
3921 new Thread(kernelProcess, holdMemoryMapLifecycleGate, &context, nullptr, false, true, true);
3922 holder->setName("hosted mmap lifecycle holder");
3923 bool passed = holder->start();
3924
3925 for (size_t attempt = 0; attempt < HostedAttempts && passed && !context.holderEntered;
3926 ++attempt) {
3928 }
3929 passed = passed && context.holderEntered == 1;
3930
3931 Thread* reaper = nullptr;
3932 bool blocked = false;
3933 if (passed) {
3934 reaper = new Thread(kernelProcess, deletePosixProcess, &context, nullptr, false, true, true);
3935 reaper->setName("hosted POSIX process reaper");
3936 passed = reaper->start();
3937
3938 for (size_t attempt = 0; attempt < HostedAttempts && passed; ++attempt) {
3939 Thread::WaitDebugInfo info = {};
3940 if (context.reaperEntered && reaper->getWaitDebugInfo(info) && info.queue && info.queued &&
3941 info.channelOwner == MemoryMapManager::instance().lifecycleGateAddressForHostedTest() &&
3942 reaper->getStatus() == Thread::Sleeping) {
3943 blocked = true;
3944 break;
3945 }
3947 }
3948 passed = passed && blocked && context.processDeleted == 0;
3949 }
3950
3951 context.releaseGate.release();
3952 passed = holder->join() && passed;
3953 if (reaper) {
3954 passed = reaper->join() && passed;
3955 passed = passed && context.processDeleted == 1;
3956 } else {
3957 delete process;
3958 }
3959
3960 if (!passed) {
3961 ERROR(
3962 "HOSTED-SYSCALL-TEST: FAIL posix-teardown-contention: "
3963 "real PosixSubsystem destruction did not sleep and resume on "
3964 "the memory-map lifecycle gate");
3965 return false;
3966 }
3967
3968 NOTICE("HOSTED-SYSCALL-TEST: PASS posix-teardown-contention");
3969 return true;
3970}
3971
3972bool zeroResultWinsSignal(Thread* thread) {
3973 thread->setErrno(0);
3974 thread->setInterruptionReason(Thread::InterruptedBySignal);
3975 const bool completed = finishInterruptibleSocketCall(thread, static_cast<ssize_t>(0));
3976 const bool passed =
3977 completed && thread->getInterruptionReason() == Thread::NotInterrupted && !thread->getErrno();
3978 if (!passed) {
3979 ERROR(
3980 "HOSTED-SYSCALL-TEST: FAIL socket-zero-result-signal: "
3981 "EOF or zero-length success was replaced with EINTR");
3982 thread->clearInterruption();
3983 thread->setErrno(0);
3984 return false;
3985 }
3986
3987 NOTICE("HOSTED-SYSCALL-TEST: PASS socket-zero-result-signal");
3988 return true;
3989}
3990
3991bool cloneStateDropsParentErrnoDestination() {
3992 long error = 0;
3993 SyscallState parent = {};
3994 parent.error_ptr = reinterpret_cast<uintptr_t>(&error);
3995 parent.result = 37;
3996
3997 const SyscallState child = posix_copy_clone_state(parent);
3998 const bool passed = !child.error_ptr && child.result == parent.result &&
3999 parent.error_ptr == reinterpret_cast<uintptr_t>(&error);
4000 if (!passed) {
4001 ERROR(
4002 "HOSTED-SYSCALL-TEST: FAIL clone-errno-lifetime: "
4003 "the child retained its parent's stack-local errno destination");
4004 return false;
4005 }
4006
4007 NOTICE("HOSTED-SYSCALL-TEST: PASS clone-errno-lifetime");
4008 return true;
4009}
4010
4011bool cloneVmNullStackPreservesInterrupts() {
4012 const bool interruptsWereEnabled = Processor::getInterrupts();
4013 const uintptr_t result = SyscallManager::instance().syscall(
4014 posix, POSIX_CLONE, CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD, 0, 0, 0,
4015 0);
4016 const bool interruptsStillEnabled = Processor::getInterrupts();
4017 if (interruptsStillEnabled != interruptsWereEnabled) {
4018 Processor::setInterrupts(interruptsWereEnabled);
4019 }
4020
4021 if (!interruptsWereEnabled || !interruptsStillEnabled || result != static_cast<uintptr_t>(-1)) {
4022 ERROR(
4023 "HOSTED-SYSCALL-TEST: FAIL clone-vm-null-stack-interrupts: "
4024 "the invalid clone path did not preserve its enabled IRQ state");
4025 return false;
4026 }
4027
4028 NOTICE("HOSTED-SYSCALL-TEST: PASS clone-vm-null-stack-interrupts");
4029 return true;
4030}
4031
4032enum CloneVmBeforeStartAction {
4033 CancelChildBeforeStart,
4034 WaitForProcessExit,
4035};
4036
4037struct CloneVmUserFixture {
4038 int parentTid;
4039 int childTid;
4040 uintptr_t childTls;
4041 alignas(16) uint8_t childStack[4096];
4042};
4043
4044struct CloneVmExitRaceContext {
4045 explicit CloneVmExitRaceContext(CloneVmBeforeStartAction action)
4046 : process(nullptr),
4047 caller(nullptr),
4048 terminator(nullptr),
4049 child(nullptr),
4050 action(action),
4051 beforeStart(0, true),
4052 userAddress(0),
4053 parentTid(-1),
4054 childTid(-1),
4055 observedTid(0),
4056 callerEntered(0),
4057 callerReturned(0),
4058 callerInterruptsBefore(0),
4059 callerInterruptsAfter(0),
4060 terminatorEntered(0),
4061 terminatorReturned(0),
4062 hookCalls(0),
4063 tidsReady(0),
4064 terminationElectionCalls(0),
4065 ownershipWindowReleased(0),
4066 ownershipCancellationObserved(0),
4067 ownershipStartObserved(0),
4068 controlledHookRelease(0),
4069 electionTimedOut(0),
4070 childCancellationPublished(0),
4071 cloneResult(static_cast<size_t>(-1)),
4072 childCancellationRequested(0),
4073 childCancellationReapable(0),
4074 terminatorStarted(0),
4075 terminalCancellation(0),
4076 unexpectedHookRelease(0),
4077 hookTimedOut(0),
4078 rescueCancellation(0),
4079 processDestructions(0),
4080 subsystemDestructions(0) {}
4081
4082 Process* process;
4083 Thread* caller;
4084 Thread* terminator;
4085 Atomic<Thread*> child;
4086 CloneVmBeforeStartAction action;
4087 Semaphore beforeStart;
4088 uintptr_t userAddress;
4089 int parentTid;
4090 int childTid;
4091 size_t observedTid;
4092 Atomic<size_t> callerEntered;
4093 Atomic<size_t> callerReturned;
4094 Atomic<size_t> callerInterruptsBefore;
4095 Atomic<size_t> callerInterruptsAfter;
4096 Atomic<size_t> terminatorEntered;
4097 Atomic<size_t> terminatorReturned;
4098 Atomic<size_t> hookCalls;
4099 Atomic<size_t> tidsReady;
4100 Atomic<size_t> terminationElectionCalls;
4101 Atomic<size_t> ownershipWindowReleased;
4102 Atomic<size_t> ownershipCancellationObserved;
4103 Atomic<size_t> ownershipStartObserved;
4104 Atomic<size_t> controlledHookRelease;
4105 Atomic<size_t> electionTimedOut;
4106 Atomic<size_t> childCancellationPublished;
4107 Atomic<size_t> cloneResult;
4108 Atomic<size_t> childCancellationRequested;
4109 Atomic<size_t> childCancellationReapable;
4110 Atomic<size_t> terminatorStarted;
4111 Atomic<size_t> terminalCancellation;
4112 Atomic<size_t> unexpectedHookRelease;
4113 Atomic<size_t> hookTimedOut;
4114 Atomic<size_t> rescueCancellation;
4115 Atomic<size_t> processDestructions;
4116 Atomic<size_t> subsystemDestructions;
4117};
4118
4119CloneVmExitRaceContext* g_CloneVmExitRaceContext = nullptr;
4120
4121class CloneVmExitRaceProcess final : public PosixProcess {
4122 public:
4123 CloneVmExitRaceProcess(Process* parent, Atomic<size_t>& destructions)
4124 : PosixProcess(parent), m_Destructions(destructions) {}
4125
4126 ~CloneVmExitRaceProcess() override {
4127 m_Destructions += 1;
4128 }
4129
4130 private:
4131 Atomic<size_t>& m_Destructions;
4132};
4133
4134class CloneVmExitRaceSubsystem final : public PosixSubsystem {
4135 public:
4136 explicit CloneVmExitRaceSubsystem(Atomic<size_t>& destructions)
4137 : PosixSubsystem(), m_Destructions(destructions) {}
4138
4139 ~CloneVmExitRaceSubsystem() override {
4140 m_Destructions += 1;
4141 }
4142
4143 private:
4144 Atomic<size_t>& m_Destructions;
4145};
4146
4147int terminateCloneVmProcess(void* parameter) {
4148 CloneVmExitRaceContext* context = reinterpret_cast<CloneVmExitRaceContext*>(parameter);
4149 context->terminatorEntered += 1;
4150 SyscallManager::instance().syscall(posix, POSIX_EXIT_GROUP, 0);
4151 context->terminatorReturned += 1;
4152 return 1;
4153}
4154
4155void terminateCloneVmBeforeStart(Thread* child, size_t threadId, void* parameter) {
4156 CloneVmExitRaceContext* context = reinterpret_cast<CloneVmExitRaceContext*>(parameter);
4157 if (!context || !child || child->getParent() != context->process) {
4158 return;
4159 }
4160 context->child = child;
4161 context->observedTid = threadId;
4162 auto* user = reinterpret_cast<CloneVmUserFixture*>(context->userAddress);
4163 uintptr_t tlsValue = 0;
4164 const bool copied = PosixSubsystem::copyFromUser(&context->parentTid, &user->parentTid,
4165 sizeof(context->parentTid)) &&
4166 PosixSubsystem::copyFromUser(&context->childTid, &user->childTid,
4167 sizeof(context->childTid)) &&
4168 PosixSubsystem::copyFromUser(&tlsValue, &user->childTls, sizeof(tlsValue));
4169 if (copied && child->getId() == threadId && context->parentTid == static_cast<int>(threadId) &&
4170 context->childTid == static_cast<int>(threadId) &&
4171 tlsValue == reinterpret_cast<uintptr_t>(&user->childTls)) {
4172 context->tidsReady += 1;
4173 }
4174 context->hookCalls += 1;
4175
4176 bool cancelChild = context->action == CancelChildBeforeStart || context->rescueCancellation;
4177 if (!cancelChild) {
4178 const bool released = context->beforeStart.acquire(1, 5, 0);
4179 cancelChild = context->rescueCancellation;
4180 if (!cancelChild) {
4181 if (released && context->ownershipWindowReleased) {
4182 context->controlledHookRelease += 1;
4183 return;
4184 }
4185 Thread* current = Processor::information().getCurrentThread();
4186 if (!released && current && current->getUnwindState() == Thread::TerminateThread) {
4187 context->terminalCancellation += 1;
4188 } else {
4189 context->unexpectedHookRelease += 1;
4190 if (!released) {
4191 context->hookTimedOut += 1;
4192 }
4193 }
4194 return;
4195 }
4196 }
4197
4199 context->childCancellationPublished = 1;
4200 context->childCancellationRequested += 1;
4201 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4202 if (child->isReapableForHostedTest()) {
4203 context->childCancellationReapable += 1;
4204 return;
4205 }
4207 }
4208 context->hookTimedOut += 1;
4209}
4210
4211void observeCloneVmTerminationElection(Process* process, Thread* owner) {
4212 CloneVmExitRaceContext* context = __atomic_load_n(&g_CloneVmExitRaceContext, __ATOMIC_ACQUIRE);
4213 if (!context || process != context->process || owner != context->terminator) {
4214 return;
4215 }
4216 context->terminationElectionCalls += 1;
4217 context->ownershipWindowReleased = 1;
4218 context->beforeStart.release();
4219 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4220 Thread* child = context->child.value();
4221 if (child && context->callerReturned) {
4222 if (child->getUnwindState() == Thread::TerminateThread &&
4223 !child->wasStartPublishedForHostedTest()) {
4224 context->ownershipCancellationObserved += 1;
4225 } else {
4226 context->ownershipStartObserved += 1;
4227 }
4228 context->childCancellationPublished = 1;
4229 return;
4230 }
4232 }
4233 context->electionTimedOut += 1;
4234 context->childCancellationPublished = 1;
4235}
4236
4237void clearCloneVmHooks() {
4238 posixSetCloneBeforeStartHookForTest(nullptr, nullptr);
4239 Process::setTerminationElectionHook(nullptr);
4240 __atomic_store_n(&g_CloneVmExitRaceContext, static_cast<CloneVmExitRaceContext*>(nullptr),
4241 __ATOMIC_RELEASE);
4242}
4243
4244int cloneVmWhileProcessExits(void* parameter) {
4245 CloneVmExitRaceContext* context = reinterpret_cast<CloneVmExitRaceContext*>(parameter);
4246 context->callerEntered += 1;
4247 const size_t pageSize = PhysicalMemoryManager::getPageSize();
4248 const size_t length = (sizeof(CloneVmUserFixture) + pageSize - 1) & ~(pageSize - 1);
4249 uintptr_t address = 0;
4250 if (!context->process->allocateUserRange(Process::UserRegion::Normal, length, address)) {
4251 context->callerReturned += 1;
4252 return 1;
4253 }
4254 uintptr_t mappedAddress = address;
4256 mappedAddress, length, MemoryMappedObject::Read | MemoryMappedObject::Write);
4257 if (!mapping || mappedAddress != address) {
4258 if (mapping) {
4259 MemoryMapManager::instance().remove(mappedAddress, length);
4260 }
4261 context->process->freeUserRange(Process::UserRegion::Normal, address, length);
4262 context->callerReturned += 1;
4263 return 1;
4264 }
4265 context->userAddress = address;
4266 auto* user = reinterpret_cast<CloneVmUserFixture*>(address);
4267 const CloneVmUserFixture initial = {-1, -1, 0, {}};
4268 if (!PosixSubsystem::copyToUser(user, &initial, sizeof(initial))) {
4269 context->callerReturned += 1;
4270 return 1;
4271 }
4272 const bool interruptsWereEnabled = Processor::getInterrupts();
4273 context->callerInterruptsBefore = interruptsWereEnabled ? 1 : 0;
4274 context->cloneResult = SyscallManager::instance().syscall(
4275 posix, POSIX_CLONE,
4276 CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SETTLS |
4277 CLONE_PARENT_SETTID | CLONE_CHILD_SETTID,
4278 reinterpret_cast<uintptr_t>(user->childStack + sizeof(user->childStack)),
4279 reinterpret_cast<uintptr_t>(&user->parentTid), reinterpret_cast<uintptr_t>(&user->childTid),
4280 reinterpret_cast<uintptr_t>(&user->childTls));
4281 context->callerInterruptsAfter = Processor::getInterrupts() ? 1 : 0;
4282 if (Processor::getInterrupts() != interruptsWereEnabled) {
4283 Processor::setInterrupts(interruptsWereEnabled);
4284 }
4285 context->callerReturned += 1;
4286 return 1;
4287}
4288
4289int cleanupCloneVmUserFixture(void* parameter) {
4290 CloneVmExitRaceContext* context = reinterpret_cast<CloneVmExitRaceContext*>(parameter);
4291 if (context->userAddress) {
4292 const size_t pageSize = PhysicalMemoryManager::getPageSize();
4293 const size_t length = (sizeof(CloneVmUserFixture) + pageSize - 1) & ~(pageSize - 1);
4294 MemoryMapManager::instance().remove(context->userAddress, length);
4295 context->process->freeUserRange(Process::UserRegion::Normal, context->userAddress, length);
4296 context->userAddress = 0;
4297 }
4298 return 0;
4299}
4300
4301bool waitForCloneVmHookPause(CloneVmExitRaceContext* context) {
4302 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4303 Thread::WaitDebugInfo info = {};
4304 if (context->hookCalls == static_cast<size_t>(1) && context->caller->getWaitDebugInfo(info) &&
4305 info.queue && info.queued && info.channelOwner == &context->beforeStart &&
4306 context->caller->getStatus() == Thread::Sleeping) {
4307 return true;
4308 }
4309 if (context->unexpectedHookRelease || context->hookTimedOut || context->callerReturned) {
4310 return false;
4311 }
4313 }
4314 return false;
4315}
4316
4317bool waitForCloneVmThreadReapable(Thread* thread) {
4318 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4319 if (thread->isReapableForHostedTest()) {
4320 return true;
4321 }
4323 }
4324 return false;
4325}
4326
4327bool waitForCloneVmThreadCount(Process* process, size_t count) {
4328 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4329 if (process->getNumThreads() == count) {
4330 return true;
4331 }
4333 }
4334 return false;
4335}
4336
4337NORETURN void fatalCloneVmFixture(const char* detail) {
4338 clearCloneVmHooks();
4339 FATAL("HOSTED-SYSCALL-TEST: clone fixture could not retire safely: " << detail);
4340 panic(detail);
4341}
4342
4343bool cloneVmDetachedCancellationReturnsCachedTid(Process* kernelProcess) {
4344 CloneVmExitRaceContext* context = new CloneVmExitRaceContext(CancelChildBeforeStart);
4345 CloneVmExitRaceProcess* process =
4346 new CloneVmExitRaceProcess(kernelProcess, context->processDestructions);
4347 process->setSubsystem(new CloneVmExitRaceSubsystem(context->subsystemDestructions));
4348 process->description() = "hosted clone detached-cancellation fixture";
4349
4350 context->process = process;
4351 context->caller =
4352 new Thread(process, cloneVmWhileProcessExits, context, nullptr, false, true, true);
4353 context->caller->setName("hosted clone detached-cancellation caller");
4354 process->publish();
4355
4356 posixSetCloneBeforeStartHookForTest(terminateCloneVmBeforeStart, context);
4357 const bool callerStarted = context->caller->start();
4358 bool callerReapable = callerStarted && waitForCloneVmThreadReapable(context->caller);
4359 if (!callerReapable) {
4360 context->rescueCancellation = 1;
4361 context->caller->setUnwindState(Thread::TerminateThread);
4362 context->beforeStart.release();
4363 callerReapable = waitForCloneVmThreadReapable(context->caller);
4364 }
4365 if (!callerReapable) {
4366 fatalCloneVmFixture("detached-cancellation caller remained live after rescue");
4367 }
4368 clearCloneVmHooks();
4369
4370 const bool childDeletedBeforeReturn = waitForCloneVmThreadCount(process, 1);
4371 bool passed = callerStarted && callerReapable && childDeletedBeforeReturn &&
4372 process->getState() == Process::Active && context->callerEntered == 1 &&
4373 context->callerReturned == 1 && context->callerInterruptsBefore == 1 &&
4374 context->callerInterruptsAfter == 1 && context->hookCalls == 1 &&
4375 context->tidsReady == 1 && context->childCancellationRequested == 1 &&
4376 context->childCancellationReapable == 1 && !context->hookTimedOut &&
4377 !context->unexpectedHookRelease && context->observedTid &&
4378 context->cloneResult == context->observedTid &&
4379 context->parentTid == static_cast<int>(context->observedTid) &&
4380 context->childTid == static_cast<int>(context->observedTid);
4381
4382 if (!childDeletedBeforeReturn) {
4383 fatalCloneVmFixture("detached child remained live after its creator retired");
4384 }
4385 if (!context->caller->joinForCompletion()) {
4386 fatalCloneVmFixture("detached-cancellation caller could not be joined");
4387 }
4388 if (!waitForCloneVmThreadCount(process, 0)) {
4389 fatalCloneVmFixture("detached-cancellation process retained a live thread");
4390 }
4391 Thread* cleanup = new Thread(process, cleanupCloneVmUserFixture, context, nullptr, false, true);
4392 if (!cleanup->joinForCompletion()) {
4393 fatalCloneVmFixture("detached-cancellation user mapping cleanup could not be joined");
4394 }
4395
4396 delete process;
4397 passed = passed && context->processDestructions == 1 && context->subsystemDestructions == 1;
4398 delete context;
4399
4400 if (!passed) {
4401 ERROR(
4402 "HOSTED-SYSCALL-TEST: FAIL clone-vm-detached-cached-tid: "
4403 "POSIX clone did not return the cached ID after detached cancellation");
4404 return false;
4405 }
4406
4407 NOTICE("HOSTED-SYSCALL-TEST: PASS clone-vm-detached-cached-tid");
4408 return true;
4409}
4410
4411bool cloneVmTerminalStartCancellation(Process* kernelProcess) {
4412 CloneVmExitRaceContext* context = new CloneVmExitRaceContext(WaitForProcessExit);
4413 CloneVmExitRaceProcess* process =
4414 new CloneVmExitRaceProcess(kernelProcess, context->processDestructions);
4415 process->setSubsystem(new CloneVmExitRaceSubsystem(context->subsystemDestructions));
4416 process->description() = "hosted clone-vs-exit fixture";
4417
4418 context->process = process;
4419 context->caller =
4420 new Thread(process, cloneVmWhileProcessExits, context, nullptr, false, true, true);
4421 context->caller->setName("hosted clone-vs-exit caller");
4422 context->terminator =
4423 new Thread(process, terminateCloneVmProcess, context, nullptr, false, true, true);
4424 context->terminator->setName("hosted clone-vs-exit terminator");
4425 process->publish();
4426
4427 __atomic_store_n(&g_CloneVmExitRaceContext, context, __ATOMIC_RELEASE);
4428 Process::setTerminationElectionHook(observeCloneVmTerminationElection);
4429 posixSetCloneBeforeStartHookForTest(terminateCloneVmBeforeStart, context);
4430 const bool callerStarted = context->caller->start();
4431 const bool callerPaused = callerStarted && waitForCloneVmHookPause(context);
4432 Thread* pausedExpectedChild = context->child.value();
4433 Process::ThreadLease pausedChild;
4434 const bool pausedChildPinned = callerPaused && pausedExpectedChild &&
4435 process->acquireThread(pausedChild, pausedExpectedChild) &&
4436 pausedChild->getId() == context->observedTid;
4437 const bool terminatorStarted = context->terminator->start();
4438 if (terminatorStarted) {
4439 context->terminatorStarted += 1;
4440 } else {
4441 context->rescueCancellation = 1;
4442 Thread* rescueExpectedChild = context->child.value();
4443 bool publishedChildSafe = !rescueExpectedChild;
4444 if (pausedChildPinned) {
4446 context->childCancellationPublished = 1;
4447 publishedChildSafe = true;
4448 } else if (rescueExpectedChild) {
4449 Process::ThreadLease rescueChild;
4450 if (process->acquireThread(rescueChild, rescueExpectedChild)) {
4452 context->childCancellationPublished = 1;
4453 publishedChildSafe = true;
4454 } else {
4455 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4456 if (process->getNumThreads() == 2) {
4457 context->child = nullptr;
4458 publishedChildSafe = true;
4459 break;
4460 }
4462 }
4463 }
4464 }
4465 if (!publishedChildSafe) {
4466 pausedChild.reset();
4467 fatalCloneVmFixture("published child could not be cancelled for start-failure rescue");
4468 }
4469 pausedChild.reset();
4470 context->terminator->setUnwindState(Thread::TerminateThread);
4471 context->caller->setUnwindState(Thread::TerminateThread);
4472 context->beforeStart.release();
4473 }
4474 pausedChild.reset();
4475
4476 bool terminated = false;
4477 for (size_t attempt = 0; attempt < HostedAttempts && terminatorStarted; ++attempt) {
4478 if (process->isTerminationReapableForHostedTest()) {
4479 terminated = true;
4480 break;
4481 }
4483 }
4484 if (terminatorStarted && !terminated) {
4485 context->beforeStart.release();
4486 for (size_t attempt = 0; attempt < HostedAttempts; ++attempt) {
4487 if (process->isTerminationReapableForHostedTest()) {
4488 terminated = true;
4489 break;
4490 }
4492 }
4493 }
4494
4495 if (!terminatorStarted) {
4496 const bool callerReapable = waitForCloneVmThreadReapable(context->caller);
4497 const bool terminatorReapable = waitForCloneVmThreadReapable(context->terminator);
4498 if (!callerReapable || !terminatorReapable) {
4499 fatalCloneVmFixture("start-failure rescue left a worker live");
4500 }
4501 clearCloneVmHooks();
4502 if (!context->caller->joinForCompletion() || !context->terminator->joinForCompletion()) {
4503 fatalCloneVmFixture("start-failure rescue could not join both workers");
4504 }
4505 if (!waitForCloneVmThreadCount(process, 0)) {
4506 fatalCloneVmFixture("start-failure rescue retained a cloned child");
4507 }
4508 Thread* cleanup = new Thread(process, cleanupCloneVmUserFixture, context, nullptr, false, true);
4509 if (!cleanup->joinForCompletion()) {
4510 fatalCloneVmFixture("start-failure user mapping cleanup could not be joined");
4511 }
4512 delete process;
4513 const bool destroyed = context->processDestructions == 1 && context->subsystemDestructions == 1;
4514 delete context;
4515 if (!destroyed) {
4516 FATAL("HOSTED-SYSCALL-TEST: clone start-failure rescue did not destroy exact owners");
4517 }
4518 ERROR(
4519 "HOSTED-SYSCALL-TEST: FAIL clone-vm-terminal-start-cancellation: "
4520 "the exit worker did not start");
4521 return false;
4522 }
4523 if (!terminated) {
4524 fatalCloneVmFixture("process-exit cancellation did not become reapable");
4525 }
4526 clearCloneVmHooks();
4527
4528 Thread* retiredChild = context->child.value();
4529 const bool retired = terminated && process->getState() == Process::Terminated &&
4530 process->getNumThreads() == 3 &&
4531 context->caller->getStatus() == Thread::AwaitingJoin &&
4532 context->terminator->getStatus() == Thread::AwaitingJoin && retiredChild &&
4533 retiredChild->getStatus() == Thread::AwaitingJoin &&
4534 !retiredChild->wasStartPublishedForHostedTest();
4535 bool passed =
4536 callerPaused && retired && context->callerEntered == 1 && context->callerReturned == 1 &&
4537 context->callerInterruptsBefore == 1 && context->callerInterruptsAfter == 1 &&
4538 context->terminatorEntered == 1 && !context->terminatorReturned && context->hookCalls == 1 &&
4539 context->tidsReady == 1 && context->terminationElectionCalls == 1 &&
4540 context->ownershipWindowReleased == 1 && context->ownershipCancellationObserved == 1 &&
4541 !context->ownershipStartObserved && context->controlledHookRelease == 1 &&
4542 !context->electionTimedOut && context->childCancellationPublished == 1 &&
4543 context->terminatorStarted == 1 && !context->terminalCancellation &&
4544 !context->unexpectedHookRelease && !context->hookTimedOut && context->observedTid &&
4545 context->cloneResult == context->observedTid &&
4546 context->parentTid == static_cast<int>(context->observedTid) &&
4547 context->childTid == static_cast<int>(context->observedTid);
4548
4549 passed = passed && pausedChildPinned;
4550 delete process;
4551 passed = passed && context->processDestructions == 1 && context->subsystemDestructions == 1;
4552 delete context;
4553
4554 if (!passed) {
4555 ERROR(
4556 "HOSTED-SYSCALL-TEST: FAIL clone-vm-terminal-start-cancellation: "
4557 "terminal cancellation did not retire the published clone exactly once");
4558 return false;
4559 }
4560
4561 NOTICE("HOSTED-SYSCALL-TEST: PASS clone-vm-terminal-start-cancellation");
4562 return true;
4563}
4564
4565bool failedPinnedModuleRejectsUnload() {
4566 Module module;
4567 module.name.assign("hosted-failed-pinned-probe");
4568 module.unloadable = false;
4569 module.status = Module::Failed;
4570
4571 if (KernelElf::claimModuleUnloadForTest(&module) != KernelElf::TestUnloadPinned ||
4572 module.status != Module::Failed || module.unloadComplete) {
4573 ERROR(
4574 "HOSTED-SYSCALL-TEST: FAIL failed-pinned-module: "
4575 "failed initialisation did not preserve its pinned module image");
4576 return false;
4577 }
4578
4579 NOTICE("HOSTED-SYSCALL-TEST: PASS failed-pinned-module");
4580 return true;
4581}
4582
4583ModuleInfo* findStaticModuleInfo(const char* name, size_t& matches) {
4584 ModuleInfo* match = nullptr;
4585 matches = 0;
4586 for (size_t i = 0; i < g_StaticDriverN; ++i) {
4587 ModuleInfo* info = g_StaticDrivers[i];
4588 if (info && info->name && !StringCompare(info->name, name)) {
4589 match = info;
4590 ++matches;
4591 }
4592 }
4593 return match;
4594}
4595
4596bool moduleInfoDependsOn(ModuleInfo* info, const char* dependency, bool optional = false) {
4597 const char** dependencies = optional ? info->opt_dependencies : info->dependencies;
4598 if (!dependencies) {
4599 return false;
4600 }
4601 for (size_t i = 0; dependencies[i]; ++i) {
4602 if (!StringCompare(dependencies[i], dependency)) {
4603 return true;
4604 }
4605 }
4606 return false;
4607}
4608
4609bool linkerModuleMetadataIsPinned() {
4610 size_t matches = 0;
4611 ModuleInfo* linker = findStaticModuleInfo("linker", matches);
4612
4613 if (matches != 1 || !linker || linker->unloadable || linker->runtimeUnloadable ||
4614 !moduleInfoDependsOn(linker, "vfs")) {
4615 ERROR(
4616 "HOSTED-SYSCALL-TEST: FAIL linker-pinned-metadata: "
4617 "the real linker ModuleInfo did not pin its dependency closure");
4618 return false;
4619 }
4620
4621 NOTICE("HOSTED-SYSCALL-TEST: PASS linker-pinned-metadata");
4622 return true;
4623}
4624
4625bool filesystemModuleUnloadPolicyIsCorrect() {
4626 size_t posixMatches = 0;
4627 size_t mountrootMatches = 0;
4628 size_t ramfsMatches = 0;
4629 size_t rawfsMatches = 0;
4630 ModuleInfo* posix = findStaticModuleInfo("posix", posixMatches);
4631 ModuleInfo* mountroot = findStaticModuleInfo("mountroot", mountrootMatches);
4632 ModuleInfo* ramfs = findStaticModuleInfo("ramfs", ramfsMatches);
4633 ModuleInfo* rawfs = findStaticModuleInfo("rawfs", rawfsMatches);
4634
4635 const bool metadataValid =
4636 posixMatches == 1 && mountrootMatches == 1 && ramfsMatches == 1 && rawfsMatches == 1 &&
4637 posix && mountroot && ramfs && rawfs && posix->unloadable && !posix->runtimeUnloadable &&
4638 mountroot->unloadable && !mountroot->runtimeUnloadable && !ramfs->unloadable &&
4639 !ramfs->runtimeUnloadable && rawfs->unloadable && !rawfs->runtimeUnloadable &&
4640 moduleInfoDependsOn(posix, "mountroot") && moduleInfoDependsOn(posix, "ramfs") &&
4641 moduleInfoDependsOn(mountroot, "vfs") && moduleInfoDependsOn(mountroot, "rawfs") &&
4642 moduleInfoDependsOn(mountroot, "ramfs") && moduleInfoDependsOn(mountroot, "fat", true) &&
4643 moduleInfoDependsOn(mountroot, "ext2", true) &&
4644 moduleInfoDependsOn(mountroot, "iso9660", true) && moduleInfoDependsOn(ramfs, "vfs") &&
4645 moduleInfoDependsOn(rawfs, "vfs");
4646 if (!metadataValid) {
4647 ERROR(
4648 "HOSTED-SYSCALL-TEST: FAIL filesystem-unload-policy-metadata: "
4649 "the real filesystem owner metadata did not encode the expected unload policy or "
4650 "dependency closure");
4651 return false;
4652 }
4653
4654 NOTICE("HOSTED-SYSCALL-TEST: PASS filesystem-unload-policy-metadata");
4655 return true;
4656}
4657
4658bool runtimePinnedModuleAllowsLifecycleCleanup() {
4659 g_RuntimePinnedLifecycleCalls = 0;
4660
4661 Module active;
4662 active.name.assign("hosted-runtime-pinned-active-probe");
4663 active.exit = runtimePinnedLifecycleProbe;
4664 active.runtimeUnloadable = false;
4665 active.status = Module::Active;
4666
4667 bool runLifecycle = true;
4668 const KernelElf::TestModuleUnloadClaim explicitActive =
4669 KernelElf::claimModuleUnloadForTest(&active, false, &runLifecycle);
4670 const bool explicitActiveValid = explicitActive == KernelElf::TestUnloadRuntimePinned &&
4671 !runLifecycle && active.status == Module::Active &&
4672 !active.unloadComplete && !g_RuntimePinnedLifecycleCalls;
4673 if (explicitActive == KernelElf::TestUnloadClaimed) {
4674 KernelElf::completeModuleUnloadForTest(&active);
4675 }
4676 if (!explicitActiveValid) {
4677 ERROR(
4678 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4679 "explicit unload escaped the runtime-only lifetime boundary");
4680 return false;
4681 }
4682
4683 const KernelElf::TestModuleUnloadClaim shutdownActive =
4684 KernelElf::claimModuleUnloadForTest(&active, true, &runLifecycle);
4685 const bool shutdownActiveValid = shutdownActive == KernelElf::TestUnloadClaimed && runLifecycle &&
4686 active.status == Module::Unloading;
4687 if (shutdownActive == KernelElf::TestUnloadClaimed) {
4688 KernelElf::completeModuleUnloadForTest(&active, false, runLifecycle);
4689 }
4690 if (!shutdownActiveValid) {
4691 ERROR(
4692 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4693 "shutdown could not claim an active runtime-pinned module");
4694 return false;
4695 }
4696
4697 Module failed;
4698 failed.name.assign("hosted-runtime-pinned-failed-probe");
4699 failed.exit = runtimePinnedLifecycleProbe;
4700 failed.runtimeUnloadable = false;
4701 failed.status = Module::Failed;
4702 runLifecycle = true;
4703 const KernelElf::TestModuleUnloadClaim explicitFailed =
4704 KernelElf::claimModuleUnloadForTest(&failed, false, &runLifecycle);
4705 const bool explicitFailedValid = explicitFailed == KernelElf::TestUnloadRuntimePinned &&
4706 !runLifecycle && failed.status == Module::Failed &&
4707 !failed.unloadComplete;
4708 if (explicitFailed == KernelElf::TestUnloadClaimed) {
4709 KernelElf::completeModuleUnloadForTest(&failed, true);
4710 }
4711 if (!explicitFailedValid) {
4712 ERROR(
4713 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4714 "explicit unload escaped a failed runtime-pinned module");
4715 return false;
4716 }
4717
4718 const KernelElf::TestModuleUnloadClaim failureCleanup =
4719 KernelElf::claimModuleUnloadForTest(&failed, true, &runLifecycle);
4720 const bool failureCleanupValid = failureCleanup == KernelElf::TestUnloadClaimed && runLifecycle &&
4721 failed.status == Module::Unloading;
4722 if (failureCleanup == KernelElf::TestUnloadClaimed) {
4723 KernelElf::completeModuleUnloadForTest(&failed, true, runLifecycle);
4724 }
4725 if (!failureCleanupValid) {
4726 ERROR(
4727 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4728 "failed initialisation could not claim lifecycle cleanup");
4729 return false;
4730 }
4731
4732 if (!active.isUnloaded() || !active.unloadComplete || failed.status != Module::Failed ||
4733 !failed.unloadComplete || g_RuntimePinnedLifecycleCalls != 2) {
4734 ERROR(
4735 "HOSTED-SYSCALL-TEST: FAIL runtime-pinned-cleanup: "
4736 "shutdown or failure cleanup did not publish completion");
4737 return false;
4738 }
4739
4740 NOTICE("HOSTED-SYSCALL-TEST: PASS runtime-pinned-cleanup");
4741 return true;
4742}
4743
4744bool moduleUnloadOwnershipIsRetryable() {
4745 Module module;
4746 module.name.assign("hosted-unload-owner-probe");
4747 module.status = Module::Active;
4748 Module* fixtures[] = {&module};
4749
4750 const KernelElf::TestModuleUnloadClaim first =
4751 KernelElf::claimNamedModuleUnloadForTest(fixtures, 1, "hosted-unload-owner-probe");
4752 const KernelElf::TestModuleUnloadClaim concurrent =
4753 KernelElf::claimNamedModuleUnloadForTest(fixtures, 1, "hosted-unload-owner-probe");
4754 KernelElf::completeModuleUnloadForTest(&module);
4755 const KernelElf::TestModuleUnloadClaim repeat =
4756 KernelElf::claimNamedModuleUnloadForTest(fixtures, 1, "hosted-unload-owner-probe");
4757 const KernelElf::TestModuleUnloadClaim missing =
4758 KernelElf::claimNamedModuleUnloadForTest(fixtures, 1, "hosted-unload-missing-probe");
4759
4760 if (first != KernelElf::TestUnloadClaimed || concurrent != KernelElf::TestUnloadBusy ||
4761 repeat != KernelElf::TestUnloadComplete || missing != KernelElf::TestUnloadUnknown ||
4762 !module.isUnloaded() || !module.unloadComplete ||
4763 !KernelElf::moduleExecutionWaitsForUnloadForTest()) {
4764 ERROR(
4765 "HOSTED-SYSCALL-TEST: FAIL module-unload-ownership: "
4766 "the first owner, concurrent retry, or completed tombstone was lost");
4767 return false;
4768 }
4769
4770 NOTICE("HOSTED-SYSCALL-TEST: PASS module-unload-ownership");
4771 return true;
4772}
4773
4774bool moduleShutdownOrderIsDependencySafe() {
4775 const char* nicsOptional[] = {"ne2k", nullptr};
4776 const char* ne2kDependencies[] = {"network-stack", nullptr};
4777
4778 Module networkStack;
4779 networkStack.name.assign("network-stack");
4780 networkStack.status = Module::Active;
4781
4782 Module nics;
4783 nics.name.assign("nics");
4784 nics.depends_opt = nicsOptional;
4785 nics.runtimeUnloadable = false;
4786 nics.status = Module::Active;
4787
4788 Module ne2k;
4789 ne2k.name.assign("ne2k");
4790 ne2k.depends = ne2kDependencies;
4791 ne2k.status = Module::Active;
4792
4793 Module* modules[] = {&networkStack, &nics, &ne2k};
4794 Module* order[3] = {};
4795 const size_t planned = KernelElf::planModuleUnloadOrderForTest(modules, 3, order, 3);
4796 const size_t repeated = KernelElf::planModuleUnloadOrderForTest(modules, 3, order, 3);
4797
4798 const char* cycleADependencies[] = {"cycle-b", nullptr};
4799 const char* cycleBDependencies[] = {"cycle-a", nullptr};
4800 Module cycleA;
4801 cycleA.name.assign("cycle-a");
4802 cycleA.depends = cycleADependencies;
4803 cycleA.status = Module::Active;
4804 Module cycleB;
4805 cycleB.name.assign("cycle-b");
4806 cycleB.depends = cycleBDependencies;
4807 cycleB.status = Module::Active;
4808 Module* cycle[] = {&cycleA, &cycleB};
4809 Module* cycleOrder[2] = {};
4810 const size_t cyclicPlanned = KernelElf::planModuleUnloadOrderForTest(cycle, 2, cycleOrder, 2);
4811
4812 if (planned != 3 || order[0] != &nics || order[1] != &ne2k || order[2] != &networkStack ||
4813 repeated != 0 || cyclicPlanned != 0 || cycleA.unloadComplete || cycleB.unloadComplete) {
4814 ERROR(
4815 "HOSTED-SYSCALL-TEST: FAIL module-shutdown-order: "
4816 "optional/mandatory dependents were not retired first or a cycle was torn down");
4817 return false;
4818 }
4819
4820 Module permanent;
4821 permanent.name.assign("permanent-shutdown-probe");
4822 permanent.unloadable = false;
4823 permanent.runtimeUnloadable = false;
4824 permanent.status = Module::Active;
4825 Module runtimePinned;
4826 runtimePinned.name.assign("runtime-pinned-shutdown-probe");
4827 runtimePinned.runtimeUnloadable = false;
4828 runtimePinned.status = Module::Active;
4829 Module* retentionModules[] = {&permanent, &runtimePinned};
4830 Module* retentionOrder[2] = {};
4831 const size_t retentionPlanned =
4832 KernelElf::planModuleUnloadOrderForTest(retentionModules, 2, retentionOrder, 2);
4833
4834 if (retentionPlanned != 1 || retentionOrder[0] != &runtimePinned ||
4835 !runtimePinned.unloadComplete || permanent.unloadComplete) {
4836 ERROR(
4837 "HOSTED-SYSCALL-TEST: FAIL module-shutdown-retention-policy: "
4838 "a runtime-only module was retained or a permanent pin was retired");
4839 return false;
4840 }
4841
4842 NOTICE("HOSTED-SYSCALL-TEST: PASS module-shutdown-order");
4843 NOTICE("HOSTED-SYSCALL-TEST: PASS module-shutdown-retention-policy");
4844 return true;
4845}
4846
4847bool publishTerminalBlockedHandlerFixture(Process* kernelProcess) {
4848 TerminalBlockedHandlerContext* context = new TerminalBlockedHandlerContext;
4849 PosixProcess* process = new PosixProcess(kernelProcess);
4850 process->setSubsystem(new PosixSubsystem);
4851 process->description() = "hosted blocked POSIX handler shutdown fixture";
4852 Thread* thread =
4853 new Thread(process, terminalBlockedHandlerEntry, context, nullptr, false, true, true);
4854 thread->setName("hosted blocked POSIX handler fixture");
4855 context->thread = thread;
4856 process->publish();
4857
4858 g_TerminalBlockedHandlerContext = context;
4859 SyscallManager::instance().setHandlerPinHook(terminalBlockedHandlerPin);
4860 const bool started = thread->start();
4861
4862 bool blocked = false;
4863 for (size_t attempt = 0; attempt < HostedAttempts && started; ++attempt) {
4864 Thread::WaitDebugInfo info = {};
4865 if (context->hookEntered == static_cast<size_t>(1) && thread->getWaitDebugInfo(info) &&
4866 info.queue && info.queued && info.channelOwner == &context->blocker &&
4867 thread->getStatus() == Thread::Sleeping) {
4868 blocked = true;
4869 break;
4870 }
4872 }
4873
4874 SyscallManager::instance().setHandlerPinHook(nullptr);
4875
4876 if (!started || !blocked || context->exitStaged != static_cast<size_t>(1) ||
4877 context->releasedByTermination || context->unexpectedRelease || context->syscallReturned) {
4878 ERROR(
4879 "HOSTED-SYSCALL-TEST: FAIL posix-terminal-blocked-handler-fixture: "
4880 "the POSIX handler was not admitted and blocked with a staged process exit");
4881 return false;
4882 }
4883
4884 NOTICE("HOSTED-SYSCALL-TEST: PASS posix-terminal-blocked-handler-fixture-published");
4885 return true;
4886}
4887
4888bool runRegressions() {
4889 NOTICE("HOSTED-SYSCALL-TEST: BEGIN real-event-boundaries");
4890 Thread* thread = Processor::information().getCurrentThread();
4891 if (!thread || thread->getStateLevel()) {
4892 ERROR(
4893 "HOSTED-SYSCALL-TEST: FAIL real-event-boundaries: "
4894 "module initialisation was not at base state");
4895 return false;
4896 }
4897
4898 Process* kernelProcess = Scheduler::instance().getKernelProcess();
4899 if (!kernelProcess) {
4900 return false;
4901 }
4902
4903 NOTICE("HOSTED-SYSCALL-TEST: BEGIN usercopy");
4904 if (!runHostedUsercopyRegressions(kernelProcess)) {
4905 return false;
4906 }
4907
4908 NOTICE("HOSTED-SYSCALL-TEST: BEGIN mmap-placement");
4909 if (!runHostedMmapPlacementRegressions(kernelProcess)) {
4910 return false;
4911 }
4912
4913 NOTICE("HOSTED-SYSCALL-TEST: BEGIN time-syscall-usercopy");
4914 if (!runHostedTimeSyscallRegressions(kernelProcess)) {
4915 return false;
4916 }
4917
4918 NOTICE("HOSTED-SYSCALL-TEST: BEGIN resource-syscall-semantics");
4919 if (!runHostedResourceSyscallRegressions(kernelProcess)) {
4920 return false;
4921 }
4922
4923 NOTICE("HOSTED-SYSCALL-TEST: BEGIN child-resource-accounting");
4924 if (!runHostedChildResourceRegressions(kernelProcess)) {
4925 return false;
4926 }
4927
4928 NOTICE("HOSTED-SYSCALL-TEST: BEGIN faccessat2-semantics");
4929 if (!runHostedAccessSyscallRegressions(kernelProcess)) {
4930 return false;
4931 }
4932
4933 if (!runHostedVmPermissionRegressions() || !runHostedSystemUsercopyRegressions(kernelProcess) ||
4934 !runHostedFutexRobustRegressions(kernelProcess) ||
4935 !runHostedFileContractRegressions(kernelProcess) ||
4936 !runHostedProcessQueryRegressions(kernelProcess) ||
4937 !runHostedTermiosSyscallRegressions(kernelProcess)) {
4938 return false;
4939 }
4940
4941 NOTICE("HOSTED-SYSCALL-TEST: BEGIN sleep-clock-usercopy");
4942 if (!runHostedSleepClockSyscallRegressions(kernelProcess)) {
4943 return false;
4944 }
4945
4946 NOTICE("HOSTED-SYSCALL-TEST: BEGIN posix-exit-status");
4947 if (!runHostedPosixExitStatusRegressions(kernelProcess)) {
4948 return false;
4949 }
4950
4951 NOTICE("HOSTED-SYSCALL-TEST: BEGIN thread-signal-syscalls");
4952 if (!runHostedThreadSignalSyscallRegressions(kernelProcess)) {
4953 return false;
4954 }
4955
4956 NOTICE("HOSTED-SYSCALL-TEST: BEGIN rt-sigsuspend");
4957 if (!runHostedRtSigsuspendRegressions(kernelProcess)) {
4958 return false;
4959 }
4960
4961 bool establishedAliasPassed = true;
4962 NOTICE("HOSTED-SYSCALL-TEST: BEGIN directory-retained-lookup-atomicity");
4963 establishedAliasPassed &= directoryRetainedLookupAtomicity(kernelProcess);
4964
4965 NOTICE("HOSTED-SYSCALL-TEST: BEGIN directory-retained-lookup-lifecycle");
4966 establishedAliasPassed &= directoryRetainedLookupLifecycle(kernelProcess);
4967
4968 NOTICE("HOSTED-SYSCALL-TEST: BEGIN vfs-established-alias-serialization");
4969 establishedAliasPassed &= establishedAliasRetainSerialization(kernelProcess);
4970
4971 NOTICE("HOSTED-SYSCALL-TEST: BEGIN file-established-alias-lifetime");
4972 establishedAliasPassed &= establishedFileAliasLifetime();
4973
4974 NOTICE("HOSTED-SYSCALL-TEST: BEGIN process-filesystem-context-lifetime");
4975 establishedAliasPassed &= processFilesystemContextLifetime(kernelProcess);
4976
4977 NOTICE("HOSTED-SYSCALL-TEST: BEGIN mmap-established-alias-lifetime");
4978 establishedAliasPassed &= establishedMappingAliasLifetime();
4979
4980 NOTICE("HOSTED-SYSCALL-TEST: BEGIN munmap-target-page-geometry");
4981 establishedAliasPassed &= munmapUsesTargetPageGeometry(thread);
4982
4983 NOTICE("HOSTED-SYSCALL-TEST: BEGIN mmap-split-alias-lifetime");
4984 establishedAliasPassed &= mappingManagerSplitLifetime(kernelProcess);
4985
4986 NOTICE("HOSTED-SYSCALL-TEST: BEGIN posix-path-lookup-lifetime");
4987 establishedAliasPassed &= posixPathLookupLifetime(kernelProcess);
4988 if (!establishedAliasPassed) {
4989 return false;
4990 }
4991
4992 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-close-pinning");
4993 if (!descriptorClosePinning(kernelProcess)) {
4994 return false;
4995 }
4996
4997 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-close-generation");
4998 if (!descriptorCloseGeneration(kernelProcess)) {
4999 return false;
5000 }
5001
5002 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-open-file-description-state");
5003 if (!descriptorOpenFileDescriptionState()) {
5004 return false;
5005 }
5006
5007 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-open-file-description-lifetime");
5008 if (!descriptorOpenFileDescriptionLifetime()) {
5009 return false;
5010 }
5011
5012 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-append-policy");
5013 if (!descriptorAppendPolicy(kernelProcess)) {
5014 return false;
5015 }
5016
5017 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-nonblocking-policy");
5018 if (!descriptorNonblockingPolicy()) {
5019 return false;
5020 }
5021
5022 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-position-alias-serialization");
5023 if (!descriptorPositionAliasSerialization(kernelProcess)) {
5024 return false;
5025 }
5026
5027 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-vector-io-serialization");
5028 if (!descriptorVectorIoSerialization(kernelProcess)) {
5029 return false;
5030 }
5031
5032 NOTICE("HOSTED-SYSCALL-TEST: BEGIN advisory-lock-fail-closed");
5033 if (!runHostedAdvisoryLockRegressions(kernelProcess)) {
5034 return false;
5035 }
5036
5037 NOTICE("HOSTED-SYSCALL-TEST: BEGIN scalar-io-user-buffer-lifetime");
5038 if (!runHostedScalarIoRegressions(kernelProcess)) {
5039 return false;
5040 }
5041
5042 NOTICE("HOSTED-SYSCALL-TEST: BEGIN positional-io-semantics");
5043 if (!runHostedPositionalIoRegressions(kernelProcess)) {
5044 return false;
5045 }
5046
5047 NOTICE("HOSTED-SYSCALL-TEST: BEGIN positional-vector-io-semantics");
5048 if (!runHostedPositionalVectorIoRegressions(kernelProcess)) {
5049 return false;
5050 }
5051
5052 NOTICE("HOSTED-SYSCALL-TEST: BEGIN vector-io-user-buffer-lifetime");
5053 if (!runHostedVectorIoRegressions(kernelProcess)) {
5054 return false;
5055 }
5056
5057 NOTICE("HOSTED-SYSCALL-TEST: BEGIN ppoll-linux-abi");
5058 if (!runHostedPpollRegressions(kernelProcess)) {
5059 return false;
5060 }
5061
5062 NOTICE("HOSTED-SYSCALL-TEST: BEGIN pselect-linux-abi");
5063 if (!runHostedPselectRegressions(kernelProcess)) {
5064 return false;
5065 }
5066
5067 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-dup-contract");
5068 if (!descriptorDupContract(kernelProcess)) {
5069 return false;
5070 }
5071
5072 NOTICE("HOSTED-SYSCALL-TEST: BEGIN dup3-atomic-replacement");
5073 if (!runHostedDup3Regressions(kernelProcess)) {
5074 return false;
5075 }
5076
5077 NOTICE("HOSTED-SYSCALL-TEST: BEGIN descriptor-position-policy");
5078 if (!descriptorPositionPolicy()) {
5079 return false;
5080 }
5081
5082 NOTICE("HOSTED-SYSCALL-TEST: BEGIN select-projection");
5083 if (!selectProjectionContract()) {
5084 return false;
5085 }
5086
5087 NOTICE("HOSTED-SYSCALL-TEST: BEGIN pipe-poll-readiness");
5088 if (!pipePollReadiness(kernelProcess)) {
5089 return false;
5090 }
5091
5092 NOTICE("HOSTED-SYSCALL-TEST: BEGIN epoll-level-oneshot-ofd-lifetime");
5093 if (!epollLevelOneShotAndOfdLifetime(kernelProcess)) {
5094 return false;
5095 }
5096
5097 NOTICE("HOSTED-SYSCALL-TEST: BEGIN epoll-reordered-transition-publication");
5098 if (!epollReorderedTransitionPublication(kernelProcess)) {
5099 return false;
5100 }
5101
5102 NOTICE("HOSTED-SYSCALL-TEST: BEGIN epoll-persistent-fifo-reopen-reclose");
5103 if (!epollPersistentFifoReopenReclose(kernelProcess)) {
5104 return false;
5105 }
5106
5107 NOTICE("HOSTED-SYSCALL-TEST: BEGIN eventfd-counter-readiness-lifetime");
5108 if (!runHostedEventFdRegressions(kernelProcess)) {
5109 return false;
5110 }
5111
5112 NOTICE("HOSTED-SYSCALL-TEST: BEGIN inotify-vfs-epoll-lifetime");
5113 if (!runHostedInotifyRegressions(kernelProcess)) {
5114 return false;
5115 }
5116
5117 NOTICE("HOSTED-SYSCALL-TEST: BEGIN poll-close-reuse-cleanup");
5118 if (!pollCloseReuseCleanup(kernelProcess)) {
5119 return false;
5120 }
5121
5122 NOTICE("HOSTED-SYSCALL-TEST: BEGIN posix-teardown-contention");
5123 if (!posixTeardownContention(kernelProcess)) {
5124 return false;
5125 }
5126
5127 NOTICE("HOSTED-SYSCALL-TEST: BEGIN unix-bind-replacement-lifetime");
5128 if (!runHostedUnixEndpointLifetimeRegression(kernelProcess)) {
5129 return false;
5130 }
5131
5132 NOTICE("HOSTED-SYSCALL-TEST: BEGIN scm-rights-datagram");
5133 if (!runHostedScmRightsRegressions(kernelProcess)) {
5134 return false;
5135 }
5136
5137 NOTICE("HOSTED-SYSCALL-TEST: BEGIN scm-rights-stream");
5138 if (!runHostedScmStreamRegressions(kernelProcess)) {
5139 return false;
5140 }
5141
5142 NOTICE("HOSTED-SYSCALL-TEST: BEGIN unix-stream-interruption");
5143 if (!runHostedUnixStreamInterruptionRegressions(kernelProcess)) {
5144 return false;
5145 }
5146
5147 NOTICE("HOSTED-SYSCALL-TEST: BEGIN socket-zero-result-signal");
5148 if (!zeroResultWinsSignal(thread)) {
5149 return false;
5150 }
5151
5152 NOTICE("HOSTED-SYSCALL-TEST: BEGIN clone-errno-lifetime");
5153 if (!cloneStateDropsParentErrnoDestination()) {
5154 return false;
5155 }
5156
5157 NOTICE("HOSTED-SYSCALL-TEST: BEGIN clone-process-routing");
5158 if (!runHostedCloneRoutingRegressions(kernelProcess)) {
5159 return false;
5160 }
5161
5162 NOTICE("HOSTED-SYSCALL-TEST: BEGIN clone-vm-null-stack-interrupts");
5163 if (!cloneVmNullStackPreservesInterrupts()) {
5164 return false;
5165 }
5166
5167 NOTICE("HOSTED-SYSCALL-TEST: BEGIN clone-vm-detached-cached-tid");
5168 if (!cloneVmDetachedCancellationReturnsCachedTid(kernelProcess)) {
5169 return false;
5170 }
5171
5172 NOTICE("HOSTED-SYSCALL-TEST: BEGIN clone-vm-terminal-start-cancellation");
5173 if (!cloneVmTerminalStartCancellation(kernelProcess)) {
5174 return false;
5175 }
5176
5177 NOTICE("HOSTED-SYSCALL-TEST: BEGIN failed-pinned-module");
5178 if (!failedPinnedModuleRejectsUnload()) {
5179 return false;
5180 }
5181
5182 NOTICE("HOSTED-SYSCALL-TEST: BEGIN linker-pinned-metadata");
5183 if (!linkerModuleMetadataIsPinned()) {
5184 return false;
5185 }
5186
5187 NOTICE("HOSTED-SYSCALL-TEST: BEGIN filesystem-unload-policy-metadata");
5188 if (!filesystemModuleUnloadPolicyIsCorrect()) {
5189 return false;
5190 }
5191
5192 NOTICE("HOSTED-SYSCALL-TEST: BEGIN runtime-pinned-cleanup");
5193 if (!runtimePinnedModuleAllowsLifecycleCleanup()) {
5194 return false;
5195 }
5196
5197 NOTICE("HOSTED-SYSCALL-TEST: BEGIN module-unload-ownership");
5198 if (!moduleUnloadOwnershipIsRetryable()) {
5199 return false;
5200 }
5201
5202 NOTICE("HOSTED-SYSCALL-TEST: BEGIN module-shutdown-order");
5203 if (!moduleShutdownOrderIsDependencySafe()) {
5204 return false;
5205 }
5206
5208 static char pedigreeCModule[] = "pedigree-c";
5209 static char lwipModule[] = "lwip";
5210 static char networkStackModule[] = "network-stack";
5211 const uintptr_t sigretResult = manager.syscall(posix, PEDIGREE_SIGRET);
5212 const uintptr_t unwindResult = manager.syscall(posix, PEDIGREE_UNWIND_SIGNAL);
5213 const uintptr_t eventReturnResult = manager.syscall(pedigree_c, PEDIGREE_EVENT_RETURN);
5214 const uintptr_t selfUnloadResult = manager.syscall(pedigree_c, PEDIGREE_MODULE_UNLOAD,
5215 reinterpret_cast<uintptr_t>(pedigreeCModule));
5216 const uintptr_t stillLoadedResult = manager.syscall(pedigree_c, PEDIGREE_MODULE_IS_LOADED,
5217 reinterpret_cast<uintptr_t>(pedigreeCModule));
5218 const uintptr_t lwipUnloadResult =
5219 manager.syscall(pedigree_c, PEDIGREE_MODULE_UNLOAD, reinterpret_cast<uintptr_t>(lwipModule));
5220 const uintptr_t lwipStillLoadedResult = manager.syscall(pedigree_c, PEDIGREE_MODULE_IS_LOADED,
5221 reinterpret_cast<uintptr_t>(lwipModule));
5222 const uintptr_t networkStackUnloadResult = manager.syscall(
5223 pedigree_c, PEDIGREE_MODULE_UNLOAD, reinterpret_cast<uintptr_t>(networkStackModule));
5224 const uintptr_t networkStackStillLoadedResult = manager.syscall(
5225 pedigree_c, PEDIGREE_MODULE_IS_LOADED, reinterpret_cast<uintptr_t>(networkStackModule));
5226
5227 if (sigretResult != static_cast<uintptr_t>(-1) || unwindResult != static_cast<uintptr_t>(-1) ||
5228 eventReturnResult != static_cast<uintptr_t>(-1) ||
5229 selfUnloadResult != static_cast<uintptr_t>(-1) || stillLoadedResult != 1 ||
5230 lwipUnloadResult != static_cast<uintptr_t>(-1) || lwipStillLoadedResult != 1 ||
5231 networkStackUnloadResult != static_cast<uintptr_t>(-1) ||
5232 networkStackStillLoadedResult != 1 || thread->getStateLevel() || thread->getErrno()) {
5233 ERROR(
5234 "HOSTED-SYSCALL-TEST: FAIL real-event-boundaries: "
5235 "a public misuse path escaped its lifetime boundary");
5236 return false;
5237 }
5238
5239 NOTICE("HOSTED-SYSCALL-TEST: PASS real-event-boundaries");
5240
5241 PosixProcess* terminalFixture = new PosixProcess(kernelProcess);
5242 terminalFixture->setSubsystem(new PosixSubsystem);
5243 terminalFixture->description() = "hosted zero-thread POSIX shutdown fixture";
5244 terminalFixture->publish();
5245 if (terminalFixture->getNumThreads() != 0 || terminalFixture->getType() != Process::Posix) {
5246 ERROR(
5247 "HOSTED-SYSCALL-TEST: FAIL posix-terminal-drain-fixture: "
5248 "fixture was not published as an ownerless POSIX process");
5249 return false;
5250 }
5251 NOTICE("HOSTED-SYSCALL-TEST: PASS posix-terminal-drain-fixture-published");
5252
5253 if (!posixDuplicateInitRollbackPreservesProcessForTest(terminalFixture)) {
5254 ERROR(
5255 "HOSTED-SYSCALL-TEST: FAIL posix-duplicate-init-rollback: "
5256 "an unowned duplicate initialisation retired an existing POSIX process");
5257 return false;
5258 }
5259 NOTICE("HOSTED-SYSCALL-TEST: PASS posix-duplicate-init-rollback-preserved-process");
5260
5261 PosixProcess* createdFixture = new PosixProcess(kernelProcess);
5262 createdFixture->setSubsystem(new PosixSubsystem);
5263 createdFixture->description() = "hosted Created-thread POSIX shutdown fixture";
5264 Thread* createdThread =
5265 new Thread(createdFixture, terminalCreatedFixtureEntry, nullptr, nullptr, false, true, true);
5266 createdThread->setName("hosted terminal Created-thread fixture");
5267 createdFixture->publish();
5268 if (createdFixture->getNumThreads() != 1 || createdThread->getStatus() != Thread::Created ||
5269 createdFixture->getType() != Process::Posix) {
5270 ERROR(
5271 "HOSTED-SYSCALL-TEST: FAIL posix-terminal-drain-created-fixture: "
5272 "fixture did not retain its unstarted ordinary entry");
5273 return false;
5274 }
5275 NOTICE("HOSTED-SYSCALL-TEST: PASS posix-terminal-drain-created-fixture-published");
5276
5277 if (!publishTerminalBlockedHandlerFixture(kernelProcess)) {
5278 return false;
5279 }
5280 return true;
5281}
5282
5283bool entry() {
5284 const bool passed =
5285 hostedSyscallProfileRequested() ? hostedRunSyscallProfile() : runRegressions();
5286 system_reset();
5287 return passed;
5288}
5289
5290void exit() {
5291 TerminalBlockedHandlerContext* context = g_TerminalBlockedHandlerContext;
5292 g_TerminalBlockedHandlerContext = nullptr;
5293 if (!context) {
5294 return;
5295 }
5296
5297 if (context->hookEntered != static_cast<size_t>(1) ||
5298 context->exitStaged != static_cast<size_t>(1) ||
5299 context->releasedByTermination != static_cast<size_t>(1) || context->unexpectedRelease ||
5300 context->syscallReturned) {
5301 ERROR(
5302 "HOSTED-SYSCALL-TEST: FAIL posix-terminal-blocked-handler-release: "
5303 "terminal process teardown did not release the admitted handler before module exit");
5304 } else {
5305 NOTICE("HOSTED-SYSCALL-TEST: PASS posix-terminal-blocked-handler-released-by-process-exit");
5306 }
5307 delete context;
5308}
5309} // namespace
5310
5311MODULE_INFO("hosted-syscall-smoke", &entry, &exit, "posix", "pedigree-c");
Memory-mapped file interface.
static Directory * fromFile(File *pF)
Definition Directory.h:148
bool addDirectoryEntry(const String &name, File *pTarget)
Definition Directory.cc:817
Mutex & namespaceMutationLock()
Definition Directory.h:410
AddStatus addEphemeralFile(File *pFile)
Add an ephemeral file to the directory.
Definition Directory.cc:906
void remove(const HashedStringView &s)
Definition Directory.cc:579
bool removeDirectoryEntry(const HashedStringView &name, File *expected)
Definition Directory.cc:876
virtual File * convertToFile(const DirectoryEntryMetadata &meta)
MUST_USE_RESULT bool lookupRetained(const HashedStringView &s, ChildLease &child) const
Definition Directory.cc:341
MUST_USE_RESULT LookupStatus lookupChild(const HashedStringView &s, ChildLease &child) const
Definition Directory.cc:345
Definition Disk.h:35
void setNetworkImpl(const SharedPointer< NetworkSyscalls > &implementation)
OpenFileDescriptionLease acquireOpenFileDescription() const
void setOffset(uint64_t offset)
PositionGuard lockPosition() const
int getFlags() const
Get current descriptor flags.
int getStatusFlags() const
Get current status flags.
size_t fd
Descriptor number.
uint64_t write(uint64_t size, uintptr_t buffer, bool canBlock=true)
uint64_t getOffset() const
Definition File.h:74
virtual uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition File.cc:1233
virtual bool isSeekable() const
Definition File.cc:708
virtual uint64_t read(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true) final
Definition File.cc:230
virtual bool isBytewise() const
Definition File.cc:1229
virtual ReadyMask queryReady(bool reading, bool writing)
Definition File.cc:926
void dataChanged()
Definition File.cc:1316
virtual uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition File.cc:1240
virtual bool supportsReadinessNotifications() const
Definition File.cc:937
virtual bool removeNode(File *parent, const String &filename, File *file)=0
virtual bool initialise(Disk *pDisk)=0
virtual const String & getVolumeLabel() const =0
bool createSymlink(const StringView &path, const String &value, File *pStartNode=0)
bool createDirectory(const StringView &path, uint32_t mask, File *pStartNode=0)
virtual File * getRoot() const =0
bool createFile(const StringView &path, uint32_t mask, File *pStartNode=0)
MemoryMappedObject * mapAnon(uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms)
size_t remove(uintptr_t base, size_t length)
static MemoryMapManager & instance()
MemoryMappedObject * mapFile(File *pFile, uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms, size_t offset=0, bool bCopyOnWrite=true)
virtual MemoryMappedObject * split(uintptr_t at)=0
virtual MemoryMappedObject * clone()=0
Definition Mutex.h:56
virtual ReadyMask queryReady(bool reading, bool writing)
Definition Pipe.h:36
size_t getReaderCount()
Definition Pipe.cc:306
bool acquireFileDescriptor(size_t fd, DescriptorLease &descriptor)
void setAbi(Abi which)
bool closeFileDescriptor(size_t fd, const DescriptorLease &descriptor)
static bool copyFromUser(void *destination, const void *source, size_t count, size_t elementSize=1)
static bool copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)
void addFileDescriptor(size_t fd, FileDescriptor *pFd)
size_t getNumThreads()
Definition Process.cc:1243
void publish()
Definition Process.cc:832
LargeStaticString & description()
Definition Process.h:473
MUST_USE_RESULT bool acquireThread(ThreadLease &lease, size_t n)
Definition Process.cc:1248
static bool getInterrupts()
static ProcessorInformation & information()
static void setInterrupts(bool bEnable)
void notifyReadiness(ReadyMask mask)
Definition Readiness.cc:201
virtual ReadinessGenerations readinessGenerations()
Definition Readiness.cc:177
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:226
MUST_USE_RESULT bool acquireForCompletion(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
Definition Semaphore.cc:369
static SharedPointer< FilesystemContext > tryAdopt(FilesystemContext *ptr)
T * get() const
virtual uintptr_t syscall(Service_t service, uintptr_t function, uintptr_t p1=0, uintptr_t p2=0, uintptr_t p3=0, uintptr_t p4=0, uintptr_t p5=0)=0
static EXPORTED_PUBLIC SyscallManager & instance()
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 getWaitDebugInfo(WaitDebugInfo &info)
Definition Thread.cc:3184
size_t getErrno()
Definition Thread.h:473
bool joinForCompletion()
Definition Thread.cc:2771
bool join()
Definition Thread.cc:2767
Status getStatus() const
Definition Thread.h:431
UnwindType getUnwindState()
Definition Thread.h:531
DebugState getDebugState(uintptr_t &address)
Definition Thread.h:570
Process * getParent() const
Definition Thread.h:338
size_t getId()
Definition Thread.h:463
bool start()
Definition Thread.cc:794
size_t getStateLevel() const
Definition Thread.h:314
MUST_USE_RESULT bool retainTrackedFile(File *pFile)
Definition VFS.cc:1455
bool untrackFile(File *pFile, bool destroy=true)
Definition VFS.cc:1477
Filesystem * getRootFilesystem() const
Definition VFS.cc:631
static VFS & instance()
Definition VFS.cc:291
void trackFile(File *pFile)
Track a File object that exists. It is necessary to keep track of File objects, or at least those tha...
Definition VFS.cc:1440
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:117
Definition waits.c:9