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/Semaphore.h"
13#include "pedigree/kernel/process/Thread.h"
14#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
15#include "pedigree/kernel/processor/Processor.h"
16#include "pedigree/kernel/processor/VirtualAddressSpace.h"
17#include "pedigree/kernel/utilities/MemoryAllocator.h"
22#include "modules/subsys/posix/PosixProcess.h"
23#include "modules/subsys/posix/PosixSubsystem.h"
24#include "modules/subsys/posix/file-syscalls.h"
28constexpr int PreservedErrno = 123;
29constexpr uint8_t OriginalSentinel = 0x5A;
31struct PlacementContext {
33 : hintFallback(false),
34 noReplaceCollision(false),
35 failedPlacementPreserved(false),
36 fixedReplacement(false),
37 fixedSpanReserved(false),
38 exactReservation(false),
39 partialReclamation(false),
40 overlappingReclamation(false),
41 inputValidation(false),
42 noReplaceAtomic(false),
46 bool noReplaceCollision;
47 bool failedPlacementPreserved;
48 bool fixedReplacement;
49 bool fixedSpanReserved;
50 bool exactReservation;
51 bool partialReclamation;
52 bool overlappingReclamation;
59 RaceContext(uintptr_t address,
size_t length)
60 : begin(0, false), address(address), length(length) {}
68 RacerContext(RaceContext*
race, uint8_t sentinel)
69 :
race(
race), sentinel(sentinel), result(MAP_FAILED), error(0), returned(0) {}
78struct UnmapRaceContext {
79 UnmapRaceContext() : begin(0, false) {}
84struct UnmapRacerContext {
85 UnmapRacerContext(UnmapRaceContext*
race, uintptr_t address,
size_t length)
86 :
race(
race), address(address), length(length), result(-1), error(0), returned(0) {}
88 UnmapRaceContext*
race;
96bool findFreeDynamicRange(
Process* process,
size_t pageSize,
size_t length, uintptr_t& address) {
98 if (!process->snapshotUserReservations(snapshot)) {
102 const uintptr_t mask = pageSize - 1;
103 for (
size_t i = 0; i < allocator.
size(); ++i) {
104 MemoryAllocator::Range range(0, 0);
105 if (!allocator.
getRange(i, range) || range.address > ~
static_cast<uintptr_t
>(0) - mask) {
109 const uintptr_t aligned = (range.address + mask) & ~mask;
110 if (aligned >= range.address && aligned - range.address <= range.length &&
111 length <= range.length - (aligned - range.address)) {
119struct ReservationProbe {
121 Process::UserRegion region = Process::UserRegion::Normal;
123 explicit operator bool()
const {
124 return process !=
nullptr;
126 bool allocateSpecific(uintptr_t address,
size_t length)
const {
127 return process->allocateSpecificUserRange(region, address, length);
129 void free(uintptr_t address,
size_t length)
const {
130 process->freeUserRange(region, address, length);
134ReservationProbe allocatorFor(
Process* process, uintptr_t address,
size_t length) {
136 const uintptr_t end = address + length;
138 end <= addressSpace->getDynamicEnd()) {
139 return {process, Process::UserRegion::Dynamic};
141 if (address >= addressSpace->
getUserStart() && end <= addressSpace->getUserReservedStart()) {
142 return {process, Process::UserRegion::Normal};
147bool reservationHeld(ReservationProbe allocator, uintptr_t address,
size_t length) {
148 if (!allocator.allocateSpecific(address, length)) {
151 allocator.free(address, length);
155bool reservationAvailableExactlyOnce(ReservationProbe allocator, uintptr_t address,
size_t length) {
156 const bool first = allocator.allocateSpecific(address, length);
157 const bool second = first && allocator.allocateSpecific(address, length);
159 allocator.free(address, length);
161 return first && !second;
164int noReplaceRacer(
void* parameter) {
165 RacerContext* context =
reinterpret_cast<RacerContext*
>(parameter);
167 if (!context->race->begin.acquire()) {
168 context->returned += 1;
174 posix_mmap(
reinterpret_cast<void*
>(context->race->address), context->race->length,
175 PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED_NOREPLACE, -1, 0);
176 context->error = thread->
getErrno();
177 if (context->result != MAP_FAILED) {
178 *
reinterpret_cast<volatile uint8_t*
>(context->result) = context->sentinel;
180 context->returned += 1;
184bool runNoReplaceRace(
Process* process,
size_t pageSize) {
185 uintptr_t address = 0;
186 if (!findFreeDynamicRange(process, pageSize, pageSize, address)) {
190 RaceContext
race(address, pageSize);
191 RacerContext first(&
race, 0x31);
192 RacerContext second(&
race, 0x42);
193 Thread* firstThread =
new Thread(process, noReplaceRacer, &first,
nullptr,
false,
true,
true);
194 Thread* secondThread =
new Thread(process, noReplaceRacer, &second,
nullptr,
false,
true,
true);
195 firstThread->setName(
"hosted mmap no-replace racer 1");
196 secondThread->setName(
"hosted mmap no-replace racer 2");
198 const bool firstStarted = firstThread->
start();
199 const bool secondStarted = secondThread->
start();
200 race.begin.release(2);
206 if (!secondStarted) {
210 const bool firstWon = first.result ==
reinterpret_cast<void*
>(address) && !first.error;
211 const bool secondWon = second.result ==
reinterpret_cast<void*
>(address) && !second.error;
212 const bool firstLost = first.result == MAP_FAILED && first.error == Error::FileExists;
213 const bool secondLost = second.result == MAP_FAILED && second.error == Error::FileExists;
214 const uint8_t expected = firstWon ? first.sentinel : second.sentinel;
215 const bool sentinelPreserved =
216 (firstWon || secondWon) && *
reinterpret_cast<volatile uint8_t*
>(address) == expected;
217 const bool passed = firstStarted && secondStarted && firstJoined && secondJoined &&
218 first.returned == 1 && second.returned == 1 &&
219 ((firstWon && secondLost) || (secondWon && firstLost)) && sentinelPreserved;
221 if (firstWon || secondWon) {
222 posix_munmap(
reinterpret_cast<void*
>(address), pageSize);
227int unmapRacer(
void* parameter) {
228 UnmapRacerContext* context =
reinterpret_cast<UnmapRacerContext*
>(parameter);
230 if (!context->race->begin.acquire()) {
231 context->returned += 1;
236 context->result = posix_munmap(
reinterpret_cast<void*
>(context->address), context->length);
237 context->error = thread->
getErrno();
238 context->returned += 1;
239 return context->result ? 1 : 0;
242bool runOverlappingUnmapRace(
Process* process,
size_t pageSize) {
243 const size_t mappingLength = pageSize * 3;
245 posix_mmap(
nullptr, mappingLength, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
246 if (mapping == MAP_FAILED) {
250 const uintptr_t address =
reinterpret_cast<uintptr_t
>(mapping);
251 ReservationProbe allocator = allocatorFor(process, address, mappingLength);
253 posix_munmap(mapping, mappingLength);
257 UnmapRaceContext
race;
258 UnmapRacerContext first(&
race, address, pageSize * 2);
259 UnmapRacerContext second(&
race, address + pageSize, pageSize * 2);
260 Thread* firstThread =
new Thread(process, unmapRacer, &first,
nullptr,
false,
true,
true);
261 Thread* secondThread =
new Thread(process, unmapRacer, &second,
nullptr,
false,
true,
true);
262 firstThread->setName(
"hosted munmap overlap racer 1");
263 secondThread->setName(
"hosted munmap overlap racer 2");
265 const bool firstStarted = firstThread->
start();
266 const bool secondStarted = secondThread->
start();
267 race.begin.release(2);
273 if (!secondStarted) {
277 const bool releasedExactlyOnce =
278 reservationAvailableExactlyOnce(allocator, address, mappingLength);
279 const bool passed = firstStarted && secondStarted && firstJoined && secondJoined &&
280 first.returned == 1 && second.returned == 1 && !first.result &&
281 !second.result && first.error == PreservedErrno &&
282 second.error == PreservedErrno && releasedExactlyOnce;
283 if (!releasedExactlyOnce) {
284 posix_munmap(mapping, mappingLength);
289bool anonymousReservationReclamation(
Process* process,
size_t pageSize) {
290 const size_t mappingLength = pageSize * 3;
292 posix_mmap(
nullptr, mappingLength, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
293 if (mapping == MAP_FAILED) {
297 const uintptr_t address =
reinterpret_cast<uintptr_t
>(mapping);
298 ReservationProbe allocator = allocatorFor(process, address, mappingLength + pageSize);
300 posix_munmap(mapping, mappingLength);
304 const bool adjacentAvailable = allocator.allocateSpecific(address + mappingLength, pageSize);
305 if (adjacentAvailable) {
306 allocator.free(address + mappingLength, pageSize);
309 const uintptr_t middle = address + pageSize;
310 const bool middleUnmapped = !posix_munmap(
reinterpret_cast<void*
>(middle), pageSize);
311 const bool prefixHeld = reservationHeld(allocator, address, pageSize);
312 const bool suffixHeld = reservationHeld(allocator, address + (pageSize * 2), pageSize);
313 const bool middleAvailable = allocator.allocateSpecific(middle, pageSize);
314 if (middleAvailable) {
315 allocator.free(middle, pageSize);
318 void* middleMapping =
319 posix_mmap(
reinterpret_cast<void*
>(middle), pageSize, PROT_READ | PROT_WRITE,
320 MAP_PRIVATE | MAP_ANON | MAP_FIXED_NOREPLACE, -1, 0);
321 const bool middleReused = middleMapping ==
reinterpret_cast<void*
>(middle);
323 const bool remainderUnmapped = !posix_munmap(mapping, mappingLength);
324 const bool releasedExactlyOnce =
325 reservationAvailableExactlyOnce(allocator, address, mappingLength);
326 if (!releasedExactlyOnce) {
327 posix_munmap(mapping, mappingLength);
330 return adjacentAvailable && middleUnmapped && prefixHeld && suffixHeld && middleAvailable &&
331 middleReused && remainderUnmapped && releasedExactlyOnce;
334bool fixedReplacementReservesHoles(
Process* process,
size_t pageSize) {
335 const size_t replacementLength = pageSize * 3;
336 uintptr_t address = 0;
337 if (!findFreeDynamicRange(process, pageSize, replacementLength, address)) {
341 void* initial = posix_mmap(
reinterpret_cast<void*
>(address), pageSize, PROT_READ | PROT_WRITE,
342 MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
344 initial ==
reinterpret_cast<void*
>(address)
345 ? posix_mmap(
reinterpret_cast<void*
>(address), replacementLength, PROT_READ | PROT_WRITE,
346 MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0)
348 const bool holesUnavailable = replacement ==
reinterpret_cast<void*
>(address) &&
349 !process->allocateSpecificUserRange(
350 Process::UserRegion::Dynamic, address + pageSize, pageSize * 2);
352 bool releasedExactlyOnce =
false;
353 if (replacement != MAP_FAILED) {
354 if (!posix_munmap(replacement, replacementLength)) {
355 releasedExactlyOnce = reservationAvailableExactlyOnce({process, Process::UserRegion::Dynamic},
356 address, replacementLength);
358 }
else if (initial != MAP_FAILED) {
359 posix_munmap(initial, pageSize);
361 return holesUnavailable && releasedExactlyOnce;
364int exerciseMmapPlacement(
void* parameter) {
365 PlacementContext* context =
reinterpret_cast<PlacementContext*
>(parameter);
372 posix_mmap(
nullptr, pageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
373 const bool originalMapped = original != MAP_FAILED && thread->
getErrno() == PreservedErrno;
374 if (originalMapped) {
375 *
reinterpret_cast<volatile uint8_t*
>(original) = OriginalSentinel;
381 ? posix_mmap(
reinterpret_cast<void*
>(
reinterpret_cast<uintptr_t
>(original) + 17),
382 pageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0)
384 context->hintFallback = originalMapped && hinted != MAP_FAILED && hinted != original &&
385 !(
reinterpret_cast<uintptr_t
>(hinted) & (pageSize - 1)) &&
386 *
reinterpret_cast<volatile uint8_t*
>(original) == OriginalSentinel &&
387 thread->
getErrno() == PreservedErrno;
390 void* noReplace = originalMapped ? posix_mmap(original, pageSize, PROT_READ | PROT_WRITE,
391 MAP_PRIVATE | MAP_ANON | MAP_FIXED_NOREPLACE, -1, 0)
393 context->noReplaceCollision = originalMapped && noReplace == MAP_FAILED &&
394 thread->
getErrno() == Error::FileExists &&
395 *
reinterpret_cast<volatile uint8_t*
>(original) == OriginalSentinel;
396 ReservationProbe originalAllocator =
397 originalMapped ? allocatorFor(process,
reinterpret_cast<uintptr_t
>(original), pageSize)
398 : ReservationProbe{};
399 context->failedPlacementPreserved =
400 context->noReplaceCollision && originalAllocator &&
401 reservationHeld(originalAllocator,
reinterpret_cast<uintptr_t
>(original), pageSize);
404 const void* invalidFixed = posix_mmap(
nullptr, pageSize, PROT_READ | PROT_WRITE,
405 MAP_PRIVATE | MAP_ANON | MAP_FIXED_NOREPLACE, -1, 0);
406 const bool nullFixedRejected =
407 invalidFixed == MAP_FAILED && thread->
getErrno() == Error::InvalidArgument;
409 const void* overflowLength = posix_mmap(
nullptr, ~
static_cast<size_t>(0), PROT_READ | PROT_WRITE,
410 MAP_PRIVATE | MAP_ANON, -1, 0);
411 const bool overflowMmapRejected =
412 overflowLength == MAP_FAILED && thread->
getErrno() == Error::InvalidArgument;
414 const int overflowUnmap = originalMapped ? posix_munmap(original, ~
static_cast<size_t>(0)) : 0;
415 const bool overflowUnmapRejected =
416 originalMapped && overflowUnmap == -1 && thread->
getErrno() == Error::InvalidArgument &&
417 *
reinterpret_cast<volatile uint8_t*
>(original) == OriginalSentinel && originalAllocator &&
418 reservationHeld(originalAllocator,
reinterpret_cast<uintptr_t
>(original), pageSize);
419 context->inputValidation = nullFixedRejected && overflowMmapRejected && overflowUnmapRejected;
422 void* replacement = originalMapped ? posix_mmap(original, pageSize, PROT_READ | PROT_WRITE,
423 MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0)
425 context->fixedReplacement = originalMapped && replacement == original &&
426 thread->
getErrno() == PreservedErrno &&
427 !*
reinterpret_cast<volatile uint8_t*
>(replacement);
428 if (replacement != MAP_FAILED) {
429 *
reinterpret_cast<volatile uint8_t*
>(replacement) = 0x7C;
432 context->fixedSpanReserved = fixedReplacementReservesHoles(process, pageSize);
433 context->partialReclamation = anonymousReservationReclamation(process, pageSize);
434 context->overlappingReclamation = runOverlappingUnmapRace(process, pageSize);
435 context->noReplaceAtomic = runNoReplaceRace(process, pageSize);
437 if (hinted != MAP_FAILED) {
438 posix_munmap(hinted, pageSize);
440 if (replacement != MAP_FAILED) {
441 const uintptr_t replacementAddress =
reinterpret_cast<uintptr_t
>(replacement);
442 ReservationProbe replacementAllocator = allocatorFor(process, replacementAddress, pageSize);
443 const bool unmapped = !posix_munmap(replacement, pageSize);
444 context->exactReservation =
445 replacementAllocator && unmapped &&
446 reservationAvailableExactlyOnce(replacementAllocator, replacementAddress, pageSize);
447 }
else if (originalMapped) {
448 posix_munmap(original, pageSize);
451 context->returned += 1;
452 return context->hintFallback && context->noReplaceCollision &&
453 context->failedPlacementPreserved && context->fixedReplacement &&
454 context->fixedSpanReserved && context->exactReservation &&
455 context->partialReclamation && context->overlappingReclamation &&
456 context->inputValidation && context->noReplaceAtomic
462bool runHostedMmapPlacementRegressions(
Process* kernelProcess) {
465 PlacementContext context;
466 Thread*
worker =
new Thread(process, exerciseMmapPlacement, &context,
nullptr,
false,
true,
true);
467 worker->setName(
"hosted mmap placement worker");
470 const bool started =
worker->start();
471 const bool joined = started &&
worker->joinForCompletion();
476 started && joined && context.returned == 1 && context.hintFallback &&
477 context.noReplaceCollision && context.failedPlacementPreserved && context.fixedReplacement &&
478 context.fixedSpanReserved && context.exactReservation && context.partialReclamation &&
479 context.overlappingReclamation && context.inputValidation && context.noReplaceAtomic;
484 "HOSTED-SYSCALL-TEST: FAIL mmap-placement: "
486 << context.hintFallback <<
" no-replace=" << context.noReplaceCollision
487 <<
" failed-preserved=" << context.failedPlacementPreserved <<
" replacement="
488 << context.fixedReplacement <<
" fixed-span=" << context.fixedSpanReserved
489 <<
" exact=" << context.exactReservation <<
" partial=" << context.partialReclamation
490 <<
" overlap=" << context.overlappingReclamation <<
" validation="
491 << context.inputValidation <<
" no-replace-race=" << context.noReplaceAtomic);
494 NOTICE(
"HOSTED-SYSCALL-TEST: PASS mmap-placement");
static constexpr size_t getPageSize() PURE
VirtualAddressSpace * getAddressSpace()
static ProcessorInformation & information()
bool getRange(size_t index, Range &range) const
void setErrno(size_t err)
Process * getParent() const
virtual uintptr_t getDynamicStart() const
virtual uintptr_t getUserStart() const =0