The Pedigree Project 0.1
file-contract-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/utilities/utility.h"
16
17#include <fcntl.h>
18
19#include "modules/subsys/posix/FileDescriptor.h"
20#include "modules/subsys/posix/PosixProcess.h"
21#include "modules/subsys/posix/PosixSubsystem.h"
22#include "modules/subsys/posix/UnixFilesystem.h"
23#include "modules/subsys/posix/file-syscalls.h"
24#include "modules/subsys/posix/pipe-syscalls.h"
26#include <sys/stat.h>
27#include <sys/uio.h>
28
29namespace {
30constexpr size_t ReadDescriptor = 110;
31constexpr size_t WriteDescriptor = 111;
32constexpr size_t ReadWriteDescriptor = 112;
33constexpr size_t NonBlockingWriteDescriptor = 113;
34constexpr size_t LargeFileSize = (static_cast<size_t>(1) << 33) + 513;
35constexpr uintptr_t LargeInode = (static_cast<uintptr_t>(1) << 33) + 0x5678;
36
37class ContractFile final : public File {
38 public:
39 ContractFile(Filesystem* filesystem, File* parent)
40 : File(String("contract-file"), 0, 0, 0, LargeInode, filesystem, LargeFileSize, parent),
41 readCalls(0),
42 writeCalls(0),
43 m_Bytes{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'} {}
44
45 size_t getBlockSize() const override {
46 return 4096;
47 }
48
49 bool sync() override {
50 return syncSucceeds;
51 }
52
53 size_t readCalls;
54 size_t writeCalls;
55 size_t backendError = 0;
56 uint64_t maximumWrite = ~static_cast<uint64_t>(0);
57 bool syncSucceeds = true;
58
59 protected:
60 bool isBytewise() const override {
61 return true;
62 }
63
64 uint64_t readBytewise(uint64_t offset, uint64_t size, uintptr_t buffer, bool) override {
65 ++readCalls;
66 if (offset >= sizeof(m_Bytes)) {
67 return 0;
68 }
69 if (size > sizeof(m_Bytes) - offset) {
70 size = sizeof(m_Bytes) - offset;
71 }
72 MemoryCopy(reinterpret_cast<void*>(buffer), m_Bytes + offset, size);
73 return size;
74 }
75
76 uint64_t writeBytewise(uint64_t offset, uint64_t size, uintptr_t buffer, bool) override {
77 ++writeCalls;
78 if (backendError) {
79 Processor::information().getCurrentThread()->setErrno(backendError);
80 }
81 if (size > maximumWrite) {
82 size = maximumWrite;
83 }
84 if (offset >= sizeof(m_Bytes)) {
85 return 0;
86 }
87 if (size > sizeof(m_Bytes) - offset) {
88 size = sizeof(m_Bytes) - offset;
89 }
90 MemoryCopy(m_Bytes + offset, reinterpret_cast<void*>(buffer), size);
91 return size;
92 }
93
94 private:
95 char m_Bytes[8];
96};
97
98struct ContractContext {
99 explicit ContractContext(ContractFile* target)
100 : file(target),
101 denied(false),
102 allowed(false),
103 open(false),
104 metadata(false),
105 writeErrors(false),
106 returned(0) {}
107
108 ContractFile* file;
109 bool denied;
110 bool allowed;
111 bool open;
112 bool metadata;
113 bool writeErrors;
114 Atomic<size_t> returned;
115};
116
117bool expectBadDescriptor(Thread* thread, int result) {
118 return result == -1 && thread->getErrno() == Error::BadFileDescriptor;
119}
120
121int contractWorker(void* parameter) {
122 ContractContext* context = reinterpret_cast<ContractContext*>(parameter);
123 Thread* thread = Processor::information().getCurrentThread();
124 Process* process = thread->getParent();
125 const size_t pageSize = PhysicalMemoryManager::getPageSize();
126 uintptr_t address = 0;
127 if (!process->allocateUserRange(Process::UserRegion::Normal, pageSize, address)) {
128 context->returned += 1;
129 return 1;
130 }
131 uintptr_t mappedAddress = address;
132 if (!MemoryMapManager::instance().mapAnon(mappedAddress, pageSize,
133 MemoryMappedObject::Read | MemoryMappedObject::Write) ||
134 mappedAddress != address) {
135 MemoryMapManager::instance().remove(address, pageSize);
136 process->freeUserRange(Process::UserRegion::Normal, address, pageSize);
137 context->returned += 1;
138 return 1;
139 }
140
141 char* data = reinterpret_cast<char*>(address);
142 struct iovec* vector = reinterpret_cast<struct iovec*>(address + 64);
143 struct stat* status = reinterpret_cast<struct stat*>(address + 128);
144 char* path = reinterpret_cast<char*>(address + 512);
145 int* pipes = reinterpret_cast<int*>(address + 768);
146 *data = 'w';
147 vector->iov_base = data;
148 vector->iov_len = 1;
149
150 bool denied = expectBadDescriptor(thread, posix_read(WriteDescriptor, data, 1));
151 denied &= expectBadDescriptor(thread, posix_readv(WriteDescriptor, vector, 1));
152 denied &= expectBadDescriptor(thread, posix_write(ReadDescriptor, data, 1));
153 denied &= expectBadDescriptor(thread, posix_writev(ReadDescriptor, vector, 1));
154 denied &= expectBadDescriptor(thread, posix_read(WriteDescriptor, data, 0));
155 denied &= expectBadDescriptor(thread, posix_write(ReadDescriptor, data, 0));
156 denied &= expectBadDescriptor(thread, posix_readv(WriteDescriptor, vector, 0));
157 denied &= expectBadDescriptor(thread, posix_writev(ReadDescriptor, vector, 0));
158 vector->iov_len = 0;
159 denied &= expectBadDescriptor(thread, posix_readv(WriteDescriptor, vector, 1));
160 denied &= expectBadDescriptor(thread, posix_writev(ReadDescriptor, vector, 1));
161 context->denied = denied && !context->file->readCalls && !context->file->writeCalls;
162
163 vector->iov_len = 1;
164 bool allowed = posix_read(ReadDescriptor, data, 1) == 1 && *data == 'a';
165 allowed &= posix_readv(ReadDescriptor, vector, 1) == 1 && *data == 'b';
166 *data = 'w';
167 allowed &= posix_write(WriteDescriptor, data, 1) == 1;
168 *data = 'v';
169 allowed &= posix_writev(WriteDescriptor, vector, 1) == 1;
170 allowed &= posix_read(ReadWriteDescriptor, data, 1) == 1 && *data == 'w';
171 *data = 'r';
172 allowed &= posix_write(ReadWriteDescriptor, data, 1) == 1;
173 allowed &= posix_readv(ReadWriteDescriptor, vector, 1) == 1 && *data == 'c';
174 *data = 's';
175 allowed &= posix_writev(ReadWriteDescriptor, vector, 1) == 1;
176 allowed &= posix_read(ReadDescriptor, data, 0) == 0;
177 allowed &= posix_write(WriteDescriptor, data, 0) == 0;
178 allowed &= posix_readv(ReadDescriptor, vector, 0) == 0;
179 allowed &= posix_writev(WriteDescriptor, vector, 0) == 0;
180 context->allowed = allowed;
181
182 StringCopy(path, "/contract-write-only");
183 thread->setErrno(0);
184 const int deniedOpen = posix_openat(AT_FDCWD, path, O_RDONLY | O_CLOEXEC | O_NONBLOCK, 0);
185 bool open = deniedOpen == -1 && thread->getErrno() == Error::PermissionDenied;
186 if (deniedOpen >= 0) {
187 posix_close(deniedOpen);
188 }
189 const int writeOpen = posix_openat(AT_FDCWD, path, O_WRONLY | O_CLOEXEC, 0);
190 open &= writeOpen >= 0;
191 if (writeOpen >= 0) {
192 open &= posix_close(writeOpen) == 0;
193 }
194 StringCopy(path, "/contract-file");
195 const int readOpen = posix_openat(AT_FDCWD, path, O_RDONLY | O_CLOEXEC | O_NONBLOCK, 0);
196 open &= readOpen >= 0;
197 if (readOpen >= 0) {
198 open &= posix_close(readOpen) == 0;
199 }
200 context->open = open;
201
202 bool metadata = posix_fstat(ReadDescriptor, status) == 0 && S_ISREG(status->st_mode) &&
203 status->st_size == static_cast<off_t>(LargeFileSize) &&
204 status->st_ino == LargeInode && status->st_blksize == 4096 &&
205 status->st_blocks == static_cast<blkcnt_t>(LargeFileSize / 512 + 1);
206 if (posix_pipe(pipes) == 0) {
207 metadata &= posix_fstat(pipes[0], status) == 0 && S_ISFIFO(status->st_mode);
208 metadata &= posix_close(pipes[0]) == 0;
209 metadata &= posix_close(pipes[1]) == 0;
210 } else {
211 metadata = false;
212 }
213 context->metadata = metadata;
214
215 bool writeErrors = true;
216 context->file->backendError = Error::NoSpaceLeftOnDevice;
217 context->file->maximumWrite = 0;
218 vector->iov_len = 1;
219 const size_t writeDescriptors[] = {WriteDescriptor, NonBlockingWriteDescriptor};
220 for (size_t descriptor : writeDescriptors) {
221 writeErrors &=
222 posix_write(descriptor, data, 1) == -1 && thread->getErrno() == Error::NoSpaceLeftOnDevice;
223 writeErrors &= posix_pwrite64(descriptor, data, 1, 0) == -1 &&
224 thread->getErrno() == Error::NoSpaceLeftOnDevice;
225 writeErrors &= posix_writev(descriptor, vector, 1) == -1 &&
226 thread->getErrno() == Error::NoSpaceLeftOnDevice;
227 writeErrors &= posix_pwritev(descriptor, vector, 1, 0) == -1 &&
228 thread->getErrno() == Error::NoSpaceLeftOnDevice;
229 }
230 context->file->backendError = 0;
231 context->file->maximumWrite = ~static_cast<uint64_t>(0);
232 thread->setErrno(Error::IoError);
233 writeErrors &= posix_write(WriteDescriptor, data, 1) == 1 && thread->getErrno() == 0;
234 thread->setErrno(Error::IoError);
235 writeErrors &= posix_pwrite64(WriteDescriptor, data, 1, 0) == 1 && thread->getErrno() == 0;
236 thread->setErrno(Error::IoError);
237 writeErrors &= posix_writev(WriteDescriptor, vector, 1) == 1 && thread->getErrno() == 0;
238 thread->setErrno(Error::IoError);
239 writeErrors &= posix_pwritev(WriteDescriptor, vector, 1, 0) == 1 && thread->getErrno() == 0;
240
241 context->file->backendError = Error::IoError;
242 context->file->maximumWrite = 1;
243 vector->iov_len = 2;
244 writeErrors &= posix_write(WriteDescriptor, data, 2) == 1 && thread->getErrno() == 0;
245 writeErrors &= posix_pwrite64(WriteDescriptor, data, 2, 0) == 1 && thread->getErrno() == 0;
246 writeErrors &= posix_writev(WriteDescriptor, vector, 1) == 1 && thread->getErrno() == 0;
247 writeErrors &= posix_pwritev(WriteDescriptor, vector, 1, 0) == 1 && thread->getErrno() == 0;
248 context->file->backendError = 0;
249 context->file->maximumWrite = ~static_cast<uint64_t>(0);
250 context->file->syncSucceeds = false;
251 writeErrors &= posix_fsync(ReadDescriptor) == -1 && thread->getErrno() == Error::IoError;
252 context->file->syncSucceeds = true;
253 writeErrors &= posix_fsync(ReadDescriptor) == 0;
254 writeErrors &= posix_fsync(-1) == -1 && thread->getErrno() == Error::BadFileDescriptor;
255 context->writeErrors = writeErrors;
256
257 MemoryMapManager::instance().remove(address, pageSize);
258 process->freeUserRange(Process::UserRegion::Normal, address, pageSize);
259 context->returned += 1;
260 return 0;
261}
262} // namespace
263
264bool runHostedFileContractRegressions(Process* kernelProcess) {
266 auto* priorView = VFS::instance().mountView();
267 VFS::HostedRootViewScope fixture;
268 UnixFilesystem* filesystem = new UnixFilesystem;
269 if (!fixture.open(filesystem)) {
270 delete filesystem;
271 return false;
272 }
273 File* root = filesystem->getRoot();
274 UnixDirectory* directory = static_cast<UnixDirectory*>(Directory::fromFile(root));
275 ContractFile* file = new ContractFile(filesystem, root);
276 file->setUid(200);
277 file->setPermissions(FILE_UR | FILE_UW);
278 const bool fileAdded = directory->addEntry(file->getName(), file);
279 File* writeOnly = new File(String("contract-write-only"), 0, 0, 0, 2, filesystem, 0, root);
280 writeOnly->setUid(200);
281 writeOnly->setPermissions(FILE_UW);
282 const bool writeOnlyAdded = directory->addEntry(writeOnly->getName(), writeOnly);
283
284 PosixProcess* process =
285 new PosixProcess(kernelProcess, true, Process::FilesystemContextMode::Deferred);
286 PosixSubsystem* subsystem = new PosixSubsystem;
287 process->setSubsystem(subsystem);
288 subsystem->setAbi(PosixSubsystem::LinuxAbi);
289 const bool contextInstalled = fixture.installContext(*process);
290 process->setUserId(200);
291 process->setEffectiveUserId(200);
292 subsystem->addFileDescriptor(ReadDescriptor,
293 new FileDescriptor(file, 0, ReadDescriptor, 0, O_RDONLY));
294 subsystem->addFileDescriptor(WriteDescriptor,
295 new FileDescriptor(file, 0, WriteDescriptor, 0, O_WRONLY));
296 subsystem->addFileDescriptor(ReadWriteDescriptor,
297 new FileDescriptor(file, 0, ReadWriteDescriptor, 0, O_RDWR));
298 subsystem->addFileDescriptor(
299 NonBlockingWriteDescriptor,
300 new FileDescriptor(file, 0, NonBlockingWriteDescriptor, 0, O_WRONLY | O_NONBLOCK));
301
302 ContractContext context(file);
303 Thread* worker = new Thread(process, contractWorker, &context, nullptr, false, true, true);
304 worker->setName("hosted file contracts");
305 const bool started = fileAdded && writeOnlyAdded && contextInstalled && worker->start();
306 const bool joined = started && worker->joinForCompletion();
307 if (!started) {
308 delete worker;
309 }
310 bool passed = started && joined && context.returned == 1 && context.denied && context.allowed &&
311 context.open && context.metadata && context.writeErrors;
312 subsystem->freeMultipleFds();
313 delete process;
314 const bool rootRestored = fixture.close();
315 if (!rootRestored)
316 FATAL("Hosted filesystem fixture retained owners after teardown");
317 passed = rootRestored && VFS::instance().getRootFilesystem() == priorRoot &&
318 VFS::instance().mountView() == priorView && passed;
319 delete filesystem;
320
321 if (!passed) {
322 ERROR("HOSTED-SYSCALL-TEST: FAIL file-contracts: denied="
323 << context.denied << ", allowed=" << context.allowed << ", open=" << context.open
324 << ", metadata=" << context.metadata << ", writeErrors=" << context.writeErrors);
325 return false;
326 }
327 NOTICE("HOSTED-SYSCALL-TEST: PASS file-contracts");
328 return true;
329}
Memory-mapped file interface.
static Directory * fromFile(File *pF)
Definition Directory.h:148
Definition File.h:74
virtual uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition File.cc:1233
String getName() const
Definition File.cc:670
virtual bool isBytewise() const
Definition File.cc:1229
virtual bool sync()
Definition File.cc:519
virtual uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition File.cc:1240
virtual size_t getBlockSize() const
Definition File.cc:949
size_t remove(uintptr_t base, size_t length)
static MemoryMapManager & instance()
void setAbi(Abi which)
void freeMultipleFds(bool bOnlyCloExec=false, size_t iFirst=0, size_t iLast=-1)
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
Process * getParent() const
Definition Thread.h:338
virtual File * getRoot() const
Filesystem * getRootFilesystem() const
Definition VFS.cc:631
static VFS & instance()
Definition VFS.cc:291