The Pedigree Project 0.1
usercopy-regressions.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "pedigree/kernel/Log.h"
9#include "pedigree/kernel/errors.h"
10#include "pedigree/kernel/process/Process.h"
11#include "pedigree/kernel/process/Thread.h"
12#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
13#include "pedigree/kernel/processor/Processor.h"
14#include "pedigree/kernel/processor/VirtualAddressSpace.h"
15#include "pedigree/kernel/utilities/utility.h"
16
17#include <signal.h>
18
19#include "modules/subsys/posix/PosixSubsystem.h"
20#include "modules/subsys/posix/signal-syscalls.h"
22
23bool runHostedUsercopyRegressions(Process* process) {
24 size_t extent = 99;
25 const size_t maximum = ~static_cast<size_t>(0);
26 const bool overflowRejected =
27 !PosixSubsystem::checkedUserBufferSize(maximum, 2, extent) && !extent;
28 const bool zeroExtentAccepted = PosixSubsystem::checkedUserBufferSize(maximum, 0, extent) &&
29 !extent && PosixSubsystem::copyFromUser(nullptr, nullptr, 0) &&
30 PosixSubsystem::copyToUser(nullptr, nullptr, 0);
31
32 const size_t pageSize = PhysicalMemoryManager::getPageSize();
33 const size_t mappingLength = pageSize * 2;
34 uintptr_t address = 0;
35 if (!process->allocateUserRange(Process::UserRegion::Normal, mappingLength, address)) {
36 ERROR("HOSTED-SYSCALL-TEST: FAIL usercopy: could not reserve userspace range");
37 return false;
38 }
39
40 uintptr_t mappedAddress = address;
42 mappedAddress, mappingLength,
43 MemoryMappedObject::Read | MemoryMappedObject::Write | MemoryMappedObject::Exec);
44 if (!mapping || mappedAddress != address) {
45 MemoryMapManager::instance().remove(address, mappingLength);
46 process->freeUserRange(Process::UserRegion::Normal, address, mappingLength);
47 ERROR("HOSTED-SYSCALL-TEST: FAIL usercopy: could not map userspace range");
48 return false;
49 }
50
51 const uint8_t source[] = {0x31, 0x42, 0x53, 0x64, 0x75, 0x86};
52 uint8_t result[sizeof(source)] = {};
53 void* userBuffer = reinterpret_cast<void*>(address + pageSize - 3);
54
55 const bool roundTrip = PosixSubsystem::copyToUser(userBuffer, source, sizeof(source)) &&
56 PosixSubsystem::copyFromUser(result, userBuffer, sizeof(result)) &&
57 !MemoryCompare(source, result, sizeof(source));
58 const char sourceString[] = {'u', 's', 'e', 'r', 0};
59 String copiedString;
60 const bool stringSnapshot =
61 PosixSubsystem::copyToUser(userBuffer, sourceString, sizeof(sourceString)) &&
62 PosixSubsystem::copyUserString(reinterpret_cast<const char*>(userBuffer), copiedString,
63 sizeof(sourceString)) == PosixSubsystem::UserStringSuccess &&
64 copiedString == String("user");
65 const bool executableUserAccepted = PosixSubsystem::checkAddress(
66 reinterpret_cast<uintptr_t>(userBuffer), 1, PosixSubsystem::SafeExecute);
67 const uintptr_t kernelStart = Processor::information().getVirtualAddressSpace().getKernelStart();
68 const bool kernelPointerRejected =
69 !PosixSubsystem::copyFromUser(result, reinterpret_cast<const void*>(kernelStart), 1) &&
70 !PosixSubsystem::checkAddress(kernelStart, 1, PosixSubsystem::SafeExecute);
71 const bool nullPointersRejected = !PosixSubsystem::copyFromUser(nullptr, userBuffer, 1) &&
72 !PosixSubsystem::copyFromUser(result, nullptr, 1) &&
73 !PosixSubsystem::copyToUser(nullptr, source, 1) &&
74 !PosixSubsystem::copyToUser(userBuffer, nullptr, 1);
75
76 auto& manager = MemoryMapManager::instance();
77 bool mappingCoverage = manager.allows(address, mappingLength,
78 MemoryMappedObject::Read | MemoryMappedObject::Write |
79 MemoryMappedObject::Exec) &&
80 manager.allows(reinterpret_cast<uintptr_t>(userBuffer), sizeof(source),
81 MemoryMappedObject::Read | MemoryMappedObject::Write);
82 mappingCoverage =
83 manager.setPermissions(address + pageSize, pageSize, MemoryMappedObject::Read) == 1 &&
84 manager.allows(address, mappingLength, MemoryMappedObject::Read) &&
85 !manager.allows(address, mappingLength, MemoryMappedObject::Write) &&
86 manager.allows(address, pageSize, MemoryMappedObject::Write) &&
87 manager.allows(address + pageSize, pageSize, MemoryMappedObject::Read) &&
88 !manager.allows(address + pageSize, pageSize, MemoryMappedObject::Write) && mappingCoverage;
89 mappingCoverage =
90 manager.setPermissions(address + pageSize, pageSize, MemoryMappedObject::None) == 1 &&
91 !manager.allows(address + pageSize, pageSize, MemoryMappedObject::Read) &&
92 manager.allows(address + pageSize, pageSize, MemoryMappedObject::None) && mappingCoverage;
93 mappingCoverage = manager.setPermissions(address, mappingLength,
94 MemoryMappedObject::Read | MemoryMappedObject::Write |
95 MemoryMappedObject::Exec) == 2 &&
96 mappingCoverage;
97
98 Thread* currentThread = Processor::information().getCurrentThread();
99 const uint64_t originalMask = currentThread->getSignalMask();
100 const uint64_t requestedMask = (static_cast<uint64_t>(1) << (SIGUSR1 - 1)) |
101 (static_cast<uint64_t>(1) << (SIGKILL - 1)) |
102 (static_cast<uint64_t>(1) << (SIGSTOP - 1));
103 void* userMask = reinterpret_cast<void*>(address + 64);
104 void* userOldMask = reinterpret_cast<void*>(address + 128);
105 uint64_t returnedMask = 0;
106 const bool signalMaskSnapshots =
107 PosixSubsystem::copyToUser(userMask, &requestedMask, sizeof(requestedMask)) &&
108 posix_sigprocmask(SIG_SETMASK, userMask, userOldMask, sizeof(uint64_t), true) == 0 &&
109 PosixSubsystem::copyFromUser(&returnedMask, userOldMask, sizeof(returnedMask)) &&
110 returnedMask == originalMask &&
111 currentThread->getSignalMask() ==
112 (requestedMask & ~((static_cast<uint64_t>(1) << (SIGKILL - 1)) |
113 (static_cast<uint64_t>(1) << (SIGSTOP - 1))));
114 currentThread->setSignalMask(originalMask);
115
116 const Thread::AlternateSignalStack originalStack = currentThread->getAlternateSignalStack();
117 stack_t disableStack = {};
118 disableStack.ss_flags = SS_DISABLE;
119 void* userStack = reinterpret_cast<void*>(address + 256);
120 void* userOldStack = reinterpret_cast<void*>(address + 384);
121 stack_t returnedStack = {};
122 const int expectedOldStackFlags =
123 originalStack.enabled ? (originalStack.inUse ? SS_ONSTACK : 0) : SS_DISABLE;
124 const bool alternateStackSnapshots =
125 PosixSubsystem::copyToUser(userStack, &disableStack, sizeof(disableStack)) &&
126 posix_sigaltstack(reinterpret_cast<const stack_t*>(userStack),
127 reinterpret_cast<stack_t*>(userOldStack)) == 0 &&
128 PosixSubsystem::copyFromUser(&returnedStack, userOldStack, sizeof(returnedStack)) &&
129 returnedStack.ss_flags == expectedOldStackFlags &&
130 (!originalStack.enabled ||
131 (returnedStack.ss_sp == reinterpret_cast<void*>(originalStack.base) &&
132 returnedStack.ss_size == originalStack.size)) &&
133 !currentThread->getAlternateSignalStack().enabled;
134 currentThread->getAlternateSignalStack() = originalStack;
135
136 size_t checkedExtent = 99;
137 const bool arrayRangeAccepted = PosixSubsystem::checkUserBuffer(
138 reinterpret_cast<uintptr_t>(userBuffer), 2, 3, PosixSubsystem::SafeRead, &checkedExtent);
139 const bool overflowRangeRejected =
140 !PosixSubsystem::checkUserBuffer(address, maximum, 2, PosixSubsystem::SafeRead,
141 &checkedExtent) &&
142 !checkedExtent;
143 const size_t reducedPermissionObjects =
144 manager.setPermissions(address, mappingLength, MemoryMappedObject::Read);
145 const bool reducedPermissionsRejected =
146 reducedPermissionObjects == 2 && !PosixSubsystem::copyToUser(userBuffer, source, 1) &&
147 PosixSubsystem::copyFromUser(result, userBuffer, 1) &&
148 !PosixSubsystem::checkAddress(reinterpret_cast<uintptr_t>(userBuffer), 1,
149 PosixSubsystem::SafeExecute);
150 currentThread->setErrno(0);
151 const uint64_t maskBeforeRejectedOutput = currentThread->getSignalMask();
152 const bool signalMaskReadOnlyOutputRejected =
153 posix_sigprocmask(SIG_SETMASK, userMask, userOldMask, sizeof(uint64_t), true) == -1 &&
154 currentThread->getErrno() == Error::BadAddress &&
155 currentThread->getSignalMask() == maskBeforeRejectedOutput;
156 currentThread->setSignalMask(originalMask);
157 currentThread->setErrno(0);
158 const bool alternateStackReadOnlyOutputRejected =
159 posix_sigaltstack(reinterpret_cast<const stack_t*>(userStack),
160 reinterpret_cast<stack_t*>(userOldStack)) == -1 &&
161 currentThread->getErrno() == Error::BadAddress &&
162 currentThread->getAlternateSignalStack().base == originalStack.base &&
163 currentThread->getAlternateSignalStack().size == originalStack.size &&
164 currentThread->getAlternateSignalStack().enabled == originalStack.enabled &&
165 currentThread->getAlternateSignalStack().inUse == originalStack.inUse;
166 currentThread->getAlternateSignalStack() = originalStack;
167
168 const size_t removed = MemoryMapManager::instance().remove(address + pageSize, pageSize);
169 const bool holeCoverage = !manager.allows(address, mappingLength, MemoryMappedObject::Read) &&
170 manager.allows(address, pageSize, MemoryMappedObject::Read) &&
171 !manager.allows(address + pageSize, pageSize, MemoryMappedObject::Read);
172 const bool incompleteRangeRejected =
173 removed == 1 && !PosixSubsystem::copyFromUser(result, userBuffer, sizeof(result));
174 currentThread->setErrno(0);
175 const uint64_t maskBeforeRejectedCopy = currentThread->getSignalMask();
176 const bool signalMaskIncompleteRangeRejected =
177 posix_sigprocmask(SIG_SETMASK, userBuffer, nullptr, sizeof(uint64_t), true) == -1 &&
178 currentThread->getErrno() == Error::BadAddress &&
179 currentThread->getSignalMask() == maskBeforeRejectedCopy;
180 currentThread->setErrno(0);
181 const bool alternateStackIncompleteRangeRejected =
182 posix_sigaltstack(reinterpret_cast<const stack_t*>(userBuffer), nullptr) == -1 &&
183 currentThread->getErrno() == Error::BadAddress &&
184 currentThread->getAlternateSignalStack().base == originalStack.base &&
185 currentThread->getAlternateSignalStack().size == originalStack.size &&
186 currentThread->getAlternateSignalStack().enabled == originalStack.enabled &&
187 currentThread->getAlternateSignalStack().inUse == originalStack.inUse;
188 currentThread->setErrno(0);
189
190 MemoryMapManager::instance().remove(address, pageSize);
191 process->freeUserRange(Process::UserRegion::Normal, address, mappingLength);
192
193 const bool passed =
194 overflowRejected && zeroExtentAccepted && roundTrip && stringSnapshot &&
195 executableUserAccepted && kernelPointerRejected && nullPointersRejected && mappingCoverage &&
196 signalMaskSnapshots && alternateStackSnapshots && arrayRangeAccepted && checkedExtent == 0 &&
197 overflowRangeRejected && reducedPermissionsRejected && signalMaskReadOnlyOutputRejected &&
198 alternateStackReadOnlyOutputRejected && holeCoverage && incompleteRangeRejected &&
199 signalMaskIncompleteRangeRejected && alternateStackIncompleteRangeRejected;
200 if (!passed) {
201 ERROR(
202 "HOSTED-SYSCALL-TEST: FAIL usercopy: overflow, range, permission, or copy contract "
203 "regressed");
204 return false;
205 }
206
207 NOTICE("HOSTED-SYSCALL-TEST: PASS usercopy");
208 return true;
209}
Memory-mapped file interface.
MemoryMappedObject * mapAnon(uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms)
size_t remove(uintptr_t base, size_t length)
static MemoryMapManager & instance()
static bool copyFromUser(void *destination, const void *source, size_t count, size_t elementSize=1)
static bool checkedUserBufferSize(size_t count, size_t elementSize, size_t &extent)
static UserStringResult copyUserString(const char *userString, String &copy, size_t maxLength)
static bool checkUserBuffer(uintptr_t addr, size_t count, size_t elementSize, size_t flags, size_t *extent=nullptr)
static bool copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)
static bool checkAddress(uintptr_t addr, size_t extent, size_t flags)
static ProcessorInformation & information()
void setErrno(size_t err)
Definition Thread.h:478
uint64_t getSignalMask()
Definition Thread.cc:2002
size_t getErrno()
Definition Thread.h:473
void setSignalMask(uint64_t mask)
Definition Thread.cc:2007