mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-21 16:12:47 -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
178 lines
5.8 KiB
C++
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;
|
|
}
|
|
}
|