mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
Keep guest creation-mask state in libc behind C-linkage helpers so NID patching preserves the shared exports. Reject a pinned descriptor whose file identity differs from the snapshot taken before classification. Exercise close and reuse from another thread at both acquisition windows for writes, resize and seek, and create temporary ordinary-file fixtures so runtime tests work without the source tree. Constraint: NID exemptions require the literal suffix at the end of the exported symbol Confidence: high Scope-risk: narrow Tested: 18 targeted Linux runtime tests with zero skips; patched_libraries for libc/libkernel/libkernel_sys; standalone ASan/UBSan with warnings as errors; source-prefix-remapped fixtures; Clang analyzer and conventions Not-tested: Full project build and library set, Windows runtime, console hardware and titles Directive: Device/inode validation establishes file identity, not open-file-description identity
229 lines
14 KiB
C++
229 lines
14 KiB
C++
#include "SceTypes.hpp"
|
|
#include "nid/Sha1.hpp"
|
|
#include "prx/libc/include/GuestWriteWatch.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <dlfcn.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
|
|
extern "C" {
|
|
int APS5_VABI shm_open_nid_postfix(const char*, int, int);
|
|
int APS5_VABI close_nid_postfix(int);
|
|
int APS5_VABI ftruncate_nid_postfix(int, std::int64_t);
|
|
std::int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int, FileStat*);
|
|
std::int64_t APS5_VABI pread_nid_postfix(int, void*, std::size_t, std::int64_t);
|
|
std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t);
|
|
std::int64_t APS5_VABI read_nid_postfix(int, void*, std::uint64_t);
|
|
std::int64_t APS5_VABI write_nid_postfix(int, const char*, std::int64_t);
|
|
std::int64_t APS5_VABI lseek_nid_postfix(int, std::int64_t, int);
|
|
int APS5_VABI dup2_nid_postfix(int, int);
|
|
void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept;
|
|
int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept;
|
|
int APS5_VABI mprotect_nid_postfix(void*, std::size_t, int);
|
|
int APS5_VABI open_nid_postfix(const char*, int, int);
|
|
std::int64_t APS5_VABI writev_nid_postfix(int, const struct GuestIovec*, int);
|
|
std::int64_t APS5_VABI pwritev_nid_postfix(int, const struct GuestIovec*, int, std::int64_t);
|
|
int* APS5_VABI __error_nid_postfix();
|
|
}
|
|
|
|
struct GuestIovec {
|
|
void* base;
|
|
std::size_t length;
|
|
};
|
|
|
|
namespace {
|
|
constexpr int ReadOnly = 0;
|
|
constexpr int ReadWrite = 2;
|
|
constexpr int Create = 0x200;
|
|
constexpr int Exclusive = 0x800;
|
|
constexpr int Shared = 0x1;
|
|
constexpr int Private = 0x2;
|
|
constexpr std::size_t Page = 0x4000;
|
|
using Unlink = int (*)(const char*);
|
|
|
|
int CreateOrdinaryFile() {
|
|
char path[] = "/tmp/anyps5-lifecycle-XXXXXX";
|
|
const int descriptor = ::mkstemp(path);
|
|
if (descriptor < 0) throw std::runtime_error("ordinary file creation failed");
|
|
if (::unlink(path) != 0) {
|
|
::close(descriptor);
|
|
throw std::runtime_error("ordinary file unlink failed");
|
|
}
|
|
constexpr char contents[] = "ordinary file contents";
|
|
if (::write(descriptor, contents, sizeof(contents)) != static_cast<ssize_t>(sizeof(contents)) ||
|
|
::lseek(descriptor, 0, SEEK_SET) != 0) {
|
|
::close(descriptor);
|
|
throw std::runtime_error("ordinary file initialization failed");
|
|
}
|
|
return descriptor;
|
|
}
|
|
|
|
void Require(bool condition, const char* message) {
|
|
if (!condition) throw std::runtime_error(std::string(message) + " (guest errno=" + std::to_string(*__error_nid_postfix()) + ")");
|
|
}
|
|
|
|
struct Object {
|
|
std::string name = "/anyps5-lifecycle-" + std::to_string(getpid());
|
|
std::string hostName = "/anyps5-shm-";
|
|
Object() {
|
|
constexpr char hex[] = "0123456789abcdef";
|
|
const auto digest = Nid::Sha1(std::vector<std::uint8_t>(name.begin(), name.end()));
|
|
for (const auto byte : digest) {
|
|
hostName += hex[byte >> 4];
|
|
hostName += hex[byte & 15];
|
|
}
|
|
::shm_unlink(hostName.c_str());
|
|
}
|
|
~Object() { ::shm_unlink(hostName.c_str()); }
|
|
int Open(int flags) const { return shm_open_nid_postfix(name.c_str(), flags, 0600); }
|
|
};
|
|
|
|
void Run() {
|
|
Object object;
|
|
const char* anonymousName = reinterpret_cast<const char*>(1);
|
|
const int anonymous = shm_open_nid_postfix(anonymousName, 0x100e02, 0600);
|
|
Require(anonymous >= 0 && ftruncate_nid_postfix(anonymous, Page) == 0, "anonymous shared-memory setup failed");
|
|
auto* anonymousMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 3, Shared, anonymous, 0));
|
|
Require(anonymousMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "anonymous MAP_SHARED failed");
|
|
anonymousMap[0] = 'a';
|
|
char anonymousByte = 0;
|
|
Require(pread_nid_postfix(anonymous, &anonymousByte, 1, 0) == 1 && anonymousByte == 'a',
|
|
"anonymous MAP_SHARED write was not visible through the descriptor");
|
|
Require(munmap_nid_postfix(anonymousMap, Page) == 0 && close_nid_postfix(anonymous) == 0, "anonymous shared-memory cleanup failed");
|
|
const int descriptor = object.Open(ReadWrite | Create | Exclusive);
|
|
Require(descriptor >= 0, "named object creation failed");
|
|
Require(ftruncate_nid_postfix(descriptor, Page) == 0, "guest resize failed");
|
|
Require(pwrite_nid_disambig1_nid_postfix(descriptor, "old", 4, 0) == 4, "guest positioned write failed");
|
|
char bytes[8]{};
|
|
Require(pread_nid_postfix(descriptor, bytes, sizeof(bytes), Page) == 0, "read at EOF was not empty");
|
|
Require(pwrite_nid_disambig1_nid_postfix(descriptor, nullptr, 0, Page) == 0, "zero-length write failed");
|
|
*__error_nid_postfix() = 0;
|
|
Require(lseek_nid_postfix(descriptor, Page + 2, SEEK_SET) == -1 && *__error_nid_postfix() == 22,
|
|
"seek past EOF did not fail with EINVAL");
|
|
Require(lseek_nid_postfix(descriptor, 0, SEEK_END) == static_cast<std::int64_t>(Page), "seek to EOF returned the wrong position");
|
|
Require(write_nid_postfix(descriptor, "z", 1) == 0, "write at EOF grew the shared object");
|
|
Require(lseek_nid_postfix(descriptor, Page - 1, SEEK_SET) == static_cast<std::int64_t>(Page - 1), "seek near EOF failed");
|
|
Require(write_nid_postfix(descriptor, "xy", 2) == 1, "cross-EOF write did not return its short length");
|
|
Require(lseek_nid_postfix(descriptor, 0, SEEK_END) == static_cast<std::int64_t>(Page), "cross-EOF write changed object size");
|
|
char vectorBytes[] = "uv";
|
|
GuestIovec vectorWrite{vectorBytes, 2};
|
|
Require(pwritev_nid_postfix(descriptor, nullptr, 0, Page) == 0, "zero-vector positioned write on a writable descriptor failed");
|
|
Require(pwritev_nid_postfix(descriptor, &vectorWrite, 1, Page) == 0, "positioned vector write at EOF grew the object");
|
|
Require(pwritev_nid_postfix(descriptor, &vectorWrite, 1, Page - 1) == 1, "positioned vector write past EOF was not short");
|
|
Require(lseek_nid_postfix(descriptor, Page - 1, SEEK_SET) == static_cast<std::int64_t>(Page - 1), "seek before vector write failed");
|
|
Require(writev_nid_postfix(descriptor, &vectorWrite, 1) == 1, "vector write past EOF was not short");
|
|
Require(lseek_nid_postfix(descriptor, 0, SEEK_END) == static_cast<std::int64_t>(Page), "vector write changed object size");
|
|
*__error_nid_postfix() = 0;
|
|
Require(lseek_nid_postfix(descriptor, -1, SEEK_SET) == -1 && *__error_nid_postfix() == 22,
|
|
"negative seek did not return EINVAL");
|
|
Require(lseek_nid_postfix(descriptor, std::numeric_limits<std::int64_t>::max(), SEEK_CUR) == -1 &&
|
|
*__error_nid_postfix() == 84, "overflowing seek did not return EOVERFLOW");
|
|
|
|
const int second = object.Open(ReadWrite);
|
|
Require(second >= 0 && pread_nid_postfix(second, bytes, 4, 0) == 4 && std::memcmp(bytes, "old", 4) == 0,
|
|
"independent open did not see the shared object");
|
|
auto* firstMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 3, Shared, descriptor, 0));
|
|
Require(firstMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "guest shared mapping failed");
|
|
Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(reinterpret_cast<std::uintptr_t>(firstMap), Page),
|
|
"file-backed shared mapping was registered with write watch");
|
|
auto* secondMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 3, Shared, second, 0));
|
|
Require(secondMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "second guest shared mapping failed");
|
|
Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(reinterpret_cast<std::uintptr_t>(secondMap), Page),
|
|
"file-backed shared alias was registered with write watch");
|
|
*__error_nid_postfix() = 0;
|
|
Require(mmap_nid_postfix(nullptr, Page, 1, Shared, second, 1) == reinterpret_cast<void*>(static_cast<std::uintptr_t>(-1)) &&
|
|
*__error_nid_postfix() == 22, "unaligned shared mapping offset did not return EINVAL");
|
|
firstMap[0] = 'n';
|
|
Require(secondMap[0] == 'n', "shared mappings did not observe each other's writes");
|
|
auto* privateMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 3, Private, second, 0));
|
|
Require(privateMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "guest private mapping failed");
|
|
firstMap[1] = 'v';
|
|
Require(privateMap[1] == 'v', "private mapping did not reflect backing changes before its first write");
|
|
privateMap[0] = 'p';
|
|
Require(firstMap[0] == 'n', "private mapping write escaped copy-on-write isolation");
|
|
Require(pread_nid_postfix(second, bytes, 1, 0) == 1 && bytes[0] == 'n', "private mapping write changed the backing object");
|
|
const int readOnly = object.Open(ReadOnly);
|
|
Require(readOnly >= 0, "read-only open failed");
|
|
*__error_nid_postfix() = 0;
|
|
Require(pwrite_nid_disambig1_nid_postfix(readOnly, "x", 1, 0) == -1 && *__error_nid_postfix() == 9,
|
|
"positioned write on a read-only descriptor did not return EBADF");
|
|
*__error_nid_postfix() = 0;
|
|
Require(pwrite_nid_disambig1_nid_postfix(readOnly, "x", 1, Page) == -1 && *__error_nid_postfix() == 9,
|
|
"positioned write at EOF on a read-only descriptor did not return EBADF");
|
|
*__error_nid_postfix() = 0;
|
|
Require(pwritev_nid_postfix(readOnly, nullptr, 0, 0) == -1 && *__error_nid_postfix() == 9,
|
|
"zero-vector positioned write on a read-only descriptor did not return EBADF");
|
|
FileStat readOnlyInfo{};
|
|
Require(fstat_nid_disambig1_nid_postfix(readOnly, &readOnlyInfo) == 0 && readOnlyInfo.st_size == Page,
|
|
"read-only EOF write changed the shared-object size");
|
|
void* rejected = mmap_nid_postfix(nullptr, Page, 3, Shared, readOnly, 0);
|
|
Require(rejected == reinterpret_cast<void*>(static_cast<std::uintptr_t>(-1)), "writable shared mapping accepted a read-only descriptor");
|
|
auto* writableMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 1, Shared, descriptor, 0));
|
|
Require(writableMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "read-protected writable-fd mapping failed");
|
|
Require(mprotect_nid_postfix(writableMap, Page, 3) == 0, "mprotect could not restore write access from a writable descriptor");
|
|
writableMap[2] = 'm';
|
|
Require(firstMap[2] == 'm', "mprotect-upgraded mapping did not remain shared");
|
|
Require(munmap_nid_postfix(writableMap, Page) == 0, "upgraded mapping unmap failed");
|
|
auto* readOnlyMap = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 1, Shared, readOnly, 0));
|
|
Require(readOnlyMap != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "read-only shared mapping failed");
|
|
*__error_nid_postfix() = 0;
|
|
Require(mprotect_nid_postfix(readOnlyMap, Page, 3) == -1 && *__error_nid_postfix() == 13,
|
|
"mprotect on a read-only shared mapping did not return EACCES");
|
|
Require(munmap_nid_postfix(readOnlyMap, Page) == 0, "read-only mapping unmap failed");
|
|
|
|
const int ordinary = CreateOrdinaryFile();
|
|
Require(ordinary >= 0, "ordinary file setup failed");
|
|
Require(dup2_nid_postfix(ordinary, second) == second, "dup2 onto shared descriptor failed");
|
|
Require(lseek_nid_postfix(second, 0, SEEK_SET) == 0 && read_nid_postfix(second, bytes, sizeof(bytes)) > 0,
|
|
"dup2 target did not become the ordinary file");
|
|
Require(close_nid_postfix(second) == 0, "close after dup2 failed");
|
|
Require(lseek_nid_postfix(ordinary, 0, SEEK_SET) == 0 && read_nid_postfix(ordinary, bytes, sizeof(bytes)) > 0,
|
|
"closing reused descriptor damaged the ordinary file");
|
|
Require(close_nid_postfix(ordinary) == 0, "ordinary file close failed");
|
|
const int reused = object.Open(ReadWrite);
|
|
Require(reused >= 0, "shared object reopen after descriptor close failed");
|
|
Require(dup2_nid_postfix(reused, second) == second, "guest dup2 into the freed descriptor slot failed");
|
|
Require(pread_nid_postfix(second, bytes, 3, 0) == 3 && std::memcmp(bytes, "nvm", 3) == 0,
|
|
"reused descriptor slot retained ordinary-file state");
|
|
Require(close_nid_postfix(second) == 0, "reused descriptor slot close failed");
|
|
if (reused != second) Require(close_nid_postfix(reused) == 0, "reopened descriptor close failed");
|
|
|
|
Require(close_nid_postfix(descriptor) == 0 && close_nid_postfix(readOnly) == 0, "descriptor close failed");
|
|
Require(firstMap[0] == 'n', "mapping did not survive descriptor close");
|
|
const auto unlinkObject = reinterpret_cast<Unlink>(dlsym(RTLD_DEFAULT, "shm_unlink_nid_postfix"));
|
|
Require(unlinkObject != nullptr, "guest shm_unlink export is missing");
|
|
Require(unlinkObject(object.name.c_str()) == 0, "guest unlink failed");
|
|
*__error_nid_postfix() = 0;
|
|
Require(unlinkObject(object.name.c_str()) == -1 && *__error_nid_postfix() == 2, "missing-name unlink did not return ENOENT");
|
|
const int recreated = object.Open(ReadWrite | Create | Exclusive);
|
|
Require(recreated >= 0, "name recreation after unlink failed");
|
|
Require(ftruncate_nid_postfix(recreated, Page) == 0, "recreated object resize failed");
|
|
Require(pread_nid_postfix(recreated, bytes, 4, 0) == 4 && std::memcmp(bytes, "\0\0\0\0", 4) == 0,
|
|
"recreated name still refers to the unlinked object");
|
|
Require(firstMap[0] == 'n', "unlink or recreation changed the old mapping");
|
|
Require(close_nid_postfix(recreated) == 0, "recreated descriptor close failed");
|
|
Require(munmap_nid_postfix(firstMap, Page) == 0 && munmap_nid_postfix(secondMap, Page) == 0 && munmap_nid_postfix(privateMap, Page) == 0,
|
|
"guest unmap failed");
|
|
std::puts("named shared-memory lifecycle checks passed");
|
|
}
|
|
}
|
|
|
|
int main() {
|
|
try {
|
|
Run();
|
|
return 0;
|
|
} catch (const std::exception& error) {
|
|
std::fprintf(stderr, "%s\n", error.what());
|
|
return 1;
|
|
}
|
|
}
|