mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
Pin the descriptor before mmap validation so guest descriptor reuse cannot redirect the eventual mapping. Apply the canonical guest creation mask when creating named objects, distinguishing new objects before setting permissions without changing the host process umask. Add regressions that reproduce both failures and document the remaining host requirements. Constraint: Guest umask must not mutate the process-wide host mask Confidence: high Scope-risk: narrow Tested: 17 targeted Linux runtime tests passed with zero skips; conventions and diff checks; Clang analyzer on three changed production translation units Not-tested: Full project build and suite, Windows runtime, console hardware and titles
127 lines
6.5 KiB
C++
127 lines
6.5 KiB
C++
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include "nid/Sha1.hpp"
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <fcntl.h>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
extern "C" int APS5_VABI shm_open_nid_postfix(const char*, int, int);
|
|
extern "C" int* APS5_VABI __error_nid_postfix();
|
|
|
|
static void Require(bool value, const char* message) {
|
|
if (!value) {
|
|
throw std::runtime_error(std::string(message) + " (guest errno=" + std::to_string(*__error_nid_postfix()) + ")");
|
|
}
|
|
}
|
|
|
|
struct Object {
|
|
std::string name;
|
|
std::string hostName = "/anyps5-shm-";
|
|
explicit Object(std::string suffix) : name("/anyps5-test-" + std::to_string(getpid()) + suffix) {
|
|
const auto digest = Nid::Sha1(std::vector<std::uint8_t>(name.begin(), name.end()));
|
|
for (const auto byte : digest) {
|
|
hostName += "0123456789abcdef"[byte >> 4];
|
|
hostName += "0123456789abcdef"[byte & 15];
|
|
}
|
|
::shm_unlink(hostName.c_str());
|
|
}
|
|
~Object() { ::shm_unlink(hostName.c_str()); }
|
|
int Open(int flags, int mode = 0600) const { return shm_open_nid_postfix(name.c_str(), flags, mode); }
|
|
};
|
|
|
|
static void RequireError(const char* name, int flags, int error) {
|
|
*__error_nid_postfix() = 0;
|
|
Require(shm_open_nid_postfix(name, flags, 0600) == -1, "expected open failure");
|
|
Require(*__error_nid_postfix() == error, "incorrect guest errno");
|
|
}
|
|
|
|
static void Run() {
|
|
constexpr int readWrite = 2, create = 0x200, exclusive = 0x800, truncate = 0x400;
|
|
Object object("/nested");
|
|
RequireError(object.name.c_str(), readWrite, 2);
|
|
*__error_nid_postfix() = 73;
|
|
const int first = object.Open(readWrite | create | exclusive);
|
|
Require(first >= 0, "named creation failed");
|
|
Require(*__error_nid_postfix() == 73, "successful creation overwrote guest errno");
|
|
Require((fcntl(first, F_GETFD) & FD_CLOEXEC) != 0, "descriptor is not close-on-exec");
|
|
struct stat info{};
|
|
Require(fstat(first, &info) == 0 && info.st_size == 0, "new object is not empty");
|
|
Require(ftruncate(first, 0x4000) == 0, "resize failed");
|
|
auto* mapped = static_cast<char*>(::mmap(nullptr, 0x4000, PROT_READ | PROT_WRITE, MAP_SHARED, first, 0));
|
|
Require(mapped != MAP_FAILED, "host shared mapping failed");
|
|
std::memcpy(mapped, "shared", 7);
|
|
RequireError(object.name.c_str(), readWrite | create | exclusive, 17);
|
|
const int reader = object.Open(truncate);
|
|
Require(reader >= 0, "read-only reopen failed");
|
|
Require(fstat(reader, &info) == 0 && info.st_size == 0x4000, "read-only O_TRUNC truncated the object");
|
|
char bytes[7]{};
|
|
Require(pread(reader, bytes, sizeof(bytes), 0) == sizeof(bytes) && std::memcmp(bytes, "shared", 7) == 0, "reopened object has different contents");
|
|
Require(pwrite(reader, bytes, sizeof(bytes), 0) == -1, "read-only descriptor accepted a write");
|
|
Require(::mmap(nullptr, 0x4000, PROT_READ | PROT_WRITE, MAP_SHARED, reader, 0) == MAP_FAILED, "read-only descriptor accepted a writable shared mapping");
|
|
Object separate("/different");
|
|
const int different = separate.Open(readWrite | create | exclusive, 0400);
|
|
Require(different >= 0 && fstat(different, &info) == 0 && info.st_size == 0, "different names share an object");
|
|
Require(ftruncate(different, 0x4000) == 0 && pwrite(different, "y", 1, 0) == 1, "creation permissions incorrectly restrict the new descriptor");
|
|
Require(mapped[0] == 's', "different object write changed the first object");
|
|
Require(close(different) == 0, "different object close failed");
|
|
const auto child = fork();
|
|
Require(child >= 0, "fork failed");
|
|
if (child == 0) {
|
|
const int reopened = object.Open(readWrite);
|
|
char contents[7]{};
|
|
const bool okay = reopened >= 0 && pread(reopened, contents, sizeof(contents), 0) == sizeof(contents) && std::memcmp(contents, "shared", 7) == 0;
|
|
if (reopened >= 0) close(reopened);
|
|
_exit(okay ? 0 : 1);
|
|
}
|
|
int status = 0;
|
|
Require(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0, "cross-process reopen failed");
|
|
Require(::munmap(mapped, 0x4000) == 0, "host unmap failed");
|
|
Require(close(reader) == 0 && close(first) == 0, "close failed");
|
|
const int truncated = object.Open(readWrite | truncate);
|
|
Require(truncated >= 0 && fstat(truncated, &info) == 0 && info.st_size == 0, "read-write truncation failed");
|
|
Require(close(truncated) == 0, "close after truncation failed");
|
|
RequireError(nullptr, readWrite, 14);
|
|
RequireError("", readWrite, 22);
|
|
RequireError("relative", readWrite, 22);
|
|
RequireError(object.name.c_str(), 1, 22);
|
|
RequireError(object.name.c_str(), readWrite | 8, 22);
|
|
const std::string tooLong(1024, '/');
|
|
RequireError(tooLong.c_str(), readWrite, 63);
|
|
const auto prefixLength = std::string("/anyps5-test-").size() + std::to_string(getpid()).size();
|
|
Object longName(std::string(1023 - prefixLength, 'x'));
|
|
Require(longName.name.size() == 1023, "incorrect boundary test length");
|
|
const auto previousMask = umask(0077);
|
|
const int longDescriptor = longName.Open(create | readWrite, 07777);
|
|
umask(previousMask);
|
|
Require(longDescriptor >= 0 && fstat(longDescriptor, &info) == 0, "long name creation failed");
|
|
Require((info.st_mode & 07777) == 0755, "default guest umask was not applied independently of host umask");
|
|
Require(close(longDescriptor) == 0, "long name close failed");
|
|
const int longReader = longName.Open(0);
|
|
Require(longReader >= 0 && close(longReader) == 0, "maximum-length name reopen failed");
|
|
const auto* anonymous = reinterpret_cast<const char*>(1);
|
|
const int anonymousFirst = shm_open_nid_postfix(anonymous, readWrite, 0);
|
|
const int anonymousSecond = shm_open_nid_postfix(anonymous, readWrite | create | exclusive | truncate, 0400);
|
|
Require(anonymousFirst >= 0 && anonymousSecond >= 0, "anonymous creation regressed");
|
|
Require(ftruncate(anonymousFirst, 0x4000) == 0 && pwrite(anonymousFirst, "x", 1, 0) == 1, "anonymous write failed");
|
|
Require(pread(anonymousSecond, bytes, 1, 0) == 0, "anonymous objects share contents");
|
|
Require(close(anonymousFirst) == 0 && close(anonymousSecond) == 0, "anonymous close failed");
|
|
RequireError(anonymous, 0, 22);
|
|
RequireError(anonymous, 3, 22);
|
|
std::puts("named shared-memory checks passed");
|
|
}
|
|
|
|
int main() {
|
|
try {
|
|
Run();
|
|
return 0;
|
|
} catch (const std::exception& error) {
|
|
std::fprintf(stderr, "%s\n", error.what());
|
|
return 1;
|
|
}
|
|
}
|