The Pedigree Project 0.1
access-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 "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#include <unistd.h>
19
20#include "modules/subsys/posix/FileDescriptor.h"
21#include "modules/subsys/posix/PosixProcess.h"
22#include "modules/subsys/posix/PosixSubsystem.h"
23#include "modules/subsys/posix/UnixFilesystem.h"
24#include "modules/subsys/posix/file-syscalls.h"
25#include "modules/system/vfs/File.h"
27#include "modules/system/vfs/MountView.h"
28#include "modules/system/vfs/Symlink.h"
29#include "modules/system/vfs/VFS.h"
30
31namespace {
32constexpr size_t AccessDescriptor = 97;
33constexpr size_t DirectoryDescriptor = 98;
34constexpr int PreservedErrno = 149;
35
36class AccessSymlink final : public Symlink {
37 public:
38 AccessSymlink(const String& name, const String& target, Filesystem* filesystem, File* parent)
39 : Symlink(name, 0, 0, 0, 2, filesystem, target.length(), parent), m_Target(target) {}
40
41 protected:
42 uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool) override {
43 if (location >= m_Target.length()) {
44 return 0;
45 }
46 if (size > m_Target.length() - location) {
47 size = m_Target.length() - location;
48 }
49 MemoryCopy(reinterpret_cast<void*>(buffer), m_Target.cstr() + location, size);
50 return size;
51 }
52
53 private:
54 String m_Target;
55};
56
57struct AccessContext {
58 AccessContext()
59 : validation(false),
60 pathResolution(false),
61 credentials(false),
62 emptyPath(false),
63 symlinks(false),
64 usercopy(false),
65 returned(0) {}
66
67 bool validation;
68 bool pathResolution;
69 bool credentials;
70 bool emptyPath;
71 bool symlinks;
72 bool usercopy;
73 Atomic<size_t> returned;
74};
75
76bool allocateUserMapping(Process* process, size_t length, uintptr_t& address) {
77 address = 0;
78 if (!process->allocateUserRange(Process::UserRegion::Normal, length, address)) {
79 return false;
80 }
81
82 uintptr_t mappedAddress = address;
84 mappedAddress, length, MemoryMappedObject::Read | MemoryMappedObject::Write);
85 if (!mapping || mappedAddress != address) {
86 MemoryMapManager::instance().remove(address, length);
87 process->freeUserRange(Process::UserRegion::Normal, address, length);
88 address = 0;
89 return false;
90 }
91 return true;
92}
93
94bool expectFailure(Thread* thread, int result, size_t error) {
95 return result == -1 && thread->getErrno() == error;
96}
97
98bool expectSuccess(Thread* thread, int result) {
99 return result == 0 && thread->getErrno() == PreservedErrno;
100}
101
102int accessWorker(void* parameter) {
103 AccessContext* context = reinterpret_cast<AccessContext*>(parameter);
104 Thread* thread = Processor::information().getCurrentThread();
105 Process* process = thread->getParent();
106 const size_t pageSize = PhysicalMemoryManager::getPageSize();
107 uintptr_t address = 0;
108 if (!allocateUserMapping(process, pageSize, address)) {
109 context->returned += 1;
110 return 1;
111 }
112
113 constexpr char AbsolutePath[] = "/access-file";
114 constexpr char RelativePath[] = "access-file";
115 constexpr char MissingPath[] = "/access-missing";
116 constexpr char SymlinkPath[] = "/access-link";
117 char* absolutePath = reinterpret_cast<char*>(address);
118 char* relativePath = absolutePath + sizeof(AbsolutePath);
119 char* missingPath = relativePath + sizeof(RelativePath);
120 char* symlinkPath = missingPath + sizeof(MissingPath);
121 char* emptyPath = symlinkPath + sizeof(SymlinkPath);
122 MemoryCopy(absolutePath, AbsolutePath, sizeof(AbsolutePath));
123 MemoryCopy(relativePath, RelativePath, sizeof(RelativePath));
124 MemoryCopy(missingPath, MissingPath, sizeof(MissingPath));
125 MemoryCopy(symlinkPath, SymlinkPath, sizeof(SymlinkPath));
126 *emptyPath = 0;
127
128 thread->setErrno(0);
129 const bool invalidMode =
130 expectFailure(thread, posix_faccessat(AT_FDCWD, absolutePath, 8, 0), Error::InvalidArgument);
131 thread->setErrno(0);
132 const bool invalidFlags = expectFailure(
133 thread, posix_faccessat(AT_FDCWD, absolutePath, F_OK, 0x40000000), Error::InvalidArgument);
134 context->validation = invalidMode && invalidFlags;
135
136 thread->setErrno(PreservedErrno);
137 const bool absoluteIgnoresFd = expectSuccess(thread, posix_faccessat(-1, absolutePath, F_OK, 0));
138 thread->setErrno(0);
139 const bool relativeRejectsFd =
140 expectFailure(thread, posix_faccessat(-1, relativePath, F_OK, 0), Error::BadFileDescriptor);
141 thread->setErrno(0);
142 const bool relativeRejectsFile = expectFailure(
143 thread, posix_faccessat(AccessDescriptor, relativePath, F_OK, 0), Error::NotADirectory);
144 thread->setErrno(PreservedErrno);
145 const bool relativeUsesDirectory =
146 expectSuccess(thread, posix_faccessat(DirectoryDescriptor, relativePath, F_OK, 0));
147 thread->setErrno(0);
148 const bool missingIsEnoent =
149 expectFailure(thread, posix_faccessat(-1, missingPath, F_OK, 0), Error::DoesNotExist);
150 context->pathResolution = absoluteIgnoresFd && relativeRejectsFd && relativeRejectsFile &&
151 relativeUsesDirectory && missingIsEnoent;
152
153 thread->setErrno(0);
154 const bool realDenied = expectFailure(thread, posix_faccessat(AT_FDCWD, absolutePath, R_OK, 0),
155 Error::PermissionDenied);
156 thread->setErrno(PreservedErrno);
157 const bool effectiveAllowed =
158 expectSuccess(thread, posix_faccessat(AT_FDCWD, absolutePath, R_OK, AT_EACCESS));
159 context->credentials = realDenied && effectiveAllowed && process->getUserId() == 100 &&
160 process->getEffectiveUserId() == 200 && process->getGroupId() == 10 &&
161 process->getEffectiveGroupId() == 20;
162
163 thread->setErrno(0);
164 const bool emptyNeedsFlag = expectFailure(
165 thread, posix_faccessat(AccessDescriptor, emptyPath, F_OK, 0), Error::DoesNotExist);
166 thread->setErrno(PreservedErrno);
167 const bool emptyDescriptorExists =
168 expectSuccess(thread, posix_faccessat(AccessDescriptor, emptyPath, F_OK, AT_EMPTY_PATH));
169 thread->setErrno(PreservedErrno);
170 const bool emptyCwdExists =
171 expectSuccess(thread, posix_faccessat(AT_FDCWD, emptyPath, F_OK, AT_EMPTY_PATH));
172 thread->setErrno(0);
173 const bool emptyRealDenied =
174 expectFailure(thread, posix_faccessat(AccessDescriptor, emptyPath, R_OK, AT_EMPTY_PATH),
175 Error::PermissionDenied);
176 thread->setErrno(PreservedErrno);
177 const bool emptyEffectiveAllowed = expectSuccess(
178 thread, posix_faccessat(AccessDescriptor, emptyPath, R_OK, AT_EMPTY_PATH | AT_EACCESS));
179 thread->setErrno(0);
180 const bool emptyRejectsFd = expectFailure(
181 thread, posix_faccessat(-1, emptyPath, F_OK, AT_EMPTY_PATH), Error::BadFileDescriptor);
182 context->emptyPath = emptyNeedsFlag && emptyDescriptorExists && emptyCwdExists &&
183 emptyRealDenied && emptyEffectiveAllowed && emptyRejectsFd;
184
185 thread->setErrno(0);
186 const bool followsByDefault = expectFailure(
187 thread, posix_faccessat(AT_FDCWD, symlinkPath, W_OK, 0), Error::PermissionDenied);
188 thread->setErrno(PreservedErrno);
189 const bool nofollowChecksLink =
190 expectSuccess(thread, posix_faccessat(AT_FDCWD, symlinkPath, W_OK, AT_SYMLINK_NOFOLLOW));
191 context->symlinks = followsByDefault && nofollowChecksLink;
192
193 thread->setErrno(0);
194 context->usercopy =
195 expectFailure(thread, posix_faccessat(AT_FDCWD, nullptr, F_OK, 0), Error::BadAddress);
196
197 MemoryMapManager::instance().remove(address, pageSize);
198 process->freeUserRange(Process::UserRegion::Normal, address, pageSize);
199 context->returned += 1;
200 return 0;
201}
202
203bool closeDescriptor(PosixSubsystem* subsystem, size_t fd) {
204 DescriptorLease descriptor;
205 return subsystem->acquireFileDescriptor(fd, descriptor) &&
206 subsystem->closeFileDescriptor(fd, descriptor);
207}
208
209bool accessSemantics(Process* kernelProcess) {
211 auto* priorView = VFS::instance().mountView();
212 VFS::HostedRootViewScope fixture;
213 UnixFilesystem* filesystem = new UnixFilesystem;
214 if (!fixture.open(filesystem)) {
215 delete filesystem;
216 return false;
217 }
218 File* root = filesystem->getRoot();
219 UnixDirectory* directory = static_cast<UnixDirectory*>(Directory::fromFile(root));
220
221 File* target = new File(String("access-file"), 0, 0, 0, 1, filesystem, 0, root);
222 target->setUid(200);
223 target->setGid(20);
224 target->setPermissions(FILE_UR);
225 const bool targetAdded = directory->addEntry(target->getName(), target);
226
227 AccessSymlink* link =
228 new AccessSymlink(String("access-link"), String("/access-file"), filesystem, root);
229 link->setUid(100);
230 link->setGid(10);
231 link->setPermissions(FILE_UW);
232 const bool linkAdded = directory->addEntry(link->getName(), link);
233
234 PosixProcess* process =
235 new PosixProcess(kernelProcess, true, Process::FilesystemContextMode::Deferred);
236 PosixSubsystem* subsystem = new PosixSubsystem;
237 process->setSubsystem(subsystem);
238 subsystem->setAbi(PosixSubsystem::LinuxAbi);
239 const bool contextInstalled = fixture.installContext(*process);
240 process->setUserId(100);
241 process->setEffectiveUserId(200);
242 process->setGroupId(10);
243 process->setEffectiveGroupId(20);
244 subsystem->addFileDescriptor(AccessDescriptor,
245 new FileDescriptor(target, 0, AccessDescriptor, 0, O_RDONLY));
246 FilesystemPathRef rootPath;
247 const bool rootResolved = fixture.view()->bootRootPath(rootPath);
248 if (rootResolved)
249 subsystem->addFileDescriptor(DirectoryDescriptor,
250 new FileDescriptor(rootPath, 0, DirectoryDescriptor, 0, O_RDONLY));
251
252 AccessContext context;
253 Thread* worker = new Thread(process, accessWorker, &context, nullptr, false, true, true);
254 worker->setName("hosted faccessat2 semantics");
255 const bool started =
256 targetAdded && linkAdded && contextInstalled && rootResolved && worker->start();
257 const bool joined = started && worker->joinForCompletion();
258 if (!started) {
259 delete worker;
260 }
261
262 bool passed = started && joined && context.returned == 1 && context.validation &&
263 context.pathResolution && context.credentials && context.emptyPath &&
264 context.symlinks && context.usercopy;
265 passed = closeDescriptor(subsystem, AccessDescriptor) && passed;
266 passed = closeDescriptor(subsystem, DirectoryDescriptor) && passed;
267 delete process;
268 rootPath.reset();
269
270 const bool rootRestored = fixture.close();
271 if (!rootRestored)
272 FATAL("Hosted filesystem fixture retained owners after teardown");
273 passed = rootRestored && VFS::instance().getRootFilesystem() == priorRoot &&
274 VFS::instance().mountView() == priorView && passed;
275 delete filesystem;
276
277 if (!passed) {
278 ERROR(
279 "HOSTED-SYSCALL-TEST: FAIL faccessat2-semantics: "
280 "validation, resolution, credential, empty-path, symlink, or usercopy behavior regressed");
281 return false;
282 }
283
284 NOTICE("HOSTED-SYSCALL-TEST: PASS faccessat2-semantics");
285 return true;
286}
287} // namespace
288
289bool runHostedAccessSyscallRegressions(Process* process) {
290 return accessSemantics(process);
291}
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
MemoryMappedObject * mapAnon(uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms)
size_t remove(uintptr_t base, size_t length)
static MemoryMapManager & instance()
bool acquireFileDescriptor(size_t fd, DescriptorLease &descriptor)
void setAbi(Abi which)
bool closeFileDescriptor(size_t fd, const DescriptorLease &descriptor)
void addFileDescriptor(size_t fd, FileDescriptor *pFd)
virtual int64_t getUserId() const
Definition Process.cc:2051
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