The Pedigree Project 0.1
system-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/utilities/lib.h"
14
15#include <limits.h>
16
17#include "modules/subsys/posix/PosixProcess.h"
18#include "modules/subsys/posix/PosixSubsystem.h"
19#include "modules/subsys/posix/system-syscalls.h"
21#include <sys/utsname.h>
22
23namespace {
24struct UserFixture {
25 utsname name;
26 uid_t uid[3];
27 gid_t gid[3];
28 gid_t groups[3];
29 unsigned long tls;
30 uint32_t capVersion;
31 int capPid;
32 uint32_t capabilities[3];
33 const char* arguments[2];
34 char executable[64];
35};
36struct Context {
37 bool passed = false;
38};
39
40int usercopyWorker(void* parameter) {
41 Context* context = reinterpret_cast<Context*>(parameter);
42 Thread* thread = Processor::information().getCurrentThread();
43 auto* process = static_cast<PosixProcess*>(thread->getParent());
44 const size_t pageSize = PhysicalMemoryManager::getPageSize();
45 const size_t length = PosixSubsystem::MaximumExecArgumentBytes + 2 * pageSize;
46 uintptr_t address = 0;
47 if (!process->allocateUserRange(Process::UserRegion::Normal, length, address)) {
48 return 1;
49 }
50 uintptr_t mappedAddress = address;
52 if (!mappings.mapAnon(mappedAddress, length,
53 MemoryMappedObject::Read | MemoryMappedObject::Write) ||
54 mappedAddress != address) {
55 return 1;
56 }
57 UserFixture initial = {};
58 initial.groups[0] = 17;
59 initial.groups[1] = 29;
60 initial.capVersion = 0x19980330;
61 StringCopy(initial.executable, "/__pedigree_missing_usercopy_fixture__");
62 auto* fixture = reinterpret_cast<UserFixture*>(address);
63 bool passed = PosixSubsystem::copyToUser(fixture, &initial, sizeof(initial));
64 process->setUserId(101);
65 process->setEffectiveUserId(102);
66 process->setSavedUserId(103);
67 process->setGroupId(201);
68 process->setEffectiveGroupId(202);
69 process->setSavedGroupId(203);
70 passed &= posix_getuid() == 101 && posix_geteuid() == 102 && posix_getgid() == 201 &&
71 posix_getegid() == 202;
72 passed &= posix_getresuid(&fixture->uid[0], &fixture->uid[1], &fixture->uid[2]) == 0 &&
73 fixture->uid[0] == 101 && fixture->uid[1] == 102 && fixture->uid[2] == 103;
74 passed &= posix_getresgid(&fixture->gid[0], &fixture->gid[1], &fixture->gid[2]) == 0 &&
75 fixture->gid[0] == 201 && fixture->gid[1] == 202 && fixture->gid[2] == 203;
76 passed &= posix_uname(&fixture->name) == 0 && fixture->name.sysname[0] == 'P' &&
77 fixture->name.nodename[0] == 'p';
78 thread->setErrno(0);
79 passed &= posix_uname(nullptr) == -1 && thread->getErrno() == Error::BadAddress;
80 passed &= posix_setgroups(2, fixture->groups) == 0 && posix_getgroups(0, nullptr) == 2;
81 thread->setErrno(0);
82 passed &=
83 posix_getgroups(1, fixture->groups) == -1 && thread->getErrno() == Error::InvalidArgument;
84 passed &= posix_getgroups(3, fixture->groups) == 2 && fixture->groups[0] == 17 &&
85 fixture->groups[1] == 29;
86 thread->setErrno(0);
87 passed &= posix_setgroups(NGROUPS_MAX + 1, fixture->groups) == -1 &&
88 thread->getErrno() == Error::InvalidArgument;
89 thread->setErrno(0);
90 passed &= posix_setgroups(1, nullptr) == -1 && thread->getErrno() == Error::BadAddress;
91 passed &= posix_setgroups(0, nullptr) == 0 && posix_getgroups(0, nullptr) == 0;
92 const unsigned long savedTls = thread->getTlsBase();
93 passed &= posix_arch_prctl(0x1003, reinterpret_cast<unsigned long>(&fixture->tls)) == 0 &&
94 fixture->tls == savedTls;
95 thread->setErrno(0);
96 passed &= posix_arch_prctl(0x1003, 0) == -1 && thread->getErrno() == Error::BadAddress;
97 thread->setErrno(0);
98 passed &= posix_arch_prctl(0x1002, process->getAddressSpace()->getKernelStart()) == -1 &&
99 thread->getErrno() == Error::NotEnoughPermissions && thread->getTlsBase() == savedTls;
100 const unsigned long replacementTls = reinterpret_cast<unsigned long>(&fixture->tls);
101 const bool changedTls = posix_arch_prctl(0x1002, replacementTls) == 0 &&
102 thread->getTlsBase() == replacementTls && fixture->tls == savedTls;
103 const bool clearedTls = posix_arch_prctl(0x1002, 0) == 0 && thread->getTlsBase() == 0;
104 const bool restoredTls = posix_arch_prctl(0x1002, savedTls) == 0;
105 passed &= changedTls && clearedTls && restoredTls;
106 passed &= posix_capget(&fixture->capVersion, fixture->capabilities) == 0 &&
107 fixture->capabilities[0] == 0xFFFFFFFF && fixture->capabilities[1] == 0xFFFFFFFF &&
108 fixture->capabilities[2] == 0xFFFFFFFF;
109 passed &= posix_capset(&fixture->capVersion, fixture->capabilities) == 0;
110 thread->setErrno(0);
111 passed &=
112 posix_capset(&fixture->capVersion, nullptr) == -1 && thread->getErrno() == Error::BadAddress;
113 fixture->capVersion = 0;
114 thread->setErrno(0);
115 passed &= posix_capget(&fixture->capVersion, nullptr) == -1 &&
116 thread->getErrno() == Error::InvalidArgument && fixture->capVersion == 0x19980330;
117
118 SyscallState state = {};
119 thread->setErrno(0);
120 passed &= posix_execve(fixture->executable, nullptr, nullptr, state) == -1 &&
121 thread->getErrno() == Error::DoesNotExist;
122 fixture->arguments[0] = fixture->executable;
123 thread->setErrno(0);
124 passed &= posix_execve(fixture->executable, fixture->arguments, nullptr, state) == -1 &&
125 thread->getErrno() == Error::DoesNotExist;
126 char* oversized = reinterpret_cast<char*>(address + pageSize);
127 const size_t argumentBytes = PosixSubsystem::MaximumExecArgumentBytes;
128 char* content = new char[argumentBytes + 1];
129 ByteSet(content, 'a', argumentBytes);
130 content[argumentBytes] = 0;
131 passed &= PosixSubsystem::copyToUser(oversized, content, argumentBytes + 1);
132 delete[] content;
133 fixture->arguments[0] = oversized;
134 thread->setErrno(0);
135 passed &= posix_execve(fixture->executable, fixture->arguments, nullptr, state) == -1 &&
136 thread->getErrno() == Error::TooBig;
137
138 mappings.remove(address, length);
139 process->freeUserRange(Process::UserRegion::Normal, address, length);
140 context->passed = passed;
141 return passed ? 0 : 1;
142}
143} // namespace
144
145bool runHostedSystemUsercopyRegressions(Process* kernelProcess) {
146 PosixProcess* process = new PosixProcess(kernelProcess);
147 process->setSubsystem(new PosixSubsystem);
148 process->publish();
149 Context context;
150 Thread* worker = new Thread(process, usercopyWorker, &context, nullptr, false, true);
151 const bool joined = worker->joinForCompletion();
152 const bool passed = joined && context.passed;
153 delete process;
154 if (passed) {
155 NOTICE("HOSTED-SYSCALL-TEST: PASS system-usercopy-contracts");
156 } else {
157 ERROR("HOSTED-SYSCALL-TEST: FAIL system-usercopy-contracts");
158 }
159 return passed;
160}
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 copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)
VirtualAddressSpace * getAddressSpace()
Definition Process.h:478
void publish()
Definition Process.cc:832
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
uintptr_t getTlsBase()
Definition Thread.cc:2665
virtual uintptr_t getKernelStart() const =0