The Pedigree Project 0.1
positional-io-regressions.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/errors.h"
11#include "pedigree/kernel/process/Process.h"
12#include "pedigree/kernel/process/Thread.h"
13#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
14#include "pedigree/kernel/processor/Processor.h"
15#include "pedigree/kernel/processor/VirtualAddressSpace.h"
16#include "pedigree/kernel/utilities/utility.h"
17
18#include <fcntl.h>
19#include <limits.h>
20#include <stdint.h>
21
22#include "modules/subsys/posix/FileDescriptor.h"
23#include "modules/subsys/posix/PosixSubsystem.h"
24#include "modules/subsys/posix/file-syscalls.h"
25#include "modules/system/vfs/File.h"
27#include "modules/system/vfs/Pipe.h"
28
29namespace {
30constexpr size_t BounceCapacity = PIPE_BUF_MAX + 1;
31constexpr size_t ChunkedLength = BounceCapacity * 2 + 17;
32constexpr size_t FaultLength = BounceCapacity * 2;
33constexpr int PreservedErrno = 147;
34
35enum class FaultMode { None, WriteAfterFirst, ReadBeforeSecondCopy };
36
37bool closeDescriptor(PosixSubsystem* subsystem, size_t fd) {
38 DescriptorLease descriptor;
39 return subsystem->acquireFileDescriptor(fd, descriptor) &&
40 subsystem->closeFileDescriptor(fd, descriptor);
41}
42
43bool allocateUserMapping(Process* process, size_t length, uintptr_t& address) {
44 address = 0;
45 if (!process->allocateUserRange(Process::UserRegion::Normal, length, address)) {
46 return false;
47 }
48
49 uintptr_t mappedAddress = address;
51 mappedAddress, length, MemoryMappedObject::Read | MemoryMappedObject::Write);
52 if (!mapping || mappedAddress != address) {
53 MemoryMapManager::instance().remove(address, length);
54 process->freeUserRange(Process::UserRegion::Normal, address, length);
55 address = 0;
56 return false;
57 }
58 return true;
59}
60
61class PositionalProbeFile final : public File {
62 public:
63 explicit PositionalProbeFile(FaultMode faultMode)
64 : File(String("positional-io-probe"), 0, 0, 0, 1, nullptr, 65536, nullptr),
65 m_FaultMode(faultMode),
66 m_UserBase(0),
67 m_MappingLength(0),
68 m_PageSize(0),
69 m_ReadCalls(0),
70 m_WriteCalls(0),
71 m_SawRawPointer(false),
72 m_ReadOffsets{0, 0, 0, 0},
73 m_WriteOffsets{0, 0, 0, 0},
74 m_ReadSizes{0, 0, 0, 0},
75 m_WriteSizes{0, 0, 0, 0},
76 m_FirstWriteValues{0, 0, 0, 0} {}
77
78 void configure(uintptr_t userBase, size_t mappingLength, size_t pageSize) {
79 m_UserBase = userBase;
80 m_MappingLength = mappingLength;
81 m_PageSize = pageSize;
82 }
83
84 size_t readCalls() const {
85 return m_ReadCalls;
86 }
87
88 size_t writeCalls() const {
89 return m_WriteCalls;
90 }
91
92 uint64_t readOffset(size_t index) const {
93 return m_ReadOffsets[index];
94 }
95
96 uint64_t writeOffset(size_t index) const {
97 return m_WriteOffsets[index];
98 }
99
100 size_t readSize(size_t index) const {
101 return m_ReadSizes[index];
102 }
103
104 size_t writeSize(size_t index) const {
105 return m_WriteSizes[index];
106 }
107
108 char firstWriteValue(size_t index) const {
109 return m_FirstWriteValues[index];
110 }
111
112 bool sawRawPointer() const {
113 return m_SawRawPointer;
114 }
115
116 protected:
117 bool isBytewise() const override {
118 return true;
119 }
120
121 uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool) override {
122 const size_t slot = m_ReadCalls;
123 m_ReadCalls += 1;
124 recordPointer(buffer);
125 if (slot < 4) {
126 m_ReadOffsets[slot] = location;
127 m_ReadSizes[slot] = size;
128 }
129 ByteSet(reinterpret_cast<void*>(buffer), static_cast<char>('a' + slot), size);
130
131 if (m_FaultMode == FaultMode::ReadBeforeSecondCopy && slot == 1) {
132 MemoryMapManager::instance().setPermissions(
133 m_UserBase + m_PageSize, m_MappingLength - m_PageSize, MemoryMappedObject::Read);
134 }
135 return size;
136 }
137
138 uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool) override {
139 const size_t slot = m_WriteCalls;
140 m_WriteCalls += 1;
141 recordPointer(buffer);
142 if (slot < 4) {
143 m_WriteOffsets[slot] = location;
144 m_WriteSizes[slot] = size;
145 if (size) {
146 m_FirstWriteValues[slot] = *reinterpret_cast<const char*>(buffer);
147 }
148 }
149
150 if (m_FaultMode == FaultMode::WriteAfterFirst && slot == 0) {
151 MemoryMapManager::instance().setPermissions(
152 m_UserBase + m_PageSize, m_MappingLength - m_PageSize, MemoryMappedObject::None);
153 }
154 if (location + size > getSize()) {
155 setSize(location + size);
156 }
157 return size;
158 }
159
160 private:
161 void recordPointer(uintptr_t buffer) {
162 if (buffer >= m_UserBase && buffer < m_UserBase + m_MappingLength) {
163 m_SawRawPointer = true;
164 }
165 }
166
167 FaultMode m_FaultMode;
168 uintptr_t m_UserBase;
169 size_t m_MappingLength;
170 size_t m_PageSize;
171 Atomic<size_t> m_ReadCalls;
172 Atomic<size_t> m_WriteCalls;
173 bool m_SawRawPointer;
174 uint64_t m_ReadOffsets[4];
175 uint64_t m_WriteOffsets[4];
176 size_t m_ReadSizes[4];
177 size_t m_WriteSizes[4];
178 char m_FirstWriteValues[4];
179};
180
181struct PositionalIoContext {
182 PositionalIoContext(Process* process, PositionalProbeFile* policyWrite,
183 PositionalProbeFile* policyRead, PositionalProbeFile* faultWrite,
184 PositionalProbeFile* faultRead)
185 : process(process),
186 policyWrite(policyWrite),
187 policyRead(policyRead),
188 faultWrite(faultWrite),
189 faultRead(faultRead),
190 policyWriteResult(-2),
191 policyWriteError(0),
192 policyReadResult(-2),
193 policyReadError(0),
194 faultWriteResult(-2),
195 faultWriteError(0),
196 faultReadResult(-2),
197 faultReadError(0),
198 negativeResult(-2),
199 negativeError(0),
200 overflowResult(-2),
201 overflowError(0),
202 oversizedResult(-2),
203 oversizedError(0),
204 pipeReadResult(-2),
205 pipeReadError(0),
206 pipeWriteResult(-2),
207 pipeWriteError(0),
208 wrongReadResult(-2),
209 wrongReadError(0),
210 wrongWriteResult(-2),
211 wrongWriteError(0),
212 badAddressResult(-2),
213 badAddressError(0),
214 readValues{0, 0, 0},
215 setup(false),
216 returned(0) {}
217
218 Process* process;
219 PositionalProbeFile* policyWrite;
220 PositionalProbeFile* policyRead;
221 PositionalProbeFile* faultWrite;
222 PositionalProbeFile* faultRead;
223 ssize_t policyWriteResult;
224 int policyWriteError;
225 ssize_t policyReadResult;
226 int policyReadError;
227 ssize_t faultWriteResult;
228 int faultWriteError;
229 ssize_t faultReadResult;
230 int faultReadError;
231 ssize_t negativeResult;
232 int negativeError;
233 ssize_t overflowResult;
234 int overflowError;
235 ssize_t oversizedResult;
236 int oversizedError;
237 ssize_t pipeReadResult;
238 int pipeReadError;
239 ssize_t pipeWriteResult;
240 int pipeWriteError;
241 ssize_t wrongReadResult;
242 int wrongReadError;
243 ssize_t wrongWriteResult;
244 int wrongWriteError;
245 ssize_t badAddressResult;
246 int badAddressError;
247 char readValues[3];
248 bool setup;
249 Atomic<size_t> returned;
250};
251
252int positionalIoWorker(void* parameter) {
253 constexpr int PolicyWriteDescriptor = 90;
254 constexpr int PolicyReadDescriptor = 91;
255 constexpr int FaultWriteDescriptor = 92;
256 constexpr int FaultReadDescriptor = 93;
257 constexpr int PipeReadDescriptor = 94;
258 constexpr int PipeWriteDescriptor = 95;
259
260 PositionalIoContext* context = reinterpret_cast<PositionalIoContext*>(parameter);
261 Thread* thread = Processor::information().getCurrentThread();
262 const size_t pageSize = PhysicalMemoryManager::getPageSize();
263 const size_t mappingLength = pageSize * 4;
264 uintptr_t address = 0;
265 if (!allocateUserMapping(context->process, mappingLength, address)) {
266 context->returned += 1;
267 return 1;
268 }
269
270 context->policyWrite->configure(address, mappingLength, pageSize);
271 context->policyRead->configure(address, mappingLength, pageSize);
272 context->faultWrite->configure(address, mappingLength, pageSize);
273 context->faultRead->configure(address, mappingLength, pageSize);
274
275 char* userBuffer = reinterpret_cast<char*>(address);
276 ByteSet(userBuffer, 'w', mappingLength);
277 thread->setErrno(PreservedErrno);
278 context->policyWriteResult = posix_pwrite64(PolicyWriteDescriptor, userBuffer, ChunkedLength, 7);
279 context->policyWriteError = thread->getErrno();
280
281 ByteSet(userBuffer, 0, mappingLength);
282 thread->setErrno(PreservedErrno);
283 context->policyReadResult = posix_pread64(PolicyReadDescriptor, userBuffer, ChunkedLength, 19);
284 context->policyReadError = thread->getErrno();
285 context->readValues[0] = userBuffer[0];
286 context->readValues[1] = userBuffer[BounceCapacity];
287 context->readValues[2] = userBuffer[BounceCapacity * 2];
288
289 ByteSet(userBuffer, 'f', mappingLength);
290 thread->setErrno(PreservedErrno);
291 context->faultWriteResult = posix_pwrite64(FaultWriteDescriptor, userBuffer, FaultLength, 31);
292 context->faultWriteError = thread->getErrno();
293 MemoryMapManager::instance().setPermissions(address, mappingLength,
294 MemoryMappedObject::Read | MemoryMappedObject::Write);
295
296 ByteSet(userBuffer, 0, mappingLength);
297 thread->setErrno(PreservedErrno);
298 context->faultReadResult = posix_pread64(FaultReadDescriptor, userBuffer, FaultLength, 41);
299 context->faultReadError = thread->getErrno();
300 MemoryMapManager::instance().setPermissions(address, mappingLength,
301 MemoryMappedObject::Read | MemoryMappedObject::Write);
302
303 thread->setErrno(0);
304 context->negativeResult = posix_pread64(PolicyReadDescriptor, userBuffer, 1, -1);
305 context->negativeError = thread->getErrno();
306 thread->setErrno(0);
307 context->overflowResult = posix_pwrite64(PolicyWriteDescriptor, userBuffer, 2, INT64_MAX);
308 context->overflowError = thread->getErrno();
309 thread->setErrno(0);
310 context->oversizedResult =
311 posix_pread64(PolicyReadDescriptor, userBuffer, static_cast<size_t>(SSIZE_MAX) + 1, 0);
312 context->oversizedError = thread->getErrno();
313
314 thread->setErrno(0);
315 context->pipeReadResult = posix_pread64(PipeReadDescriptor, userBuffer, 1, 0);
316 context->pipeReadError = thread->getErrno();
317 thread->setErrno(0);
318 context->pipeWriteResult = posix_pwrite64(PipeWriteDescriptor, userBuffer, 1, 0);
319 context->pipeWriteError = thread->getErrno();
320
321 thread->setErrno(0);
322 context->wrongReadResult = posix_pread64(PolicyWriteDescriptor, userBuffer, 1, 0);
323 context->wrongReadError = thread->getErrno();
324 thread->setErrno(0);
325 context->wrongWriteResult = posix_pwrite64(PolicyReadDescriptor, userBuffer, 1, 0);
326 context->wrongWriteError = thread->getErrno();
327
328 const uintptr_t kernelStart = Processor::information().getVirtualAddressSpace().getKernelStart();
329 thread->setErrno(0);
330 context->badAddressResult =
331 posix_pwrite64(PolicyWriteDescriptor, reinterpret_cast<const char*>(kernelStart), 1, 0);
332 context->badAddressError = thread->getErrno();
333
334 MemoryMapManager::instance().remove(address, mappingLength);
335 context->process->freeUserRange(Process::UserRegion::Normal, address, mappingLength);
336 context->setup = true;
337 context->returned += 1;
338 return 0;
339}
340
341bool positionalIoSemantics(Process* kernelProcess) {
342 constexpr size_t PolicyWriteDescriptor = 90;
343 constexpr size_t PolicyReadDescriptor = 91;
344 constexpr size_t FaultWriteDescriptor = 92;
345 constexpr size_t FaultReadDescriptor = 93;
346 constexpr size_t PipeReadDescriptor = 94;
347 constexpr size_t PipeWriteDescriptor = 95;
348
349 Process* process = new Process(kernelProcess);
350 PosixSubsystem* subsystem = new PosixSubsystem;
351 process->setSubsystem(subsystem);
352
353 PositionalProbeFile policyWrite(FaultMode::None);
354 PositionalProbeFile policyRead(FaultMode::None);
355 PositionalProbeFile faultWrite(FaultMode::WriteAfterFirst);
356 PositionalProbeFile faultRead(FaultMode::ReadBeforeSecondCopy);
357 subsystem->addFileDescriptor(
358 PolicyWriteDescriptor,
359 new FileDescriptor(&policyWrite, 123, PolicyWriteDescriptor, 0, O_WRONLY | O_APPEND));
360 subsystem->addFileDescriptor(
361 PolicyReadDescriptor,
362 new FileDescriptor(&policyRead, 147, PolicyReadDescriptor, 0, O_RDONLY | O_APPEND));
363 subsystem->addFileDescriptor(
364 FaultWriteDescriptor,
365 new FileDescriptor(&faultWrite, 173, FaultWriteDescriptor, 0, O_WRONLY | O_APPEND));
366 subsystem->addFileDescriptor(
367 FaultReadDescriptor, new FileDescriptor(&faultRead, 197, FaultReadDescriptor, 0, O_RDONLY));
368
369 Pipe* pipe = new Pipe;
370 subsystem->addFileDescriptor(PipeReadDescriptor,
371 new FileDescriptor(pipe, 0, PipeReadDescriptor, 0, O_RDONLY));
372 subsystem->addFileDescriptor(PipeWriteDescriptor,
373 new FileDescriptor(pipe, 0, PipeWriteDescriptor, 0, O_WRONLY));
374
375 PositionalIoContext context(process, &policyWrite, &policyRead, &faultWrite, &faultRead);
376 Thread* worker = new Thread(process, positionalIoWorker, &context, nullptr, false, true, true);
377 worker->setName("hosted positional I/O semantics");
378 const bool started = worker->start();
379 const bool joined = started && worker->joinForCompletion();
380 if (!started) {
381 delete worker;
382 }
383
384 DescriptorLease policyWriter;
385 DescriptorLease policyReader;
386 DescriptorLease faultWriter;
387 DescriptorLease faultReader;
388 const bool acquired = subsystem->acquireFileDescriptor(PolicyWriteDescriptor, policyWriter) &&
389 subsystem->acquireFileDescriptor(PolicyReadDescriptor, policyReader) &&
390 subsystem->acquireFileDescriptor(FaultWriteDescriptor, faultWriter) &&
391 subsystem->acquireFileDescriptor(FaultReadDescriptor, faultReader);
392
393 bool passed =
394 started && joined && context.returned == 1 && context.setup && acquired &&
395 context.policyWriteResult == static_cast<ssize_t>(ChunkedLength) &&
396 context.policyWriteError == PreservedErrno &&
397 context.policyReadResult == static_cast<ssize_t>(ChunkedLength) &&
398 context.policyReadError == PreservedErrno && policyWriter->getOffset() == 123 &&
399 policyReader->getOffset() == 147 && policyWrite.writeCalls() == 3 &&
400 policyWrite.writeOffset(0) == 7 && policyWrite.writeOffset(1) == 7 + BounceCapacity &&
401 policyWrite.writeOffset(2) == 7 + BounceCapacity * 2 &&
402 policyWrite.writeSize(0) == BounceCapacity && policyWrite.writeSize(1) == BounceCapacity &&
403 policyWrite.writeSize(2) == 17 && policyWrite.firstWriteValue(0) == 'w' &&
404 policyWrite.firstWriteValue(1) == 'w' && policyWrite.firstWriteValue(2) == 'w' &&
405 !policyWrite.sawRawPointer() && policyRead.readCalls() == 3 &&
406 policyRead.readOffset(0) == 19 && policyRead.readOffset(1) == 19 + BounceCapacity &&
407 policyRead.readOffset(2) == 19 + BounceCapacity * 2 &&
408 policyRead.readSize(0) == BounceCapacity && policyRead.readSize(1) == BounceCapacity &&
409 policyRead.readSize(2) == 17 && !policyRead.sawRawPointer() && context.readValues[0] == 'a' &&
410 context.readValues[1] == 'b' && context.readValues[2] == 'c' &&
411 context.faultWriteResult == static_cast<ssize_t>(BounceCapacity) &&
412 context.faultWriteError == PreservedErrno &&
413 context.faultReadResult == static_cast<ssize_t>(BounceCapacity) &&
414 context.faultReadError == PreservedErrno && faultWriter->getOffset() == 173 &&
415 faultReader->getOffset() == 197 && faultWrite.writeCalls() == 1 &&
416 faultWrite.writeOffset(0) == 31 && !faultWrite.sawRawPointer() &&
417 faultRead.readCalls() == 2 && faultRead.readOffset(0) == 41 &&
418 faultRead.readOffset(1) == 41 + BounceCapacity && !faultRead.sawRawPointer() &&
419 context.negativeResult == -1 && context.negativeError == Error::InvalidArgument &&
420 context.overflowResult == -1 && context.overflowError == Error::InvalidArgument &&
421 context.oversizedResult == -1 && context.oversizedError == Error::InvalidArgument &&
422 context.pipeReadResult == -1 && context.pipeReadError == Error::IllegalSeek &&
423 context.pipeWriteResult == -1 && context.pipeWriteError == Error::IllegalSeek &&
424 context.wrongReadResult == -1 && context.wrongReadError == Error::BadFileDescriptor &&
425 context.wrongWriteResult == -1 && context.wrongWriteError == Error::BadFileDescriptor &&
426 context.badAddressResult == -1 && context.badAddressError == Error::BadAddress;
427
428 policyWriter.reset();
429 policyReader.reset();
430 faultWriter.reset();
431 faultReader.reset();
432 for (size_t fd = PolicyWriteDescriptor; fd <= PipeWriteDescriptor; ++fd) {
433 passed = closeDescriptor(subsystem, fd) && passed;
434 }
435 delete process;
436
437 if (!passed) {
438 ERROR(
439 "HOSTED-SYSCALL-TEST: FAIL positional-io-semantics: "
440 "offset isolation, append override, usercopy, partial progress, or validation regressed");
441 return false;
442 }
443
444 NOTICE("HOSTED-SYSCALL-TEST: PASS positional-io-semantics");
445 return true;
446}
447} // namespace
448
449bool runHostedPositionalIoRegressions(Process* process) {
450 return positionalIoSemantics(process);
451}
Memory-mapped file interface.
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 isBytewise() const
Definition File.cc:1229
virtual uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition File.cc:1240
MemoryMappedObject * mapAnon(uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms)
size_t remove(uintptr_t base, size_t length)
static MemoryMapManager & instance()
Definition Pipe.h:36
bool acquireFileDescriptor(size_t fd, DescriptorLease &descriptor)
bool closeFileDescriptor(size_t fd, const DescriptorLease &descriptor)
void addFileDescriptor(size_t fd, FileDescriptor *pFd)
static ProcessorInformation & information()
void setErrno(size_t err)
Definition Thread.h:478
size_t getErrno()
Definition Thread.h:473