mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-28 12:05:06 +00:00
228 lines
9.7 KiB
C++
228 lines
9.7 KiB
C++
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#include "prx/libc/include/Shutdown.hpp"
|
|
#include "prx/libc/include/Api.hpp"
|
|
#include "prx/libkernel/include/Api.hpp"
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iterator>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
|
|
namespace {
|
|
|
|
bool cleaned = false;
|
|
|
|
void Require(bool condition, const char* message) {
|
|
if (!condition) {
|
|
std::fprintf(stderr, "Exec fixture: %s\n", message);
|
|
std::fflush(stderr);
|
|
std::_Exit(98);
|
|
}
|
|
}
|
|
|
|
std::filesystem::path Root() {
|
|
#ifdef _WIN32
|
|
std::wstring value(32768, L'\0');
|
|
const auto length = GetEnvironmentVariableW(L"ANYPS5_LOADEXEC_TEST_ROOT", value.data(), static_cast<DWORD>(value.size()));
|
|
Require(length > 0 && length < value.size(), "missing fixture directory");
|
|
value.resize(length);
|
|
return value;
|
|
#else
|
|
const auto* value = std::getenv("ANYPS5_LOADEXEC_TEST_ROOT");
|
|
Require(value != nullptr, "missing fixture directory");
|
|
return std::filesystem::path(reinterpret_cast<const char8_t*>(value));
|
|
#endif
|
|
}
|
|
|
|
void RecordPid() {
|
|
std::ofstream file(Root() / "pids.txt", std::ios::app);
|
|
#ifdef _WIN32
|
|
file << GetCurrentProcessId() << '\n';
|
|
#else
|
|
file << getpid() << '\n';
|
|
#endif
|
|
file.close();
|
|
Require(!file.fail(), "writing the process ID file");
|
|
}
|
|
|
|
void Record(const char* value) {
|
|
std::ofstream file(Root() / "events.txt", std::ios::app);
|
|
file << value << '\n';
|
|
file.close();
|
|
Require(!file.fail(), "writing the event file");
|
|
}
|
|
|
|
std::string Events() {
|
|
std::ifstream file(Root() / "events.txt");
|
|
return {std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
|
|
}
|
|
|
|
void Shutdown() { cleaned = true; Record("shutdown"); }
|
|
void APS5_VABI Finalize(void*) { Record("guest-exit"); }
|
|
void HostFinalize() { Record("host-exit"); }
|
|
|
|
void Register() {
|
|
LibcRegisterShutdown_nid_postfix(Shutdown);
|
|
Require(__cxa_atexit_nid_postfix(Finalize, nullptr, nullptr) == 0, "guest exit registration");
|
|
Require(std::atexit(HostFinalize) == 0, "host exit registration");
|
|
}
|
|
|
|
void Arguments(const std::uintptr_t* block, const std::vector<std::string>& expected) {
|
|
Require(block[0] == expected.size(), "entry argument count");
|
|
const auto* argv = reinterpret_cast<const char* const*>(block + 1);
|
|
Require(getargc_nid_postfix() == static_cast<int>(expected.size()), "getargc argument count");
|
|
const auto* kernel = getargv_nid_postfix();
|
|
for (std::size_t index = 0; index < expected.size(); ++index) {
|
|
Require(argv[index] && expected[index] == argv[index], "entry argument value");
|
|
Require(kernel[index] && expected[index] == kernel[index], "getargv argument value");
|
|
}
|
|
Require(argv[expected.size()] == nullptr && kernel[expected.size()] == nullptr, "argument terminator");
|
|
}
|
|
|
|
std::string LoaderEnvironment() {
|
|
const auto* value = std::getenv("LD_LIBRARY_PATH");
|
|
return value != nullptr ? std::string("LD_LIBRARY_PATH=") + value : std::string();
|
|
}
|
|
|
|
std::vector<std::string> SecondEnvironment() {
|
|
std::vector<std::string> entries{"EXEC_SECOND=stage2"};
|
|
auto loader = LoaderEnvironment();
|
|
if (!loader.empty()) entries.push_back(loader);
|
|
const auto* root = std::getenv("ANYPS5_LOADEXEC_TEST_ROOT");
|
|
Require(root != nullptr, "missing fixture directory for the environment");
|
|
entries.emplace_back(std::string("ANYPS5_LOADEXEC_TEST_ROOT=") + root);
|
|
return entries;
|
|
}
|
|
|
|
}
|
|
|
|
extern "C" void APS5_VABI ExecFirst_nid_no_patch(const std::uintptr_t* block) {
|
|
(void)block;
|
|
RecordPid();
|
|
Record("first");
|
|
Register();
|
|
char* const present[] = {const_cast<char*>("/app0/picker/second.bin"), nullptr};
|
|
char* const empty[] = {nullptr};
|
|
char* const unpaired[] = {const_cast<char*>("EXEC_SECOND"), nullptr};
|
|
const char* const absent = "/app0/picker/absent.bin";
|
|
Require(execv_nid_postfix(nullptr, present) == -1 && *__error_nid_postfix() == 9, "null path rejected with EBADF");
|
|
Require(execv_nid_postfix("/app0/picker/second.bin", nullptr) == -1 && *__error_nid_postfix() == 14,
|
|
"null arguments rejected with EFAULT");
|
|
Require(execv_nid_postfix(absent, empty) == -1 && *__error_nid_postfix() == 2,
|
|
"an empty argument list is accepted and an absent path rejected with ENOENT");
|
|
Require(execve_nid_postfix(absent, present, nullptr) == -1 && *__error_nid_postfix() == 2,
|
|
"a null environment is accepted and an absent path rejected with ENOENT");
|
|
Require(execve_nid_postfix(absent, present, unpaired) == -1 && *__error_nid_postfix() == 2,
|
|
"an unpaired environment entry is copied and an absent path rejected with ENOENT");
|
|
Require(execvp_nid_postfix("absent-bin", present) == -1 && *__error_nid_postfix() == 2, "PATH search ends with ENOENT");
|
|
static std::string huge(300000, 'X');
|
|
char* const oversized[] = {const_cast<char*>("/app0/picker/second.bin"), &huge[0], nullptr};
|
|
Require(execv_nid_postfix("/app0/picker/second.bin", oversized) == -1 && *__error_nid_postfix() == 7,
|
|
"oversized arguments rejected with E2BIG");
|
|
static std::string largeVector(262201, 'K');
|
|
static std::string largeEntry = "EXEC_LARGE_SECOND=" + largeVector;
|
|
char* const largeEnvironment[] = {&largeEntry[0], nullptr};
|
|
Require(execve_nid_postfix(absent, present, largeEnvironment) == -1 && *__error_nid_postfix() == 7,
|
|
"an environment vector beyond the limit rejected with E2BIG");
|
|
char* const beyond[] = {const_cast<char*>("/app0/picker/second.bin/child"), nullptr};
|
|
Require(execv_nid_postfix("/app0/picker/second.bin/child", beyond) == -1 && *__error_nid_postfix() == 20,
|
|
"a path beyond a regular file rejected with ENOTDIR");
|
|
#ifndef _WIN32
|
|
char* const stripped[] = {const_cast<char*>("/app0/picker/noexec.bin"), nullptr};
|
|
Require(execv_nid_postfix("/app0/picker/noexec.bin", stripped) == -1 && *__error_nid_postfix() == 13,
|
|
"a regular file without execute permission rejected with EACCES");
|
|
#endif
|
|
#ifdef _WIN32
|
|
const auto second = Root() / L"picker.second.bin.exe";
|
|
#else
|
|
const auto second = Root() / "picker.second.bin.elf";
|
|
#endif
|
|
auto intact = second;
|
|
intact += ".intact";
|
|
std::filesystem::rename(second, intact);
|
|
{
|
|
std::ofstream broken(second, std::ios::binary);
|
|
broken << "no image";
|
|
broken.close();
|
|
Require(execv_nid_postfix("/app0/picker/second.bin", present) == -1 && *__error_nid_postfix() == 8,
|
|
"a converted target without an executable image rejected with ENOEXEC");
|
|
}
|
|
std::filesystem::remove(second);
|
|
std::filesystem::rename(intact, second);
|
|
auto saved = second;
|
|
saved += ".saved";
|
|
std::filesystem::rename(second, saved);
|
|
bool rejected = false;
|
|
try {
|
|
execv_nid_postfix("/app0/picker/second.bin", present);
|
|
} catch (const std::runtime_error&) {
|
|
rejected = true;
|
|
}
|
|
std::filesystem::rename(saved, second);
|
|
Require(rejected && Events() == "first\n", "missing conversion rejected without replacing");
|
|
std::filesystem::current_path(Root() / "app0" / "working");
|
|
Record("launch");
|
|
std::vector<std::string> values = SecondEnvironment();
|
|
std::vector<char*> environment;
|
|
for (auto& value : values) environment.push_back(&value[0]);
|
|
environment.push_back(nullptr);
|
|
char* const target[] = {const_cast<char*>("/app0/picker/second.bin"), const_cast<char*>("argument with spaces"),
|
|
const_cast<char*>("quote\"and\\slash\\ Кириллица 東京"), nullptr};
|
|
try {
|
|
execve_nid_postfix("/app0/picker/second.bin", target, environment.data());
|
|
} catch (...) {
|
|
Record("throw");
|
|
std::fflush(nullptr);
|
|
std::_Exit(96);
|
|
}
|
|
Require(false, "execve returned to the first executable");
|
|
}
|
|
|
|
extern "C" void APS5_VABI ExecSecond_nid_no_patch(const std::uintptr_t* block) {
|
|
Arguments(block, {"/app0/picker/second.bin", "argument with spaces", "quote\"and\\slash\\ Кириллица 東京"});
|
|
#ifdef _WIN32
|
|
Require(std::filesystem::equivalent(std::filesystem::current_path(), Root()), "secondary inherits the image directory");
|
|
#else
|
|
Require(std::filesystem::equivalent(std::filesystem::current_path(), Root() / "app0" / "working"), "exec keeps the working directory");
|
|
#endif
|
|
Require(Events() == "first\nlaunch\n", "exec replaced the image without running exit handlers");
|
|
const std::vector<std::string> entries{SecondEnvironment()};
|
|
#ifdef _WIN32
|
|
char value[64]{};
|
|
Require(GetEnvironmentVariableA("EXEC_SECOND", value, 64) == 6 && value == std::string("stage2"),
|
|
"execve environment entry reached the replacement");
|
|
Require(GetEnvironmentVariableA("PATH", value, 64) != 0, "execve keeps the inherited host variables");
|
|
Require(GetEnvironmentVariableA("ANYPS5_LOADEXEC_PARENT", value, 64) == 0, "parent wait variable was retained");
|
|
Require(GetEnvironmentVariableA("ANYPS5_LOADEXEC_ARGV_SKIP", value, 64) == 0, "argument marker was retained");
|
|
#else
|
|
extern char** environ;
|
|
Require(environ[0] != nullptr, "replacement environment present");
|
|
for (const auto& entry : entries) {
|
|
bool found = false;
|
|
for (std::size_t index = 0; environ[index] != nullptr; ++index)
|
|
found = found || std::strcmp(environ[index], entry.c_str()) == 0;
|
|
Require(found, "replacement environment entry value");
|
|
}
|
|
bool hostPath = false;
|
|
for (std::size_t index = 0; environ[index] != nullptr; ++index)
|
|
hostPath = hostPath || std::strncmp(environ[index], "PATH=", 5) == 0;
|
|
Require(hostPath, "exec keeps the inherited host variables");
|
|
Require(!cleaned, "secondary inherited old runtime state");
|
|
#endif
|
|
RecordPid();
|
|
Record("second");
|
|
std::fflush(nullptr);
|
|
std::_Exit(37);
|
|
}
|