Files
AnyPS5/core/libs/tests/GuestNamedShmDescriptorReuse.cpp
T
bilel f6ef5a77e2 fix(libkernel): preserve patched loading and shared-memory object identity
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
2026-10-11 23:40:46 +02:00

178 lines
5.8 KiB
C++

#include "SceTypes.hpp"
#include "nid/Sha1.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cerrno>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <stdexcept>
#include <string>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <vector>
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 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);
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 __error_nid_postfix();
}
namespace {
constexpr int ReadWrite = 2;
constexpr int Create = 0x200;
constexpr int Exclusive = 0x800;
constexpr int Shared = 0x1;
constexpr std::size_t Page = 0x4000;
int armedDescriptor = -1;
int replacementDescriptor = -1;
bool armed = false;
bool swapped = false;
int CreateOrdinaryFile() {
char path[] = "/tmp/anyps5-descriptor-reuse-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-descriptor-reuse-" + 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); }
};
}
extern "C" int fcntl(int descriptor, int command, ...) {
long argument = 0;
switch (command) {
case F_GETFD:
case F_GETFL:
case F_GETOWN:
#ifdef F_GETSIG
case F_GETSIG:
#endif
#ifdef F_GETLEASE
case F_GETLEASE:
#endif
#ifdef F_GETPIPE_SZ
case F_GETPIPE_SZ:
#endif
#ifdef F_GET_SEALS
case F_GET_SEALS:
#endif
break;
#ifdef F_GETLK
case F_GETLK:
case F_SETLK:
case F_SETLKW: {
va_list args;
va_start(args, command);
argument = reinterpret_cast<long>(va_arg(args, void*));
va_end(args);
break;
}
#endif
case F_DUPFD:
case F_DUPFD_CLOEXEC:
case F_SETFD:
case F_SETFL:
case F_SETOWN:
#ifdef F_SETSIG
case F_SETSIG:
#endif
#ifdef F_SETLEASE
case F_SETLEASE:
#endif
#ifdef F_NOTIFY
case F_NOTIFY:
#endif
#ifdef F_SETPIPE_SZ
case F_SETPIPE_SZ:
#endif
#ifdef F_ADD_SEALS
case F_ADD_SEALS:
#endif
{
va_list args;
va_start(args, command);
argument = va_arg(args, int);
va_end(args);
break;
}
default:
errno = EINVAL;
return -1;
}
const long result = ::syscall(SYS_fcntl, descriptor, command, argument);
if (command == F_GETFL && armed && !swapped) {
swapped = true;
if (::syscall(SYS_dup2, replacementDescriptor, armedDescriptor) != armedDescriptor) return -1;
}
return static_cast<int>(result);
}
int main() {
try {
Object object;
const int descriptor = object.Open(ReadWrite | Create | Exclusive);
Require(descriptor >= 0 && ftruncate_nid_postfix(descriptor, Page) == 0, "shared-memory setup failed");
Require(pwrite_nid_disambig1_nid_postfix(descriptor, "original", 8, 0) == 8, "shared-memory seed failed");
const int verifier = object.Open(ReadWrite);
Require(verifier >= 0, "shared-memory verifier open failed");
const int replacement = CreateOrdinaryFile();
Require(replacement >= 0, "ordinary-file setup failed");
armedDescriptor = descriptor;
replacementDescriptor = replacement;
armed = true;
auto* mapped = static_cast<char*>(mmap_nid_postfix(nullptr, Page, 3, Shared, descriptor, 0));
armed = false;
Require(swapped, "native F_GETFL interposer did not replace the guest descriptor");
Require(mapped != reinterpret_cast<char*>(static_cast<std::uintptr_t>(-1)), "guest mmap failed after descriptor replacement");
Require(std::memcmp(mapped, "original", 8) == 0, "mapping followed the replacement descriptor instead of the pinned shared-memory object");
mapped[0] = 0x70;
char byte = 0;
Require(pread_nid_postfix(verifier, &byte, 1, 0) == 1 && byte == 0x70, "mapping did not update the original shared-memory object");
Require(munmap_nid_postfix(mapped, Page) == 0, "guest mapping cleanup failed");
Require(close_nid_postfix(descriptor) == 0 && close_nid_postfix(verifier) == 0, "guest descriptor cleanup failed");
Require(::close(replacement) == 0, "replacement descriptor cleanup failed");
std::puts("named shared-memory descriptor reuse checks passed");
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
}
}