#include "prx/libkernel_sys/include/AdditionalExports.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/Api.hpp" #include #include #include #include #include #include #include #include namespace { constexpr int GuestEnoent = 2; constexpr int GuestEacces = 13; constexpr int GuestEnoexec = 8; constexpr int GuestE2big = 7; constexpr int GuestEfault = 14; void Require(bool condition, const char* message) { if (!condition) { std::fprintf(stderr, "kernel sys execvpe: %s\n", message); std::abort(); } } void RequireFailure(int result, int error, const char* message) { Require(result == -1 && *__error_nid_postfix() == error, message); } void WriteFile(const char* name, const char* content, std::streamsize bytes, std::filesystem::perms mode) { std::ofstream file(name, std::ios::binary); file.write(content, bytes); file.close(); #ifndef _WIN32 std::filesystem::permissions(name, mode); #else (void)mode; #endif } } // namespace int main() { const auto originalDirectory = std::filesystem::current_path(); const auto stamp = std::chrono::steady_clock::now().time_since_epoch().count(); std::filesystem::path work; for (unsigned int attempt = 0;; ++attempt) { work = std::filesystem::temp_directory_path() / ("anyps5-execvpe-" + std::to_string(stamp) + "-" + std::to_string(attempt)); if (std::filesystem::create_directory(work)) break; } std::filesystem::current_path(work); char first[] = "first"; char second[] = "second"; char* const argv[] = {first, second, nullptr}; constexpr auto fullExecute = std::filesystem::perms::owner_all | std::filesystem::perms::group_exec | std::filesystem::perms::others_exec; constexpr auto plainRead = std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::group_read | std::filesystem::perms::others_read; constexpr char plainBody[] = "plain data"; RequireFailure(_execvpe_nid_postfix("", argv, nullptr), GuestEnoent, "execvpe with an empty name did not fail with ENOENT"); RequireFailure(_execvpe_nid_postfix(nullptr, argv, nullptr), GuestEfault, "execvpe with a null name did not fail with EFAULT"); RequireFailure(_execvpe_nid_postfix("kernel_execvpe_absent.bin", nullptr, nullptr), GuestEnoent, "execvpe with a null argv did not fail with ENOENT"); const std::string longName(1024 * 1024, 'x'); RequireFailure(_execvpe_nid_postfix(longName.c_str(), argv, nullptr), GuestEnoent, "an overlong name did not end the bounded search"); RequireFailure(_execvpe_nid_postfix(("/" + longName).c_str(), argv, nullptr), GuestEnoent, "an overlong slash-qualified name did not end the bounded search"); const std::string oversized(2048, 'a'); std::vector storage; std::vector oversizedArgv; for (int index = 0; index < 128; ++index) { storage.push_back(oversized); oversizedArgv.push_back(storage.back().data()); } oversizedArgv.push_back(nullptr); RequireFailure(_execvpe_nid_postfix("kernel_execvpe_absent.bin", oversizedArgv.data(), nullptr), GuestE2big, "execvpe with arguments beyond ARG_MAX did not fail with E2BIG"); WriteFile("kernel_execvpe_data.bin", plainBody, sizeof(plainBody) - 1, static_cast(fullExecute)); RequireFailure(_execvpe_nid_postfix("./kernel_execvpe_data.bin", argv, nullptr), GuestEnoent, "execvpe did not return ENOENT for a missing shell"); std::filesystem::remove("kernel_execvpe_data.bin"); std::filesystem::create_directory("bin"); WriteFile("bin/sh", "", 0, static_cast(fullExecute)); RequireFailure(_execvpe_nid_postfix("./bin/sh", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); WriteFile("bin/plain.bin", "plain", 5, static_cast(fullExecute)); RequireFailure(_execvpe_nid_postfix("./bin/plain.bin", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); std::filesystem::create_directory("sbin"); RequireFailure(_execvpe_nid_postfix("kernel_execvpe_absent.bin", argv, nullptr), GuestEnoent, "execvpe with the default search path and a missing file did not fail with ENOENT"); #ifndef _WIN32 WriteFile("bin/kernel_execvpe_refused.bin", "plain", 5, static_cast(plainRead)); RequireFailure(_execvpe_nid_postfix("kernel_execvpe_refused.bin", argv, nullptr), GuestEacces, "execvpe without execute bits did not report EACCES after the search"); std::filesystem::remove("bin/kernel_execvpe_refused.bin"); #endif std::filesystem::create_directory("usr"); std::filesystem::create_directory("usr/bin"); WriteFile("usr/bin/kernel_execvpe_data.bin", plainBody, sizeof(plainBody) - 1, static_cast(fullExecute)); RequireFailure(_execvpe_nid_postfix("kernel_execvpe_data.bin", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); std::filesystem::create_directory("sandbox"); WriteFile("sandbox/kernel_execvpe_tool.bin", plainBody, sizeof(plainBody) - 1, static_cast(fullExecute)); setenv_nid_postfix("PATH", "/sandbox", 1); RequireFailure(_execvpe_nid_postfix("kernel_execvpe_tool.bin", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); unsetenv_nid_postfix("PATH"); RequireFailure(_execvpe_nid_postfix("kernel_execvpe_tool.bin", argv, nullptr), GuestEnoent, "execvpe with a cleared PATH did not fail with ENOENT"); WriteFile("kernel_execvpe_tool.bin", plainBody, sizeof(plainBody) - 1, static_cast(fullExecute)); setenv_nid_postfix("PATH", "", 1); RequireFailure(_execvpe_nid_postfix("kernel_execvpe_tool.bin", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); unsetenv_nid_postfix("PATH"); std::filesystem::remove("kernel_execvpe_tool.bin"); setenv_nid_postfix("PATH", "/sandbox", 1); WriteFile("sandbox/kernel_execvpe_plain.bin", "plain", 5, static_cast(fullExecute)); RequireFailure(_execvpe_nid_postfix("kernel_execvpe_plain.bin", argv, nullptr), GuestEnoexec, "execvpe did not reach the empty shell through its search and fallback"); std::filesystem::remove("bin/sh"); RequireFailure(_execvpe_nid_postfix("./bin/plain.bin", argv, nullptr), GuestEnoent, "execvpe of a slash-named plain file without the shell did not fail with ENOENT"); std::filesystem::remove("bin/plain.bin"); std::filesystem::create_directory("scripts"); WriteFile("scripts/kernel_execvpe_plain.bin", "plain", 5, static_cast(fullExecute)); setenv_nid_postfix("PATH", "/scripts", 1); RequireFailure(_execvpe_nid_postfix("kernel_execvpe_plain.bin", argv, nullptr), GuestEnoent, "execvpe of an unrecognised file without the shell did not fail with ENOENT"); setenv_nid_postfix("PATH", "/absent:/scripts", 1); RequireFailure(_execvpe_nid_postfix("kernel_execvpe_plain.bin", argv, nullptr), GuestEnoent, "execvpe over a missing first element did not fail with ENOENT"); unsetenv_nid_postfix("PATH"); std::filesystem::remove("sandbox/kernel_execvpe_tool.bin"); std::filesystem::remove("sandbox/kernel_execvpe_plain.bin"); std::filesystem::remove("usr/bin/kernel_execvpe_data.bin"); std::filesystem::remove("scripts/kernel_execvpe_plain.bin"); std::filesystem::remove("bin"); std::filesystem::remove("sbin"); std::filesystem::remove("sandbox"); std::filesystem::remove("scripts"); std::filesystem::remove("usr/bin"); std::filesystem::remove("usr"); std::filesystem::current_path(originalDirectory); std::filesystem::remove_all(work); std::puts("guest_kernel_sys_execvpe passed"); return 0; }