fix(libkernel): limit execve to unsupported behavior

This commit is contained in:
MarcZX100 2026-10-13 11:30:29 +00:00 committed by Adrià Franch
parent 4f1ae1cc3a
commit 77016caa24
3 changed files with 11 additions and 116 deletions

View File

@ -155,37 +155,6 @@ std::int64_t toGuestLimit(rlim_t value) {
}
#endif
constexpr int guestNoEntry = 2;
constexpr int guestAccess = 13;
constexpr int guestNotDirectory = 20;
constexpr std::size_t guestPathMax = 1024;
constexpr std::size_t guestArgumentMax = 262144;
constexpr std::uint16_t guestFileType = 0170000;
constexpr std::uint16_t guestRegularFile = 0100000;
std::size_t addStringSpace(std::size_t space, const char* const* strings) {
for (; strings != nullptr && *strings != nullptr; ++strings)
space += std::strlen(*strings) + 1;
return space;
}
int executableError(const char* function, const char* path, const char* const* arguments, const char* const* environment) {
if (arguments == nullptr || path == nullptr)
return guestFault;
if (strnlen(path, guestPathMax) == guestPathMax)
throw std::runtime_error(std::string(function) + ": path of PATH_MAX bytes or more");
if (addStringSpace(addStringSpace(0, arguments), environment) > guestArgumentMax)
throw std::runtime_error(std::string(function) + ": arguments and environment exceed ARG_MAX");
FileStat status{};
if (stat_nid_postfix(path, &status) != 0) {
const int error = *__error_nid_postfix();
if (error != guestNoEntry && error != guestNotDirectory)
throw std::runtime_error(std::string(function) + ": unsupported lookup error " + std::to_string(error));
return error;
}
return (status.st_mode & guestFileType) == guestRegularFile ? 0 : guestAccess;
}
}
extern "C" int* APS5_VABI __error_nid_postfix();
@ -373,23 +342,13 @@ int APS5_VABI execvp_nid_postfix(const char* file, char* const* arguments) {
}
int APS5_VABI execve_nid_postfix(const char* path, char* const* arguments, char* const* environment) {
if (const int error = executableError("execve", path, arguments, environment)) {
*__error_nid_postfix() = error;
return -1;
}
(void)path;
(void)arguments;
(void)environment;
NotImplemented_nid_no_patch("execve: executable replacement");
return -1;
}
int APS5_VABI execv_nid_postfix(const char* path, char* const* arguments) {
if (const int error = executableError("execv", path, arguments, environ_nid_postfix)) {
*__error_nid_postfix() = error;
return -1;
}
NotImplemented_nid_no_patch("execv: executable replacement");
return -1;
}
int APS5_VABI sceKernelGetCurrentCpu(void) {
#ifdef _WIN32
PROCESSOR_NUMBER processor{};

View File

@ -1,27 +1,10 @@
#include "prx/libc/include/General.hpp"
#include <chrono>
#include <cstdlib>
#include <fstream>
#include <stdexcept>
#include <string>
extern "C" {
int APS5_VABI execvp_nid_postfix(const char*, char* const*);
int APS5_VABI execv_nid_postfix(const char*, char* const*);
int APS5_VABI execve_nid_postfix(const char*, char* const*, char* const*);
int* APS5_VABI __error_nid_postfix();
}
static constexpr int GUEST_ENOENT = 2;
static constexpr int GUEST_EACCES = 13;
static constexpr int GUEST_EFAULT = 14;
static constexpr int GUEST_ENOTDIR = 20;
static void Require(bool value) { if (!value) std::abort(); }
static bool ReportsNotImplemented(const char* file, char* const* arguments) {
try {
execvp_nid_postfix(file, arguments);
} catch (const std::runtime_error& error) {
return std::string(error.what()) == "execvp: executable replacement not implemented";
}
return false;
}
template <typename TCall> static bool Throws(TCall call, const std::string& message) {
try {
@ -31,61 +14,14 @@ template <typename TCall> static bool Throws(TCall call, const std::string& mess
}
return false;
}
template <typename TCall> static bool Fails(TCall call, int error) {
*__error_nid_postfix() = 0;
return call() == -1 && *__error_nid_postfix() == error;
}
static void RequireBoth(const char* path, char* const* arguments, int error) {
Require(Fails([&] { return execv_nid_postfix(path, arguments); }, error));
Require(Fails([&] { return execve_nid_postfix(path, arguments, nullptr); }, error));
}
static void Require(bool value) { if (!value) std::abort(); }
int main() {
char shell[] = "sh";
char option[] = "-c";
char command[] = "exit 0";
char* arguments[] = {shell, option, command, nullptr};
Require(ReportsNotImplemented("sh", arguments));
Require(ReportsNotImplemented("/bin/sh", arguments));
Require(ReportsNotImplemented(nullptr, nullptr));
const auto name = "anyps5-exec-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count());
const auto directory = std::filesystem::temp_directory_path() / name;
Require(std::filesystem::create_directory(directory));
{ std::ofstream output(directory / "program"); output << "program"; }
AddPathAlias_nid_no_patch("/exec-alias", directory.string().c_str());
char name[] = "sh";
char* arguments[] = {name, nullptr};
char variable[] = "HOME=/";
char* environment[] = {variable, nullptr};
RequireBoth(nullptr, arguments, GUEST_EFAULT);
RequireBoth("/exec-alias/program", nullptr, GUEST_EFAULT);
RequireBoth(nullptr, nullptr, GUEST_EFAULT);
RequireBoth("/exec-alias/missing", arguments, GUEST_ENOENT);
RequireBoth("/exec-alias/missing/program", arguments, GUEST_ENOENT);
RequireBoth("", arguments, GUEST_ENOENT);
RequireBoth("/exec-alias/program/", arguments, GUEST_ENOTDIR);
RequireBoth("/exec-alias", arguments, GUEST_EACCES);
RequireBoth("/exec-alias/", arguments, GUEST_EACCES);
Require(Throws([&] { execv_nid_postfix("/exec-alias/program", arguments); }, "execv: executable replacement not implemented"));
Require(Throws([&] { execve_nid_postfix("/exec-alias/program", arguments, environment); }, "execve: executable replacement not implemented"));
Require(Throws([&] { execve_nid_postfix("/exec-alias/program", arguments, nullptr); }, "execve: executable replacement not implemented"));
std::string fittingPath = "/exec-alias/";
while (fittingPath.size() < 1016) fittingPath += "./";
fittingPath += "missing";
Require(fittingPath.size() == 1023);
RequireBoth(fittingPath.c_str(), arguments, GUEST_ENOENT);
const std::string longPath = fittingPath + "s";
Require(Throws([&] { execv_nid_postfix(longPath.c_str(), arguments); }, "execv: path of PATH_MAX bytes or more"));
Require(Throws([&] { execve_nid_postfix(longPath.c_str(), arguments, nullptr); }, "execve: path of PATH_MAX bytes or more"));
std::string fitting(262143, 'a');
char* fittingArguments[] = {fitting.data(), nullptr};
RequireBoth("/exec-alias/missing", fittingArguments, GUEST_ENOENT);
char* overflowing[] = {fitting.data(), variable, nullptr};
Require(Throws([&] { execv_nid_postfix("/exec-alias/missing", overflowing); }, "execv: arguments and environment exceed ARG_MAX"));
Require(Throws([&] { execve_nid_postfix("/exec-alias/missing", fittingArguments, environment); }, "execve: arguments and environment exceed ARG_MAX"));
RemovePathAlias_nid_no_patch("/exec-alias");
std::filesystem::remove_all(directory);
Require(Throws([&] { execvp_nid_postfix("sh", arguments); }, "execvp: executable replacement not implemented"));
Require(Throws([&] { execvp_nid_postfix("/bin/sh", arguments); }, "execvp: executable replacement not implemented"));
Require(Throws([&] { execvp_nid_postfix(nullptr, nullptr); }, "execvp: executable replacement not implemented"));
Require(Throws([&] { execve_nid_postfix("/bin/sh", arguments, environment); }, "execve: executable replacement not implemented"));
}

View File

@ -130,7 +130,7 @@
- [cpuset_getaffinity](../../../core/libs/prx/libkernel/Pthread/Posix/Thread.cpp) (libkernel) - only the calling thread or the calling process is handled. For the process the mask is the union of the affinities of the live guest threads, as [FreeBSD 11.4](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/sys/kern/kern_cpuset.c#L1150-L1156) ORs the mask of every thread in the process; threads the guest did not create through libkernel are not included, and the console result is unverified
- [dup and dup2](../../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) throw for the combinations they do not model: `dup2` of a guest socket onto descriptors 0 to 2, whose host streams libc writes without the socket table, and on Windows duplicating a directory descriptor. Guest sockets share one socket object between the duplicates; the numbers come from the host descriptor table like those of files. The errno values for descriptor exhaustion are not verified against the console.
- [waitpid](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) returns -1 with `ECHILD` (`EINVAL` for unknown options): nothing here creates a child process; child processes would need a real `waitpid`.
- [execv, execve](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) throw like `execvp` once FreeBSD's [`execve`](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/sys/kern/kern_exec.c) would start loading the image: replacing the process image is not supported. The checks before that are FreeBSD's: `EFAULT` for a null argument vector or path, `ENOENT` and `ENOTDIR` from the path lookup (as `stat` reports them) and `EACCES` for a path that is not a regular file. Other lookup errors, a path of `PATH_MAX` (1024) bytes or more and argument and environment strings over `ARG_MAX` (262144) throw instead of returning `ENAMETOOLONG` or `E2BIG`, and whether a regular file may be executed (mount `noexec`, execute bits, image format) is not checked. `execv` passes `environ`. PPSA97358 imports both and fails to load without them; the `execv` calls in its sources run in a `fork` child (`net/tap.c`), and its `fork` and `vfork` fail with `ENOSYS` ([libc_missing.c](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/ps5/compat/libc_missing.c#L202-L203), bound by [build-title.sh](https://github.com/ZiZc3/XPSemu/blob/8eec07b4e31f66dce8e0b05c1ca1281af303e933/ps5/build-title.sh#L106-L115)). The callers of its `execve` imports were not identified.
- [execve](../../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) throws like `execvp`: replacing the process image is not supported. The console behavior for invalid arguments and executable path lookups is not modeled.
- [kern.proc.pathname](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) returns `/app0/eboot.bin` for pid -1 and the calling process, following FreeBSD's `sysctl_kern_proc_pathname`; the exact pathname on retail consoles remains unverified.
- [machdep.cacheline_size](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) returns -1 with `ENOENT`, matching FreeBSD amd64 where this sysctl OID is not defined.
- [hw.model](../../../core/libs/prx/libkernel/System/src/SystemConfiguration.cpp) (libkernel) reports the host CPU's name, built as FreeBSD 11 builds `hw.model`: the CPUID brand string (leaves 0x80000002 to 0x80000004) without leading spaces ([identcpu.c](https://github.com/freebsd/freebsd-src/blob/release/11.0.0/sys/x86/x86/identcpu.c#L683-L691)). Trailing padding stays, as it does there, so an AMD host's name ends in spaces. The name a PS5 reports is unknown. A write returns -1 with `EPERM`. A host without an x86 CPUID brand string, such as an Apple Silicon Mac, throws. PPSA99203 only prints it (`[console] hw.model: %s`)