The Pedigree Project 0.1
process-vm-syscalls.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/process/Scheduler.h"
3#include "pedigree/kernel/process/TerminationDeferral.h"
4#include "pedigree/kernel/process/Thread.h"
5#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
6#include "pedigree/kernel/processor/Processor.h"
7#include "pedigree/kernel/processor/ProcessorInformation.h"
8#include "pedigree/kernel/syscallError.h"
9#include "pedigree/kernel/utilities/Pointers.h"
10
11#include "process-vm-syscalls.h"
12#if PEDIGREE_PROCESS_MEMORY_TESTS
13#include "pedigree/kernel/utilities/assert.h"
14#endif
15
16#include <limits.h>
17
18#include "PosixProcess.h"
19#include "PosixSubsystem.h"
21#include <sys/uio.h>
22
23namespace {
24constexpr size_t MaximumVectors = 1024, MaximumTransfer = 0x7ffff000;
25#if PEDIGREE_PROCESS_MEMORY_TESTS
26Thread* fragmentCaller = nullptr;
27ProcessVmAfterFragmentHook fragmentHook = nullptr;
28void* fragmentContext = nullptr;
29#endif
30
31class ProcessMemoryResult {
32 public:
33 explicit ProcessMemoryResult(Thread& thread) : m_Thread(thread) {}
34 ssize_t finish(ssize_t result) {
35 m_Error = result < 0 ? m_Thread.getErrno() : 0;
36 return result;
37 }
38 ~ProcessMemoryResult() {
39 m_Thread.setErrno(m_Error);
40 }
41
42 private:
43 Thread& m_Thread;
44 size_t m_Error = 0;
45};
46
47bool localRange(uintptr_t address, size_t length) {
48 uintptr_t limit = Processor::information().getVirtualAddressSpace().getKernelStart();
49#if X64
50 if (limit > 0x0000800000000000ULL)
51 limit = 0x0000800000000000ULL;
52#endif
53 // This is only an arithmetic access_ok check. Residency and permissions
54 // belong to the copy, after target lookup and authorization.
55 return address < limit && length <= limit - address;
56}
57
58bool importVectors(const iovec* user, size_t count, bool local, UniqueArray<iovec>& owner,
59 size_t& total) {
60 total = 0;
61 if (count > MaximumVectors) {
62 SYSCALL_ERROR(InvalidArgument);
63 return false;
64 }
65 if (!count)
66 return true;
67 owner = UniqueArray<iovec>::allocate(count);
68 if (!owner) {
69 SYSCALL_ERROR(OutOfMemory);
70 return false;
71 }
72 if (!PosixSubsystem::copyFromUser(owner.get(), user, count, sizeof(iovec))) {
73 SYSCALL_ERROR(BadAddress);
74 return false;
75 }
76 for (size_t n = 0; n < count; ++n) {
77 auto& item = owner.get()[n];
78 if (item.iov_len > static_cast<size_t>(SSIZE_MAX)) {
79 SYSCALL_ERROR(InvalidArgument);
80 return false;
81 }
82 if (local) {
83 // Linux's one-vector import applies its transfer cap before access_ok.
84 if (count == 1 && item.iov_len > MaximumTransfer)
85 item.iov_len = MaximumTransfer;
86 if (!localRange(reinterpret_cast<uintptr_t>(item.iov_base), item.iov_len)) {
87 SYSCALL_ERROR(BadAddress);
88 return false;
89 }
90 if (item.iov_len > MaximumTransfer - total)
91 item.iov_len = MaximumTransfer - total;
92 total += item.iov_len;
93 } else if (item.iov_len) {
94 // Remote lengths need not be summed: the bounded local stream decides
95 // completion, and remote payload addresses are checked only on access.
96 total = 1;
97 }
98 }
99 return true;
100}
101
102bool acquireTarget(int pid, Scheduler::ProcessLease& process) {
103 if (pid <= 0)
104 return false;
105 auto& scheduler = Scheduler::instance();
106 if (scheduler.acquireProcessByUserspaceId(process, static_cast<size_t>(pid)))
107 return true;
109 if (!scheduler.acquireThreadByUserspaceId(task, static_cast<size_t>(pid))) {
110 return false;
111 }
112 // An exec must be able to retire this Thread while the caller waits for a
113 // mapping operation. Retain only its parent beyond the lookup.
114 return scheduler.acquireProcess(process, task->getParent());
115}
116
117bool permitted(PosixProcess& caller, PosixProcess& target) {
118 if (&caller == &target)
119 return true;
120 const auto source = caller.snapshotCredentials();
121 const auto destination = target.snapshotCredentials();
122 return destination.dumpable && source.ruid == destination.ruid &&
123 source.ruid == destination.euid && source.ruid == destination.suid &&
124 source.rgid == destination.rgid && source.rgid == destination.egid &&
125 source.rgid == destination.sgid;
126}
127
128struct Cursor {
129 const iovec* vectors;
130 size_t count, index = 0, offset = 0;
131
132 bool advanceEmpty() {
133 while (index < count && offset == vectors[index].iov_len) {
134 ++index;
135 offset = 0;
136 }
137 return index < count;
138 }
139 bool position(uintptr_t& address, size_t& remaining) const {
140 const auto& vector = vectors[index];
141 const uintptr_t base = reinterpret_cast<uintptr_t>(vector.iov_base);
142 if (offset > ~uintptr_t(0) - base)
143 return false;
144 address = base + offset;
145 remaining = vector.iov_len - offset;
146 return true;
147 }
148};
149
150ssize_t transfer(int pid, const iovec* local, size_t localCount, const iovec* remote,
151 size_t remoteCount, unsigned long flags, bool write) {
152 auto* thread = Processor::information().getCurrentThread();
153 ProcessMemoryResult completion(*thread);
154 TerminationDeferral lifetime;
155 if (flags) {
156 SYSCALL_ERROR(InvalidArgument);
157 return completion.finish(-1);
158 }
159 UniqueArray<iovec> localVectors, remoteVectors;
160 size_t total = 0, nonemptyRemote = 0;
161 if (!importVectors(local, localCount, true, localVectors, total))
162 return completion.finish(-1);
163 if (!total)
164 return completion.finish(0);
165 if (!importVectors(remote, remoteCount, false, remoteVectors, nonemptyRemote))
166 return completion.finish(-1);
167 if (!nonemptyRemote)
168 return completion.finish(0);
169
170 const size_t pageSize = PhysicalMemoryManager::getPageSize();
171 auto bounce = UniqueArray<uint8_t>::allocate(pageSize);
172 if (!bounce) {
173 SYSCALL_ERROR(OutOfMemory);
174 return completion.finish(-1);
175 }
177 if (!acquireTarget(pid, process) || process->getType() != Process::Posix ||
178 !process->getSubsystem()) {
179 SYSCALL_ERROR(NoSuchProcess);
180 return completion.finish(-1);
181 }
182 Process* callerProcess = thread->getParent();
183 if (callerProcess->getType() != Process::Posix) {
184 SYSCALL_ERROR(NotEnoughPermissions);
185 return completion.finish(-1);
186 }
187 auto& caller = *static_cast<PosixProcess*>(callerProcess);
188 auto& target = *static_cast<PosixProcess*>(process.get());
189 auto& subsystem = *static_cast<PosixSubsystem*>(process->getSubsystem());
191 auto& manager = MemoryMapManager::instance();
192 {
193 MemoryMapManager::OperationGuard operation(manager);
194 if (!permitted(caller, target)) {
195 SYSCALL_ERROR(NotEnoughPermissions);
196 return completion.finish(-1);
197 }
198 if (!subsystem.snapshotUserImage(image)) {
199 SYSCALL_ERROR(NoSuchProcess);
200 return completion.finish(-1);
201 }
202 }
203
204 Cursor localCursor{localVectors.get(), localCount},
205 remoteCursor{remoteVectors.get(), remoteCount};
206 size_t copied = 0;
207 while (copied < total && localCursor.advanceEmpty() && remoteCursor.advanceEmpty()) {
208 if (thread->getUnwindState() != Thread::Continue) {
209 SYSCALL_ERROR(Interrupted);
210 break;
211 }
212 uintptr_t localAddress, remoteAddress;
213 size_t localRemaining, remoteRemaining;
214 if (!localCursor.position(localAddress, localRemaining) ||
215 !remoteCursor.position(remoteAddress, remoteRemaining)) {
216 SYSCALL_ERROR(BadAddress);
217 break;
218 }
219 size_t length = total - copied;
220 if (length > localRemaining)
221 length = localRemaining;
222 if (length > remoteRemaining)
223 length = remoteRemaining;
224 const size_t localPage = pageSize - (localAddress % pageSize);
225 const size_t remotePage = pageSize - (remoteAddress % pageSize);
226 if (length > localPage)
227 length = localPage;
228 if (length > remotePage)
229 length = remotePage;
230
231 {
232 MemoryMapManager::OperationGuard operation(manager);
233 if (!subsystem.matchesUserImage(image)) {
234 SYSCALL_ERROR(NoSuchProcess);
235 break;
236 }
237 if (!permitted(caller, target)) {
238 SYSCALL_ERROR(NotEnoughPermissions);
239 break;
240 }
241 if (write && !PosixSubsystem::copyFromUser(
242 bounce.get(), reinterpret_cast<const void*>(localAddress), length)) {
243 SYSCALL_ERROR(BadAddress);
244 break;
245 }
246 const auto status =
247 manager.copyUserPage(*image.space, remoteAddress, bounce.get(), length, write);
248 if (status != MemoryMapManager::UserPageCopyStatus::Success) {
249 // Linux reports a remote page fault as EFAULT, including failure to
250 // allocate or load that page. Scratch allocation has its own ENOMEM.
251 syscallError(status == MemoryMapManager::UserPageCopyStatus::Unsupported
252 ? Error::OperationNotSupported
253 : Error::BadAddress);
254 break;
255 }
256 if (!write && !PosixSubsystem::copyToUser(reinterpret_cast<void*>(localAddress), bounce.get(),
257 length)) {
258 SYSCALL_ERROR(BadAddress);
259 break;
260 }
261 copied += length;
262 localCursor.offset += length;
263 remoteCursor.offset += length;
264 }
265#if PEDIGREE_PROCESS_MEMORY_TESTS
266 if (__atomic_load_n(&fragmentCaller, __ATOMIC_ACQUIRE) == thread) {
267 const auto hook = __atomic_load_n(&fragmentHook, __ATOMIC_ACQUIRE);
268 if (hook)
269 hook(copied, __atomic_load_n(&fragmentContext, __ATOMIC_ACQUIRE));
270 }
271#endif
272 }
273 return completion.finish(copied ? static_cast<ssize_t>(copied) : -1);
274}
275} // namespace
276
277#if PEDIGREE_PROCESS_MEMORY_TESTS
278void setProcessVmAfterFragmentHookForTest(Thread* expectedCaller, ProcessVmAfterFragmentHook hook,
279 void* context) {
280 assert(!expectedCaller || expectedCaller == Processor::information().getCurrentThread());
281 __atomic_store_n(&fragmentCaller, static_cast<Thread*>(nullptr), __ATOMIC_RELEASE);
282 __atomic_store_n(&fragmentHook, hook, __ATOMIC_RELEASE);
283 __atomic_store_n(&fragmentContext, context, __ATOMIC_RELEASE);
284 __atomic_store_n(&fragmentCaller, expectedCaller, __ATOMIC_RELEASE);
285}
286#endif
287
288ssize_t posix_process_vm_readv(int pid, const iovec* local, size_t localCount, const iovec* remote,
289 size_t remoteCount, unsigned long flags) {
290 return transfer(pid, local, localCount, remote, remoteCount, flags, false);
291}
292
293ssize_t posix_process_vm_writev(int pid, const iovec* local, size_t localCount, const iovec* remote,
294 size_t remoteCount, unsigned long flags) {
295 return transfer(pid, local, localCount, remote, remoteCount, flags, true);
296}
Memory-mapped file interface.
static MemoryMapManager & instance()
static bool copyFromUser(void *destination, const void *source, size_t count, size_t elementSize=1)
static bool copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)
Process * getParent()
Definition Process.h:620
static ProcessorInformation & information()
static Scheduler & instance()
Definition Scheduler.h:96
@ Continue
No unwind necessary, carry on as normal.
Definition Thread.h:517
Process * getParent() const
Definition Thread.h:340