The Pedigree Project 0.1
hosted-vas-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/processor/PhysicalMemoryManager.h"
10#include "pedigree/kernel/processor/Processor.h"
11#include "pedigree/kernel/processor/VirtualAddressSpace.h"
12
13namespace {
14constexpr size_t PageCount = 128;
15
16bool check(bool condition, const char* detail) {
17 if (!condition) {
18 ERROR("HOSTED-WAIT-TEST: FAIL hosted-vas-mapping-index: " << detail);
19 }
20 return condition;
21}
22
23bool mappingMatches(VirtualAddressSpace& space, uintptr_t address, physical_uintptr_t expected,
24 size_t expectedFlags) {
25 if (!check(space.isMapped(reinterpret_cast<void*>(address)), "a mapping disappeared")) {
26 return false;
27 }
28 physical_uintptr_t physical = 0;
29 size_t flags = 0;
30 space.getMapping(reinterpret_cast<void*>(address + sizeof(uint32_t)), physical, flags);
31 return check(physical == expected && flags == expectedFlags,
32 "mapping lookup returned different backing or permissions");
33}
34
35uint32_t contents(size_t owner, size_t page) {
36 return 0x13570000U + static_cast<uint32_t>((owner * PageCount) + page);
37}
38
39bool checkUserPages(VirtualAddressSpace& space, uintptr_t base, physical_uintptr_t* pages,
40 size_t owner) {
41 const size_t pageSize = PhysicalMemoryManager::getPageSize();
42 bool passed = true;
43 for (size_t i = 0; i < PageCount; ++i) {
44 const uintptr_t address = base + (i * pageSize);
45 size_t flags = VirtualAddressSpace::Write;
46 if (owner == 1 && i == 0) {
48 } else if (owner == 1 && i == 1) {
50 } else if (owner == 1 && i == 2) {
51 flags = 0;
52 }
53 const bool mapped = mappingMatches(space, address, pages[i], flags);
54 passed &= mapped;
55 if (mapped && !(flags & VirtualAddressSpace::NoAccess)) {
56 auto* first = reinterpret_cast<volatile uint32_t*>(address);
57 auto* last = reinterpret_cast<volatile uint32_t*>(address + pageSize - sizeof(uint32_t));
58 passed &= check(*first == contents(owner, i) && *last == ~contents(owner, i),
59 "an address-space switch exposed another space's page contents");
60 }
61 }
62 return passed;
63}
64
65bool checkKernelPages(VirtualAddressSpace& space, uintptr_t* addresses, physical_uintptr_t* pages) {
66 bool passed = true;
67 for (size_t i = 0; i < 2; ++i) {
68 const bool mapped =
69 mappingMatches(space, addresses[i], pages[i],
71 passed &= mapped;
72 if (mapped) {
73 passed &= check(*reinterpret_cast<volatile uint32_t*>(addresses[i]) == contents(2, i),
74 "an address-space switch changed a global kernel mapping");
75 }
76 }
77 return passed;
78}
79
80bool releasePages(VirtualAddressSpace& space, uintptr_t base, physical_uintptr_t* pages,
81 bool* mapped) {
83 bool passed = true;
84 for (size_t i = 0; i < PageCount; ++i) {
85 if (mapped[i]) {
86 physical_uintptr_t physical = 0;
87 size_t flags = 0;
88 const bool detached = space.detachMapping(
89 reinterpret_cast<void*>(base + (i * PhysicalMemoryManager::getPageSize())), physical,
90 flags);
91 if (!check(detached && physical == pages[i], "cleanup could not detach the owned page")) {
92 passed = false;
93 continue;
94 }
95 mapped[i] = false;
96 }
97 if (!check(PhysicalMemoryManager::pageReferenceCountForTest(pages[i]) != 0,
98 "cleanup found a physical page that was already released")) {
99 passed = false;
100 continue;
101 }
103 }
104 return passed;
105}
106} // namespace
107
108bool runHostedVasRegressions() {
109 VirtualAddressSpace& original = Processor::information().getVirtualAddressSpace();
112 const size_t pageSize = PhysicalMemoryManager::getPageSize();
114 physical_uintptr_t pages[2][PageCount];
115 bool mapped[2][PageCount] = {};
116 physical_uintptr_t kernelPages[2] = {memory.allocatePage(), memory.allocatePage()};
117 bool kernelMapped[2] = {};
118 for (size_t owner = 0; owner < 2; ++owner) {
119 for (size_t i = 0; i < PageCount; ++i) {
120 pages[owner][i] = memory.allocatePage();
121 }
122 }
123
124 // These temporary spaces have no Process. A timer must not schedule the
125 // current Thread while its recorded address space differs from the live one.
126 const bool interrupts = Processor::getInterrupts();
128
129 uintptr_t base = original.getDynamicStart() + (16 * 1024 * 1024);
130 bool available = false;
131 while (!available) {
132 available = true;
133 for (size_t i = 0; i < PageCount + 2; ++i) {
134 if (original.isMapped(reinterpret_cast<void*>(base + (i * pageSize)))) {
135 base += (PageCount + 2) * pageSize;
136 available = false;
137 break;
138 }
139 }
140 }
141 uintptr_t kernelAddresses[2] = {base + ((PageCount + 1) * pageSize),
142 kernel.getKernelHeapEnd() + (16 * 1024 * 1024)};
143 while (kernel.isMapped(reinterpret_cast<void*>(kernelAddresses[1]))) {
144 kernelAddresses[1] += pageSize;
145 }
146
147 bool passed = true;
148 for (size_t owner = 0; owner < 2; ++owner) {
149 Processor::switchAddressSpace(*spaces[owner]);
150 for (size_t i = 0; i < PageCount; ++i) {
151 const uintptr_t address = base + (i * pageSize);
152 mapped[owner][i] = spaces[owner]->map(pages[owner][i], reinterpret_cast<void*>(address),
154 passed &= check(mapped[owner][i], "could not create the userspace fixture");
155 if (mapped[owner][i]) {
156 *reinterpret_cast<volatile uint32_t*>(address) = contents(owner, i);
157 *reinterpret_cast<volatile uint32_t*>(address + pageSize - sizeof(uint32_t)) =
158 ~contents(owner, i);
159 }
160 }
161 }
162
163 if (passed) {
164 spaces[1]->setFlags(reinterpret_cast<void*>(base + sizeof(uint32_t)),
166 passed &= check(spaces[1]->trySetFlags(reinterpret_cast<void*>(base + pageSize),
168 "trySetFlags could not protect an existing page");
169 spaces[1]->setFlags(reinterpret_cast<void*>(base + (2 * pageSize)), 0);
170 uint32_t value = 0;
171 uint32_t expected = contents(1, 0);
172 bool exchanged = false;
173 passed &= check(spaces[1]->tryReadUser32(base, value) && value == contents(1, 0) &&
174 !spaces[1]->tryWriteUser32(base, 0) &&
175 !spaces[1]->tryCompareExchangeUser32(base, expected, 0, exchanged),
176 "a write-protected page did not retain read-only access");
177 passed &= check(!spaces[1]->tryReadUser32(base + pageSize, value) &&
178 !spaces[1]->tryWriteUser32(base + pageSize, 0) &&
179 !spaces[1]->tryWriteUser32(base + (2 * pageSize), 0),
180 "NoAccess or read-only permissions allowed a user access");
181
183 for (size_t i = 0; i < 2; ++i) {
184 kernelMapped[i] =
185 spaces[0]->map(kernelPages[i], reinterpret_cast<void*>(kernelAddresses[i]),
187 passed &= check(kernelMapped[i], "could not create the kernel mapping fixture");
188 if (kernelMapped[i]) {
189 *reinterpret_cast<volatile uint32_t*>(kernelAddresses[i]) = contents(2, i);
190 }
191 }
192 }
193
194 if (passed) {
195 VirtualAddressSpace* route[] = {spaces[0], &kernel, spaces[1], &kernel, spaces[0]};
196 for (VirtualAddressSpace* space : route) {
198 passed &= checkKernelPages(*space, kernelAddresses, kernelPages);
199 if (space != &kernel) {
200 const size_t owner = space == spaces[0] ? 0 : 1;
201 passed &= checkUserPages(*space, base, pages[owner], owner);
202 }
203 }
204
206 passed &= check(
207 !spaces[0]->map(pages[0][1], reinterpret_cast<void*>(base), VirtualAddressSpace::Write) &&
208 *reinterpret_cast<volatile uint32_t*>(base) == contents(1, 0),
209 "an inactive duplicate map replaced the current space's backing");
211 passed &= check(
212 !spaces[0]->map(pages[1][0], reinterpret_cast<void*>(base), VirtualAddressSpace::Write),
213 "an inactive duplicate map was accepted without a live host mapping");
214 passed &= checkKernelPages(kernel, kernelAddresses, kernelPages);
216 passed &= checkUserPages(*spaces[0], base, pages[0], 0);
217
218 uint32_t expected = contents(0, 0) + 1;
219 bool exchanged = false;
220 passed &=
221 check(spaces[0]->tryWriteUser32(base, expected) &&
222 *reinterpret_cast<volatile uint32_t*>(base) == expected &&
223 spaces[0]->tryCompareExchangeUser32(base, expected, contents(0, 0), exchanged) &&
224 exchanged && *reinterpret_cast<volatile uint32_t*>(base) == contents(0, 0),
225 "indexed writes or compare-exchange did not reach the mapped page");
226 const uintptr_t pointerAddress = base + (2 * sizeof(uintptr_t));
227 *reinterpret_cast<volatile uintptr_t*>(pointerAddress) = base + pageSize;
228 uintptr_t pointer = 0;
229 passed &=
230 check(spaces[0]->tryReadUserPointer(pointerAddress, pointer) && pointer == base + pageSize,
231 "indexed pointer read did not reach the mapped page");
232
233 const uintptr_t absent = base + (PageCount * pageSize);
234 passed &= check(
235 !spaces[0]->isMapped(reinterpret_cast<void*>(absent)) &&
236 !spaces[0]->trySetFlags(reinterpret_cast<void*>(absent), VirtualAddressSpace::Write),
237 "an absent page was found in the mapping index");
238 size_t tablePages = 99;
239 passed &= check(!spaces[0]->tryMapUserPage(pages[1][0], reinterpret_cast<void*>(base),
240 VirtualAddressSpace::Write, &tablePages) &&
241 tablePages == 0 &&
242 mappingMatches(*spaces[0], base, pages[0][0], VirtualAddressSpace::Write),
243 "duplicate insertion changed an existing mapping");
244
245 for (size_t i = 0; i < PageCount; i += 2) {
246 void* address = reinterpret_cast<void*>(base + (i * pageSize));
247 spaces[0]->unmap(address);
248 mapped[0][i] = false;
249 passed &= check(!spaces[0]->isMapped(address), "unmap left a stale mapping index entry");
250 }
251 for (size_t remaining = PageCount; remaining; remaining -= 2) {
252 const size_t i = remaining - 2;
253 mapped[0][i] = spaces[0]->map(pages[0][i], reinterpret_cast<void*>(base + (i * pageSize)),
255 passed &= check(mapped[0][i], "a deleted mapping index entry could not be reused");
256 }
257 }
258
259 if (passed) {
260 void* address = reinterpret_cast<void*>(base + (5 * pageSize));
261 physical_uintptr_t detachedPhysical = 0;
262 size_t detachedFlags = 0;
263 passed &= check(!spaces[0]->detachMapping(address, detachedPhysical, detachedFlags,
265 mappingMatches(*spaces[0], reinterpret_cast<uintptr_t>(address),
266 pages[0][5], VirtualAddressSpace::Write),
267 "a rejected detach changed the mapping");
268 const bool detached = spaces[0]->detachMapping(address, detachedPhysical, detachedFlags,
270 if (detached) {
271 mapped[0][5] = false;
272 }
273 passed &=
274 check(detached && detachedPhysical == pages[0][5] &&
275 detachedFlags == VirtualAddressSpace::Write && !spaces[0]->isMapped(address),
276 "detach did not return and remove the original mapping");
277 if (detached) {
278 mapped[0][5] = spaces[0]->map(pages[0][5], address, VirtualAddressSpace::Write);
279 passed &= check(mapped[0][5], "a detached mapping could not be reinserted");
280 }
281
282 address = reinterpret_cast<void*>(base + ((PageCount - 1) * pageSize));
283 spaces[0]->setFlags(address, VirtualAddressSpace::NoAccess);
284 passed &= check(!spaces[0]->tryDetachUserPage(address, pages[1][PageCount - 1]),
285 "tryDetachUserPage accepted the wrong physical page");
286 const bool userDetached = spaces[0]->tryDetachUserPage(address, pages[0][PageCount - 1]);
287 if (userDetached) {
288 mapped[0][PageCount - 1] = false;
289 }
290 passed &= check(userDetached && !spaces[0]->isMapped(address),
291 "tryDetachUserPage left an inaccessible mapping indexed");
292 if (userDetached) {
293 mapped[0][PageCount - 1] =
294 spaces[0]->tryMapUserPage(pages[0][PageCount - 1], address, VirtualAddressSpace::Write);
295 passed &= check(mapped[0][PageCount - 1], "tryMapUserPage could not reuse a detached entry");
296 }
297 }
298
299 VirtualAddressSpace* clone = nullptr;
300 if (passed) {
301 passed &= checkUserPages(*spaces[0], base, pages[0], 0);
302 clone = spaces[0]->clone(false);
303 passed &= check(clone != nullptr, "could not clone the populated mapping index");
304 if (clone) {
306 passed &= checkUserPages(*clone, base, pages[0], 0);
307 passed &= checkKernelPages(*clone, kernelAddresses, kernelPages);
308 for (size_t i = 0; i < PageCount; ++i) {
309 passed &= check(PhysicalMemoryManager::pageReferenceCountForTest(pages[0][i]) == 2,
310 "clone did not retain exactly two owners for a private page");
311 }
312 if (passed) {
313 *reinterpret_cast<volatile uint32_t*>(base) = 0x87654321U;
316 passed &= check(*reinterpret_cast<volatile uint32_t*>(base) == 0x87654321U,
317 "clone(false) did not preserve writable aliasing");
318 *reinterpret_cast<volatile uint32_t*>(base) = contents(0, 0);
319 }
320 bool cloneMapped[PageCount];
321 for (size_t i = 0; i < PageCount; ++i) {
322 cloneMapped[i] = true;
323 }
324 passed &= releasePages(*clone, base, pages[0], cloneMapped);
325 for (size_t i = 0; i < PageCount; ++i) {
326 passed &= check(PhysicalMemoryManager::pageReferenceCountForTest(pages[0][i]) == 1,
327 "clone cleanup changed the source's physical page ownership");
328 }
329 }
330 }
331
332 if (passed) {
334 delete clone;
335 clone = nullptr;
336 // A retired alias leaves an explicit reference in the hosted PMM. Use
337 // freshly allocated pages so this fixture tests one private clone lifetime.
338 passed &= releasePages(*spaces[0], base, pages[0], mapped[0]);
339 if (passed) {
340 for (size_t i = 0; i < PageCount; ++i) {
341 pages[0][i] = memory.allocatePage();
342 }
343 for (size_t i = 0; i < PageCount; ++i) {
344 const uintptr_t address = base + (i * pageSize);
345 mapped[0][i] = spaces[0]->map(pages[0][i], reinterpret_cast<void*>(address),
347 passed &= check(mapped[0][i], "could not recreate fresh pages for copy-on-write");
348 if (mapped[0][i]) {
349 *reinterpret_cast<volatile uint32_t*>(address) = contents(0, i);
350 *reinterpret_cast<volatile uint32_t*>(address + pageSize - sizeof(uint32_t)) =
351 ~contents(0, i);
352 }
353 }
354 }
355 }
356
357 if (passed) {
358 spaces[0]->setFlags(reinterpret_cast<void*>(base + pageSize), 0);
359 clone = spaces[0]->clone(true);
360 passed &= check(clone != nullptr, "could not clone the mapping index with copy-on-write");
361 if (clone) {
363 const bool writableCow =
364 mappingMatches(*clone, base, pages[0][0], VirtualAddressSpace::CopyOnWrite);
365 passed &= writableCow;
366 passed &= mappingMatches(*spaces[0], base, pages[0][0], VirtualAddressSpace::CopyOnWrite);
367 passed &=
368 mappingMatches(*clone, base + pageSize, pages[0][1],
370 passed &=
371 check(!clone->tryWriteUser32(base, 0) &&
372 !clone->handleCopyOnWriteFault(reinterpret_cast<void*>(base + pageSize), false),
373 "copy-on-write bypassed a pending split or read-only protection");
374
375 physical_uintptr_t replacement = 0;
376 size_t replacementFlags = 0;
377 const bool resolved =
378 writableCow && clone->handleCopyOnWriteFault(reinterpret_cast<void*>(base), false);
379 const bool replacementMapped = resolved && clone->isMapped(reinterpret_cast<void*>(base));
380 passed &= check(replacementMapped,
381 "indexed copy-on-write lookup could not resolve a writable page");
382 if (replacementMapped) {
383 clone->getMapping(reinterpret_cast<void*>(base), replacement, replacementFlags);
384 const bool privatePage =
385 replacement != pages[0][0] && replacementFlags == VirtualAddressSpace::Write;
386 passed &= check(privatePage, "copy-on-write did not publish an independent writable page");
387 if (privatePage) {
388 auto* bytes = reinterpret_cast<volatile uint32_t*>(base);
389 passed &=
390 check(*bytes == contents(0, 0), "copy-on-write lost the original page contents");
391 *bytes = 0xABCDEF01U;
393 if (mappingMatches(*spaces[0], base, pages[0][0], VirtualAddressSpace::CopyOnWrite)) {
394 passed &= check(*bytes == contents(0, 0), "the child write changed the source page");
395 } else {
396 passed = false;
397 }
399 if (mappingMatches(*clone, base, replacement, VirtualAddressSpace::Write)) {
400 passed &=
401 check(*bytes == 0xABCDEF01U, "the source switch lost the child's private page");
402 } else {
403 passed = false;
404 }
405 }
406 }
407
409 uint32_t value = 0;
410 passed &= check(!clone->tryReadUser32(base, value), "revert left an accessible index entry");
411 for (size_t i = 0; i < PageCount; ++i) {
412 passed &= check(!clone->isMapped(reinterpret_cast<void*>(base + (i * pageSize))) &&
413 PhysicalMemoryManager::pageReferenceCountForTest(pages[0][i]) == 1,
414 "revert retained a userspace mapping or changed source ownership");
415 }
416 if (replacement && replacement != pages[0][0]) {
417 passed &= check(PhysicalMemoryManager::pageReferenceCountForTest(replacement) == 0,
418 "revert leaked the private copy-on-write page");
419 }
420 passed &= checkKernelPages(*clone, kernelAddresses, kernelPages);
421
422 const physical_uintptr_t reused = memory.allocatePage();
423 const bool remapped =
424 clone->map(reused, reinterpret_cast<void*>(base), VirtualAddressSpace::Write);
425 passed &= check(remapped, "revert left an index entry that blocked remapping");
426 if (remapped) {
427 passed &= mappingMatches(*clone, base, reused, VirtualAddressSpace::Write);
428 passed &= check(clone->tryWriteUser32(base, contents(3, 0)) &&
429 clone->tryReadUser32(base, value) && value == contents(3, 0),
430 "a remapped page retained stale copy-on-write permissions or backing");
432 } else {
433 memory.freePage(reused);
434 }
435 passed &= check(PhysicalMemoryManager::pageReferenceCountForTest(reused) == 0,
436 "the remapped fixture leaked its physical page");
437 }
438 }
439
440 for (size_t owner = 0; owner < 2; ++owner) {
441 passed &= releasePages(*spaces[owner], base, pages[owner], mapped[owner]);
442 for (size_t i = 0; i < PageCount; ++i) {
443 passed &= check(PhysicalMemoryManager::pageReferenceCountForTest(pages[owner][i]) == 0,
444 "userspace fixture leaked a physical page");
445 }
446 }
448 for (size_t i = 0; i < 2; ++i) {
449 if (kernelMapped[i]) {
450 kernel.unmap(reinterpret_cast<void*>(kernelAddresses[i]));
451 }
452 memory.freePage(kernelPages[i]);
453 }
455 delete clone;
456 delete spaces[0];
457 delete spaces[1];
458 Processor::setInterrupts(interrupts);
459
460 if (passed) {
461 NOTICE("HOSTED-WAIT-TEST: PASS hosted-vas-mapping-index");
462 }
463 return passed;
464}
virtual physical_uintptr_t allocatePage(size_t pageConstraints=0)=0
static PhysicalMemoryManager & instance()
virtual void freePage(physical_uintptr_t page)=0
static bool getInterrupts()
static ProcessorInformation & information()
static void switchAddressSpace(VirtualAddressSpace &AddressSpace)
static void setInterrupts(bool bEnable)
virtual void setFlags(void *virtualAddress, size_t newFlags)=0
static VirtualAddressSpace * create()
virtual bool map(physical_uintptr_t physicalAddress, void *virtualAddress, size_t flags)=0
virtual bool tryWriteUser32(uintptr_t address, uint32_t value)
virtual bool isMapped(void *virtualAddress)=0
virtual void revertToKernelAddressSpace()=0
virtual bool handleCopyOnWriteFault(void *virtualAddress, bool userMode)=0
virtual bool getMapping(void *virtualAddress, physical_uintptr_t &physicalAddress, size_t &flags)=0
static EXPORTED_PUBLIC VirtualAddressSpace & getKernelAddressSpace()
virtual bool detachMapping(void *virtualAddress, physical_uintptr_t &physical, size_t &flags, size_t requiredFlags=0)
virtual uintptr_t getKernelHeapEnd() const =0
virtual void unmap(void *virtualAddress)=0
virtual VirtualAddressSpace * clone(bool copyOnWrite=true)=0
virtual bool tryReadUser32(uintptr_t address, uint32_t &value)