Files
AnyPS5/core/libs/tests/GuestNamedShmWindowsMapping.cpp
T
JJenkxandClaude Opus 5.5 dc1d801c4c fix(libkernel): map Windows shared memory with the regular file checks
On Windows, mmap sent a shared memory descriptor straight to the shared
file mapper, skipping the checks and the private path that a regular
file descriptor takes. A private mapping of a named or anonymous object
then failed with EOPNOTSUPP, and a writable shared mapping of a
read-only descriptor failed with EIO instead of EACCES. Shared memory
objects are files on Windows, so they now take the same access checks
and the same private copy as a regular file; only the directory, device
and file-type refusals stay limited to other descriptors.

guest_named_shm_windows_mapping covers shared mappings through two
descriptors, pread and pwrite against them, an offset mapping, a private
mapping, read-only mappings and the mappings after shm_unlink.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 08:47:28 -04:00

90 lines
4.8 KiB
C++

#include "prx/libkernel/include/Api.hpp"
#include "prx/libc/include/Api.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <windows.h>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
namespace {
constexpr int ReadOnly = 0;
constexpr int ReadWrite = 2;
constexpr int Create = 0x200;
constexpr int Exclusive = 0x800;
constexpr int ProtRead = 1;
constexpr int ProtReadWrite = 3;
constexpr int MapShared = 1;
constexpr int MapPrivate = 2;
constexpr std::size_t Size = 0x8000;
void* const Failed = reinterpret_cast<void*>(static_cast<std::uintptr_t>(-1));
void Require(bool value, const char* message) {
if (value) return;
std::fprintf(stderr, "named shared-memory mapping: %s (guest errno %d)\n", message, *__error_nid_postfix());
std::abort();
}
char* Map(std::size_t length, int protection, int flags, int descriptor, std::int64_t offset) {
return static_cast<char*>(mmap_nid_postfix(nullptr, length, protection, flags, descriptor, offset));
}
}
int main() {
const std::string name = "/aps5-named-map-" + std::to_string(::GetCurrentProcessId());
shm_unlink_nid_postfix(name.c_str());
const int first = shm_open_nid_postfix(name.c_str(), Create | Exclusive | ReadWrite, 0600);
Require(first >= 0, "creating the named object failed");
Require(ftruncate_nid_postfix(first, Size) == 0, "sizing the named object failed");
auto* view = Map(Size, ProtReadWrite, MapShared, first, 0);
Require(view != Failed, "a shared read-write mapping of the named object failed");
view[0x10] = 'a';
const int second = shm_open_nid_postfix(name.c_str(), ReadWrite, 0);
Require(second >= 0 && second != first, "reopening the named object failed");
auto* other = Map(Size, ProtReadWrite, MapShared, second, 0);
Require(other != Failed && other != view, "a second shared mapping of the named object failed");
Require(other[0x10] == 'a', "a store through one mapping is missing from a mapping of another descriptor");
other[0x5000] = 'b';
Require(view[0x5000] == 'b', "a store through the second mapping is missing from the first");
char byte = 0;
Require(pread_nid_postfix(second, &byte, 1, 0x5000) == 1 && byte == 'b', "pread does not see a store made through a mapping");
const char written = 'c';
Require(pwrite_nid_disambig1_nid_postfix(first, &written, 1, 0x6000) == 1, "pwrite into the named object failed");
Require(view[0x6000] == 'c' && other[0x6000] == 'c', "a mapping does not see a pwrite into the named object");
auto* offset = Map(0x4000, ProtRead, MapShared, second, 0x4000);
Require(offset != Failed && offset[0x1000] == 'b' && offset[0x2000] == 'c', "a mapping at a nonzero offset shows the wrong bytes");
Require(munmap_nid_postfix(offset, 0x4000) == 0, "unmapping the offset mapping failed");
const int reader = shm_open_nid_postfix(name.c_str(), ReadOnly, 0);
Require(reader >= 0, "opening the named object read-only failed");
auto* readView = Map(Size, ProtRead, MapShared, reader, 0);
Require(readView != Failed && readView[0x5000] == 'b', "a read-only mapping of a read-only descriptor failed");
Require(munmap_nid_postfix(readView, Size) == 0, "unmapping the read-only mapping failed");
*__error_nid_postfix() = 0;
Require(Map(Size, ProtReadWrite, MapShared, reader, 0) == Failed && *__error_nid_postfix() == 13, "a writable shared mapping of a read-only descriptor was not refused with EACCES");
Require(close_nid_postfix(reader) == 0, "closing the read-only descriptor failed");
Require(shm_unlink_nid_postfix(name.c_str()) == 0, "unlinking the named object failed");
*__error_nid_postfix() = 0;
Require(shm_open_nid_postfix(name.c_str(), ReadWrite, 0) == -1 && *__error_nid_postfix() == 2, "the unlinked name still opens");
view[0x7000] = 'u';
Require(other[0x7000] == 'u', "mappings of an unlinked object stopped sharing stores");
Require(pread_nid_postfix(second, &byte, 1, 0x7000) == 1 && byte == 'u', "a descriptor of an unlinked object stopped seeing mapped stores");
auto* copy = Map(Size, ProtReadWrite, MapPrivate, second, 0);
Require(copy != Failed && copy[0x10] == 'a', "a private mapping of the named object failed");
copy[0x10] = 'p';
Require(view[0x10] == 'a' && pread_nid_postfix(first, &byte, 1, 0x10) == 1 && byte == 'a', "a store through a private mapping reached the named object");
Require(munmap_nid_postfix(copy, Size) == 0, "unmapping the private mapping failed");
Require(munmap_nid_postfix(view, Size) == 0 && munmap_nid_postfix(other, Size) == 0, "unmapping the shared mappings failed");
Require(close_nid_postfix(first) == 0 && close_nid_postfix(second) == 0, "closing the named object's descriptors failed");
std::puts("named shared-memory mapping tests passed");
return 0;
}