fix(kernel): safely copy guest path arguments

This commit is contained in:
ali zedan authored and Adrià Franch committed 2026-10-25 08:28:09 +01:00
1 parent e5f23554c9
commit 2c5fcf6406
8 files changed
+333 -78

No files matched your search

+1
View File
@@ -27,6 +27,7 @@ extern "C" std::FILE* HostStandardErrorFile_nid_no_patch();
#endif
extern "C" bool CopyGuestEnvironment_nid_no_patch(const char* name, std::string& value);
extern "C" int CopyGuestPath_nid_no_patch(const char* source, std::string& destination);
extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path);
extern "C" std::filesystem::path ResolvePathOrRelinkedModule_nid_no_patch(const char* path);
+70
View File
@@ -1,5 +1,7 @@
#include "prx/libc/include/Api.hpp"
#include <algorithm>
#include <atomic>
#include <array>
#include <cstdio>
#include <mutex>
#include <stdexcept>
@@ -8,6 +10,7 @@
#include <utility>
#include <vector>
#include <filesystem>
#include <limits>
#include <optional>
#include <cerrno>
#include <cstring>
@@ -29,12 +32,19 @@
#include <ctime>
#include <fcntl.h>
#include <sys/stat.h>
#ifdef __linux__
#include <sys/uio.h>
#elif defined(__APPLE__)
#include <mach/mach.h>
#include <mach/mach_vm.h>
#endif
#include <unistd.h>
#endif
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/FilesystemError.hpp"
#include "prx/libc/include/AppArchive.hpp"
#include "prx/libc/include/ApplicationHeap.hpp"
#include "prx/libc/include/GuestArena.hpp"
namespace {
// Guest prefixes (without leading slashes) mapped to host directories, e.g. save-data mount points:
@@ -51,6 +61,41 @@ PathAliases& Aliases() {
return *aliases;
}
std::size_t HostPageSize() {
static const std::size_t size = [] {
#ifdef _WIN32
SYSTEM_INFO info{};
GetSystemInfo(&info);
return static_cast<std::size_t>(info.dwPageSize);
#else
const long value = sysconf(_SC_PAGESIZE);
if (value <= 0) throw std::runtime_error("cannot query the host page size");
return static_cast<std::size_t>(value);
#endif
}();
return size;
}
bool CopyGuestBytes(void* destination, const void* source, std::size_t bytes) {
GuestArena::GuestArenaPrepareHostRead_nid_no_patch(source, bytes);
#ifdef _WIN32
SIZE_T copied = 0;
return ReadProcessMemory(GetCurrentProcess(), source, destination, bytes, &copied) != 0 && copied == bytes;
#elif defined(__linux__)
iovec local{destination, bytes};
iovec remote{const_cast<void*>(source), bytes};
const auto copied = process_vm_readv(getpid(), &local, 1, &remote, 1, 0);
if (copied < 0 && errno != EFAULT) throw std::runtime_error("guest path: process_vm_readv failed with errno " + std::to_string(errno));
return copied >= 0 && static_cast<std::size_t>(copied) == bytes;
#elif defined(__APPLE__)
mach_vm_size_t copied = 0;
return mach_vm_read_overwrite(mach_task_self(), reinterpret_cast<mach_vm_address_t>(source), bytes,
reinterpret_cast<mach_vm_address_t>(destination), &copied) == KERN_SUCCESS && copied == bytes;
#else
throw std::runtime_error("checked guest path copies are not implemented on this host");
#endif
}
std::string TrimSlashes(const char* path) {
std::string s(path);
std::size_t start = 0;
@@ -489,6 +534,31 @@ int SetWorkingDirectory(WorkingDirectory& state, const std::filesystem::path& re
}
}
extern "C" int CopyGuestPath_nid_no_patch(const char* source, std::string& destination) {
constexpr std::size_t maximum = 1024;
if (source == nullptr) return 14;
const auto address = reinterpret_cast<std::uintptr_t>(source);
std::array<char, maximum> bytes{};
std::size_t offset = 0;
while (offset < bytes.size()) {
if (address > std::numeric_limits<std::uintptr_t>::max() - offset) return 14;
const auto current = address + offset;
const auto pageRemaining = HostPageSize() - current % HostPageSize();
const auto count = std::min(pageRemaining, bytes.size() - offset);
if (!CopyGuestBytes(bytes.data() + offset, reinterpret_cast<const void*>(current), count)) return 14;
if (const auto* end = static_cast<const char*>(std::memchr(bytes.data() + offset, 0, count))) {
try {
destination.assign(bytes.data(), static_cast<std::size_t>(end - bytes.data()));
} catch (const std::bad_alloc&) {
return 12;
}
return 0;
}
offset += count;
}
return 63;
}
#ifndef _WIN32
namespace {
std::mutex g_hostStandardErrorMutex;
+10 -3
View File
@@ -107,10 +107,17 @@ static int SourceLookupError_nid_no_patch(const std::filesystem::path& source) {
}
extern "C" int RenameGuestPath_nid_no_patch(const char* from, const char* to) {
if (!from) { errno = 14; return -1; }
if (!*from) { errno = 2; return -1; }
std::string fromCopy;
if (const int error = CopyGuestPath_nid_no_patch(from, fromCopy)) { errno = error; return -1; }
from = fromCopy.c_str();
if (fromCopy.empty()) { errno = 2; return -1; }
if (const int dot = RenameDotError_nid_no_patch(from)) { errno = dot; return -1; }
const int destinationError = !to ? 14 : !*to ? 2 : RenameDotError_nid_no_patch(to);
std::string toCopy;
int destinationError = CopyGuestPath_nid_no_patch(to, toCopy);
if (destinationError == 0) {
to = toCopy.c_str();
destinationError = toCopy.empty() ? 2 : RenameDotError_nid_no_patch(to);
}
try {
const auto source = ResolvePath_nid_no_patch(from);
if (destinationError) {
+12 -5
View File
@@ -262,7 +262,10 @@ extern "C" {
int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode) {
if (File::InvalidOpenFlags(flags)) return SCE_KERNEL_ERROR_EINVAL;
if (path != nullptr && *path == '\0') return SCE_KERNEL_ERROR_ENOENT;
std::string copy;
if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceKernelError(error);
path = copy.c_str();
if (copy.empty()) return SCE_KERNEL_ERROR_ENOENT;
APS5_LOG_OUT("path=%s flags=0x%X nativeFlags=0x%X mode=0%o", path, flags, MapFlags(flags), mode);
if (path != nullptr && File::IsRandomDevicePath(path)) {
if ((flags & SCE_KERNEL_O_ACCMODE) != SCE_KERNEL_O_RDONLY) throw std::runtime_error(std::string(__func__) + ": writing to " + path + " is not implemented");
@@ -416,8 +419,10 @@ static int StatNative(const std::filesystem::path& native, bool directory, FileS
}
int APS5_VABI sceKernelStat(const char* path, FileStat* sb) {
if (path == nullptr) return SceErrorFromErrno(GuestEfault);
if (*path == '\0') return SceErrorFromErrno(2);
std::string copy;
if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error);
path = copy.c_str();
if (copy.empty()) return SceErrorFromErrno(2);
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native, true, true)) return SceKernelError(error);
return StatNative(native, std::string_view(path).back() == '/', sb);
@@ -435,8 +440,10 @@ int StatInDirectory_nid_no_patch(int directory, const char* path, FileStat* sb,
}
int APS5_VABI sceKernelUnlink(const char* path) {
if (path == nullptr) return SceErrorFromErrno(GuestEfault);
if (*path == '\0') return SceErrorFromErrno(2);
std::string copy;
if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error);
path = copy.c_str();
if (copy.empty()) return SceErrorFromErrno(2);
if (const int dot = RenameDotError_nid_no_patch(path)) return SceKernelError(dot);
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error);
@@ -992,6 +992,9 @@ extern "C" int APS5_VABI shm_open_nid_postfix(const char* path, int flags, int m
if (accessMode != SCE_KERNEL_O_RDONLY && accessMode != SCE_KERNEL_O_RDWR) return Fail(GuestInvalid);
if ((flags & ~AcceptedFlags) != 0) return Fail(GuestInvalid);
if (path != anonymousObject) {
std::string copy;
if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return Fail(error);
path = copy.c_str();
#ifdef _WIN32
return OpenNamedFile(path, flags, mode);
#else
@@ -1007,6 +1010,9 @@ extern "C" int APS5_VABI shm_open_nid_postfix(const char* path, int flags, int m
}
extern "C" int APS5_VABI shm_unlink_nid_postfix(const char* path) {
std::string copy;
if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return Fail(error);
path = copy.c_str();
#ifdef _WIN32
return UnlinkNamedFile(path);
#else
+60 -25
View File
@@ -597,9 +597,9 @@ extern "C" int APS5_VABI pipe_nid_postfix(int* descriptors) {
return 0;
}
static int PathError(const char* path) {
if (path == nullptr) return GUEST_EFAULT;
return *path == '\0' ? GUEST_ENOENT : 0;
static int PathError(const char* path, std::string& copy) {
if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return error;
return copy.empty() ? GUEST_ENOENT : 0;
}
#ifdef _WIN32
@@ -682,7 +682,9 @@ static int SetFileFlags(const char* function, const std::string& target, std::ui
extern "C" {
int APS5_VABI chmod_nid_postfix(const char* path, int mode) {
if (const int error = PathError(path)) return PosixFailure(error);
std::string copy;
if (const int error = PathError(path, copy)) return PosixFailure(error);
path = copy.c_str();
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return PosixFailure(error);
if (NativeChmod(native, mode & 07777) != 0) return PosixResult(SceErrorFromErrno(errno == ENOENT ? NotFoundError_nid_no_patch(native) : errno));
@@ -690,7 +692,9 @@ int APS5_VABI chmod_nid_postfix(const char* path, int mode) {
}
int APS5_VABI chflags_nid_postfix(const char* path, std::uint64_t flags) {
if (const int error = PathError(path)) return PosixFailure(error);
std::string copy;
if (const int error = PathError(path, copy)) return PosixFailure(error);
path = copy.c_str();
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return PosixFailure(error);
if (const int error = LookupError(native)) return PosixFailure(error);
@@ -943,7 +947,9 @@ int64_t APS5_VABI lseek_nid_postfix(int d, int64_t offset, int whence) {
}
int APS5_VABI mkdir_nid_postfix(const char* path, uint16_t mode) {
if (const int error = PathError(path)) return PosixFailure(error);
std::string copy;
if (const int error = PathError(path, copy)) return PosixFailure(error);
path = copy.c_str();
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return PosixFailure(error);
if (NativeMkdir(native, mode) != 0) {
@@ -960,7 +966,9 @@ int APS5_VABI mkdir_nid_postfix(const char* path, uint16_t mode) {
int APS5_VABI open_nid_postfix(const char* path, int flags, int mode) {
if (File::InvalidOpenFlags(flags)) return PosixFailure(GUEST_EINVAL);
if (const int error = PathError(path)) return PosixFailure(error);
std::string copy;
if (const int error = PathError(path, copy)) return PosixFailure(error);
path = copy.c_str();
return PosixResult(sceKernelOpen(path, flags, static_cast<std::uint16_t>(mode)));
}
@@ -1043,13 +1051,17 @@ std::int64_t APS5_VABI _write_nid_postfix(int descriptor, const void* buffer, st
int APS5_VABI stat_nid_postfix(const char* path, FileStat* sb) {
if (sb == nullptr) return PosixFailure(GUEST_EFAULT);
if (const int error = PathError(path)) return PosixFailure(error);
std::string copy;
if (const int error = PathError(path, copy)) return PosixFailure(error);
path = copy.c_str();
return PosixResult(sceKernelStat(path, sb));
}
int APS5_VABI lstat_nid_postfix(const char* path, FileStat* sb) {
if (sb == nullptr) return PosixFailure(GUEST_EFAULT);
if (const int error = PathError(path)) return PosixFailure(error);
std::string copy;
if (const int error = PathError(path, copy)) return PosixFailure(error);
path = copy.c_str();
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return PosixFailure(error);
if (!File::FillLinkStat(native, sb)) return PosixResult(SceErrorFromErrno(errno == ENOENT ? NotFoundError_nid_no_patch(native) : errno));
@@ -1057,13 +1069,18 @@ int APS5_VABI lstat_nid_postfix(const char* path, FileStat* sb) {
}
int APS5_VABI unlink_nid_postfix(const char* path) {
if (const int error = PathError(path)) return PosixFailure(error);
std::string copy;
if (const int error = PathError(path, copy)) return PosixFailure(error);
path = copy.c_str();
return PosixResult(sceKernelUnlink(path));
}
int APS5_VABI sceKernelCheckReachability(const char* path) {
if (path == nullptr) return SCE_KERNEL_ERROR_EINVAL;
if (*path == '\0') return SCE_KERNEL_ERROR_ENOENT;
std::string copy;
if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceKernelError(error);
path = copy.c_str();
if (copy.empty()) return SCE_KERNEL_ERROR_ENOENT;
if (std::strlen(path) > 255) return SCE_KERNEL_ERROR_ENAMETOOLONG;
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native, true, true)) return SceKernelError(error);
@@ -1238,8 +1255,10 @@ int APS5_VABI getdents_nid_postfix(int fd, char* buf, int nbytes) {
int APS5_VABI sceKernelMkdir(const char* path, uint16_t mode) {
(void)mode;
if (!path) return SceErrorFromErrno(GUEST_EFAULT);
if (!*path) return SceErrorFromErrno(GUEST_ENOENT);
std::string copy;
if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error);
path = copy.c_str();
if (copy.empty()) return SceErrorFromErrno(GUEST_ENOENT);
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error);
while (!native.has_filename() && native.has_relative_path()) native = native.parent_path();
@@ -1504,8 +1523,10 @@ int APS5_VABI sceKernelRename(const char* from, const char* to) {
}
int APS5_VABI sceKernelRmdir(const char* path) {
if (!path) return SceErrorFromErrno(GUEST_EFAULT);
if (*path == '\0') return SceErrorFromErrno(GUEST_ENOENT);
std::string copy;
if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error);
path = copy.c_str();
if (copy.empty()) return SceErrorFromErrno(GUEST_ENOENT);
if (const int dot = RenameDotError_nid_no_patch(path)) return SceKernelError(dot);
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error);
@@ -1526,7 +1547,9 @@ int APS5_VABI sceKernelRmdir(const char* path) {
}
int APS5_VABI rmdir_nid_postfix(const char* path) {
if (const int error = PathError(path)) return PosixFailure(error);
std::string copy;
if (const int error = PathError(path, copy)) return PosixFailure(error);
path = copy.c_str();
return PosixResult(sceKernelRmdir(path));
}
@@ -1535,8 +1558,10 @@ int APS5_VABI rmdir_nid_postfix(const char* path) {
extern "C" {
int APS5_VABI sceKernelChmod_nid_postfix(const char* path, std::uint16_t mode) {
if (!path) return SceErrorFromErrno(GUEST_EFAULT);
if (!*path) return SceErrorFromErrno(GUEST_ENOENT);
std::string copy;
if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error);
path = copy.c_str();
if (copy.empty()) return SceErrorFromErrno(GUEST_ENOENT);
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error);
if (NativeChmod(native, mode & 07777) != 0) return SceErrorFromErrno(errno);
@@ -1564,8 +1589,10 @@ static bool InvalidTimes(const KernelTimeval* times) {
int APS5_VABI sceKernelTruncate_nid_postfix(const char* path, std::int64_t length) {
if (length < 0) return SceErrorFromErrno(GUEST_EINVAL);
if (!path) return SceErrorFromErrno(GUEST_EFAULT);
if (*path == '\0') return SceErrorFromErrno(GUEST_ENOENT);
std::string copy;
if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error);
path = copy.c_str();
if (copy.empty()) return SceErrorFromErrno(GUEST_ENOENT);
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error);
std::error_code error;
@@ -1585,14 +1612,18 @@ int APS5_VABI sceKernelTruncate_nid_postfix(const char* path, std::int64_t lengt
int APS5_VABI truncate_nid_postfix(const char* path, std::int64_t length) {
if (length < 0) return PosixFailure(GUEST_EINVAL);
if (const int error = PathError(path)) return PosixFailure(error);
std::string copy;
if (const int error = PathError(path, copy)) return PosixFailure(error);
path = copy.c_str();
return PosixResult(sceKernelTruncate_nid_postfix(path, length));
}
int APS5_VABI sceKernelUtimes_nid_postfix(const char* path, const KernelTimeval* times) {
if (InvalidTimes(times)) return SCE_KERNEL_ERROR_EINVAL;
if (!path) return SceErrorFromErrno(GUEST_EFAULT);
if (*path == '\0') return SceErrorFromErrno(GUEST_ENOENT);
std::string copy;
if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error);
path = copy.c_str();
if (copy.empty()) return SceErrorFromErrno(GUEST_ENOENT);
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error);
if (NativeUtimes(native, times) != 0) return SceErrorFromErrno(errno == ENOENT ? NotFoundError_nid_no_patch(native) : errno);
@@ -1601,13 +1632,17 @@ int APS5_VABI sceKernelUtimes_nid_postfix(const char* path, const KernelTimeval*
int APS5_VABI utimes_nid_postfix(const char* path, const KernelTimeval* times) {
if (InvalidTimes(times)) return PosixFailure(GUEST_EINVAL);
if (const int error = PathError(path)) return PosixFailure(error);
std::string copy;
if (const int error = PathError(path, copy)) return PosixFailure(error);
path = copy.c_str();
return PosixResult(sceKernelUtimes_nid_postfix(path, times));
}
int APS5_VABI lutimes_nid_postfix(const char* path, const KernelTimeval* times) {
if (InvalidTimes(times)) return PosixFailure(GUEST_EINVAL);
if (const int error = PathError(path)) return PosixFailure(error);
std::string copy;
if (const int error = PathError(path, copy)) return PosixFailure(error);
path = copy.c_str();
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return PosixFailure(error);
if (NativeUtimes(native, times, false) != 0) return PosixResult(SceErrorFromErrno(errno));
+58 -45
View File
@@ -162,6 +162,12 @@ std::int64_t PathConfigurationValue(int name, const char* function) {
}
}
int CopyPath(const char*& path, std::string& copy) {
if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return error;
path = copy.c_str();
return copy.empty() ? GuestEnoent : 0;
}
int GuestError(const std::error_code& error) {
const auto condition = error.default_error_condition();
if (condition == std::errc::resource_unavailable_try_again) return GuestEagain;
@@ -351,8 +357,8 @@ int APS5_VABI fstat_nid_postfix(int d, FileStat* sb) {
std::int64_t APS5_VABI readlink_nid_postfix(const char* path, char* buffer, std::size_t size) {
if (size > static_cast<std::size_t>(std::numeric_limits<std::int64_t>::max())) return Fail(GuestEinval);
if (path == nullptr) return Fail(GuestEfault);
if (*path == '\0') return Fail(GuestEnoent);
std::string copy;
if (const int error = CopyPath(path, copy)) return Fail(error);
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return Fail(error);
return ReadNativeLink(native, buffer, size);
@@ -360,8 +366,8 @@ std::int64_t APS5_VABI readlink_nid_postfix(const char* path, char* buffer, std:
std::int64_t APS5_VABI readlinkat_nid_postfix(int fd, const char* path, char* buffer, std::size_t size) {
if (size > static_cast<std::size_t>(std::numeric_limits<std::int64_t>::max())) return Fail(GuestEinval);
if (path == nullptr) return Fail(GuestEfault);
if (*path == '\0') return Fail(GuestEnoent);
std::string copy;
if (const int error = CopyPath(path, copy)) return Fail(error);
if (fd == GuestAtFdcwd || *path == '/') return readlink_nid_postfix(path, buffer, size);
int error = 0;
const char* base = GetDirectoryDescriptorPath_nid_no_patch(fd, &error);
@@ -371,8 +377,8 @@ std::int64_t APS5_VABI readlinkat_nid_postfix(int fd, const char* path, char* bu
}
std::int64_t APS5_VABI pathconf_nid_postfix(const char* path, int name) {
if (path == nullptr) return Fail(GuestEfault);
if (*path == '\0') return Fail(GuestEnoent);
std::string copy;
if (const int error = CopyPath(path, copy)) return Fail(error);
if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return Fail(GuestEnametoolong);
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return Fail(error);
@@ -463,12 +469,13 @@ static int LinkNative(const std::filesystem::path& source, const std::filesystem
}
int APS5_VABI link_nid_postfix(const char* existing, const char* created) {
if (existing == nullptr || created == nullptr) return static_cast<int>(Fail(GuestEfault));
if (*existing == '\0' || *created == '\0') return static_cast<int>(Fail(GuestEnoent));
std::string existingCopy, createdCopy;
if (const int error = CopyPath(existing, existingCopy)) return static_cast<int>(Fail(error));
std::filesystem::path source, destination;
if (const int error = ResolvePathError_nid_no_patch(existing, source)) return static_cast<int>(Fail(error));
if (const int error = ResolvePathError_nid_no_patch(created, destination)) return static_cast<int>(Fail(error));
if (const int error = LinkSourceError(source)) return static_cast<int>(Fail(error));
if (const int error = CopyPath(created, createdCopy)) return static_cast<int>(Fail(error));
if (const int error = ResolvePathError_nid_no_patch(created, destination)) return static_cast<int>(Fail(error));
return LinkNative(source, destination);
}
@@ -484,14 +491,13 @@ static int ResolveAtPath(int fd, const char* path, std::filesystem::path& native
int APS5_VABI linkat_nid_postfix(int fd1, const char* path1, int fd2, const char* path2, int flag) {
if (flag & ~GuestAtSymlinkFollow) return static_cast<int>(Fail(GuestEinval));
if (path1 == nullptr) return static_cast<int>(Fail(GuestEfault));
if (*path1 == '\0') return static_cast<int>(Fail(GuestEnoent));
std::string copy1, copy2;
if (const int error = CopyPath(path1, copy1)) return static_cast<int>(Fail(error));
std::filesystem::path source, destination;
if (const int error = ResolveAtPath(fd1, path1, source)) return static_cast<int>(Fail(error));
if (flag == 0 && IsSymbolicLink(source)) NotImplemented_nid_no_patch("linkat: a hard link to a symbolic link itself");
if (const int error = LinkSourceError(source)) return static_cast<int>(Fail(error));
if (path2 == nullptr) return static_cast<int>(Fail(GuestEfault));
if (*path2 == '\0') return static_cast<int>(Fail(GuestEnoent));
if (const int error = CopyPath(path2, copy2)) return static_cast<int>(Fail(error));
if (const int error = ResolveAtPath(fd2, path2, destination)) return static_cast<int>(Fail(error));
return LinkNative(source, destination);
}
@@ -510,8 +516,8 @@ int APS5_VABI fchdir_nid_postfix(int fd) {
}
int APS5_VABI chroot_nid_postfix(const char* path) {
if (path == nullptr) return static_cast<int>(Fail(GuestEfault));
if (*path == '\0') return static_cast<int>(Fail(GuestEnoent));
std::string copy;
if (const int error = CopyPath(path, copy)) return static_cast<int>(Fail(error));
if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return static_cast<int>(Fail(GuestEnametoolong));
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return static_cast<int>(Fail(error));
@@ -527,9 +533,9 @@ int APS5_VABI chroot_nid_postfix(const char* path) {
}
int APS5_VABI symlink_nid_postfix(const char* target, const char* created) {
if (target == nullptr || created == nullptr) return static_cast<int>(Fail(GuestEfault));
if (std::strlen(target) >= GuestPathMax) return static_cast<int>(Fail(GuestEnametoolong));
if (*target == '\0' || *created == '\0') return static_cast<int>(Fail(GuestEnoent));
std::string targetCopy, createdCopy;
if (const int error = CopyPath(target, targetCopy)) return static_cast<int>(Fail(error));
if (const int error = CopyPath(created, createdCopy)) return static_cast<int>(Fail(error));
const std::filesystem::path contents(target);
if (contents.has_root_name() || contents.has_root_directory()) {
NotImplemented_nid_no_patch("symlink: absolute targets");
@@ -588,8 +594,8 @@ int APS5_VABI openat_nid_postfix(int fd, const char* path, int flags, ...) {
#endif
}
if (File::InvalidOpenFlags(flags)) return static_cast<int>(Fail(GuestEinval));
if (path == nullptr) return static_cast<int>(Fail(GuestEfault));
if (*path == '\0') return static_cast<int>(Fail(GuestEnoent));
std::string copy;
if (const int error = CopyPath(path, copy)) return static_cast<int>(Fail(error));
if (fd == GuestAtFdcwd || *path == '/') return open_nid_postfix(path, flags, mode);
const int result = OpenInDirectory_nid_no_patch(fd, path, flags, static_cast<std::uint16_t>(mode));
if (result < 0) return static_cast<int>(Fail(result & 0xffff));
@@ -616,8 +622,8 @@ int APS5_VABI _openat_nid_postfix(int fd, const char* path, int flags, ...) {
int APS5_VABI mkdirat_nid_postfix(int fd, const char* path, std::uint16_t mode) {
if (path == nullptr) return static_cast<int>(Fail(GuestEfault));
if (*path == '\0') return static_cast<int>(Fail(GuestEnoent));
std::string copy;
if (const int error = CopyPath(path, copy)) return static_cast<int>(Fail(error));
if (fd == GuestAtFdcwd || *path == '/') return mkdir_nid_postfix(path, mode);
if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return static_cast<int>(Fail(GuestEnametoolong));
int error = 0;
@@ -653,8 +659,8 @@ int APS5_VABI mkdirat_nid_postfix(int fd, const char* path, std::uint16_t mode)
int APS5_VABI fchmodat_nid_postfix(int fd, const char* path, int mode, int flag) {
if (flag != 0 && flag != GuestAtSymlinkNofollow) return static_cast<int>(Fail(GuestEinval));
if (path == nullptr) return static_cast<int>(Fail(GuestEfault));
if (*path == '\0') return static_cast<int>(Fail(GuestEnoent));
std::string copy;
if (const int error = CopyPath(path, copy)) return static_cast<int>(Fail(error));
std::filesystem::path native;
if (fd == GuestAtFdcwd || *path == '/') {
if (const int error = ResolvePathError_nid_no_patch(path, native)) return static_cast<int>(Fail(error));
@@ -692,9 +698,13 @@ int APS5_VABI fchmodat_nid_postfix(int fd, const char* path, int mode, int flag)
}
int APS5_VABI lchmod_nid_postfix(const char* path, std::uint16_t mode) {
std::string copy;
if (path != nullptr) {
if (const int error = CopyPath(path, copy)) return static_cast<int>(Fail(error));
}
#ifndef __APPLE__
std::filesystem::path native;
if (path != nullptr && *path != '\0' && ResolvePathError_nid_no_patch(path, native) == 0) {
if (path != nullptr && ResolvePathError_nid_no_patch(path, native) == 0) {
#ifdef _WIN32
const DWORD attributes = GetFileAttributesW(native.wstring().c_str());
const bool symbolic = attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
@@ -755,8 +765,8 @@ int APS5_VABI fchownat_nid_postfix(int fd, const char* path, std::uint32_t uid,
int APS5_VABI fstatat_nid_postfix(int fd, const char* path, FileStat* sb, int flag) {
if (flag & ~GuestAtSymlinkNofollow) return static_cast<int>(Fail(GuestEinval));
if (path == nullptr) return static_cast<int>(Fail(GuestEfault));
if (*path == '\0') return static_cast<int>(Fail(GuestEnoent));
std::string copy;
if (const int error = CopyPath(path, copy)) return static_cast<int>(Fail(error));
if (fd == GuestAtFdcwd || *path == '/') return flag ? lstat_nid_postfix(path, sb) : stat_nid_postfix(path, sb);
const int result = StatInDirectory_nid_no_patch(fd, path, sb, flag == 0);
if (result < 0) return static_cast<int>(Fail(result & 0xffff));
@@ -765,8 +775,8 @@ int APS5_VABI fstatat_nid_postfix(int fd, const char* path, FileStat* sb, int fl
int APS5_VABI unlinkat_nid_postfix(int fd, const char* path, int flag) {
if (flag & ~GuestAtRemovedir) return static_cast<int>(Fail(GuestEinval));
if (path == nullptr) return static_cast<int>(Fail(GuestEfault));
if (*path == '\0') return static_cast<int>(Fail(GuestEnoent));
std::string copy;
if (const int error = CopyPath(path, copy)) return static_cast<int>(Fail(error));
if (fd == GuestAtFdcwd || *path == '/') return (flag & GuestAtRemovedir) ? rmdir_nid_postfix(path) : unlink_nid_postfix(path);
if (std::strlen(path) >= GuestPathMax || HasLongComponent(path)) return static_cast<int>(Fail(GuestEnametoolong));
int error = 0;
@@ -842,14 +852,13 @@ int APS5_VABI scePthreadCondattrGetclock(const PthreadCondattr* attr, KernelCloc
}
int APS5_VABI renameat_nid_postfix(int oldfd, const char* from, int newfd, const char* to) {
if (from == nullptr || to == nullptr) return static_cast<int>(Fail(GuestEfault));
if (*from == '\0' || *to == '\0') return static_cast<int>(Fail(GuestEnoent));
if ((oldfd == GuestAtFdcwd || *from == '/') && (newfd == GuestAtFdcwd || *to == '/'))
std::string fromCopy, toCopy;
if (const int error = CopyPath(from, fromCopy)) return static_cast<int>(Fail(error));
if ((oldfd == GuestAtFdcwd || *from == '/') && newfd == GuestAtFdcwd)
return RenameGuestPath_nid_no_patch(from, to);
if (std::strlen(from) >= GuestPathMax || HasLongComponent(from) ||
std::strlen(to) >= GuestPathMax || HasLongComponent(to))
if (std::strlen(from) >= GuestPathMax || HasLongComponent(from))
return static_cast<int>(Fail(GuestEnametoolong));
if (IsDotOrDotDot(from) || IsDotOrDotDot(to)) return static_cast<int>(Fail(GuestEinval));
if (IsDotOrDotDot(from)) return static_cast<int>(Fail(GuestEinval));
std::filesystem::path source;
if (oldfd == GuestAtFdcwd || *from == '/') {
@@ -861,6 +870,20 @@ int APS5_VABI renameat_nid_postfix(int oldfd, const char* from, int newfd, const
source = std::filesystem::path(reinterpret_cast<const char8_t*>(base)) /
std::filesystem::path(reinterpret_cast<const char8_t*>(from));
}
const std::string_view fromView(from);
const bool oldTrailingSlash = !fromView.empty() && fromView.back() == '/';
while (!source.has_filename() && source.has_relative_path()) source = source.parent_path();
std::error_code statusError;
const auto sourceStatus = std::filesystem::symlink_status(source, statusError);
if (statusError && statusError != std::errc::no_such_file_or_directory)
return static_cast<int>(Fail(FilesystemError(statusError)));
if (sourceStatus.type() == std::filesystem::file_type::not_found)
return static_cast<int>(Fail(HasNonDirectoryPrefix(source) ? GuestEnotdir : GuestEnoent));
if (oldTrailingSlash && !std::filesystem::is_directory(sourceStatus)) return static_cast<int>(Fail(GuestEnotdir));
if (const int error = CopyPath(to, toCopy)) return static_cast<int>(Fail(error));
if (std::strlen(to) >= GuestPathMax || HasLongComponent(to)) return static_cast<int>(Fail(GuestEnametoolong));
if (IsDotOrDotDot(to)) return static_cast<int>(Fail(GuestEinval));
std::filesystem::path destination;
if (newfd == GuestAtFdcwd || *to == '/') {
@@ -873,21 +896,11 @@ int APS5_VABI renameat_nid_postfix(int oldfd, const char* from, int newfd, const
std::filesystem::path(reinterpret_cast<const char8_t*>(to));
}
const std::string_view fromView(from);
const std::string_view toView(to);
const bool oldTrailingSlash = !fromView.empty() && fromView.back() == '/';
const bool newTrailingSlash = !toView.empty() && toView.back() == '/';
while (!source.has_filename() && source.has_relative_path()) source = source.parent_path();
while (!destination.has_filename() && destination.has_relative_path()) destination = destination.parent_path();
std::error_code statusError;
const auto sourceStatus = std::filesystem::symlink_status(source, statusError);
if (oldTrailingSlash) {
if (sourceStatus.type() == std::filesystem::file_type::not_found)
return static_cast<int>(Fail(HasNonDirectoryPrefix(source) ? GuestEnotdir : GuestEnoent));
if (!std::filesystem::is_directory(sourceStatus)) return static_cast<int>(Fail(GuestEnotdir));
}
if (newTrailingSlash && sourceStatus.type() != std::filesystem::file_type::not_found &&
!std::filesystem::is_directory(sourceStatus))
return static_cast<int>(Fail(GuestEnotdir));
+116
View File
@@ -5,6 +5,7 @@
#include "prx/libc/include/Api.hpp"
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/RemoveTree.hpp"
#include "prx/libkernel/File/include/FileFlags.hpp"
#include "prx/libkernel/KernelErrors.hpp"
@@ -30,6 +31,7 @@
#endif
#ifndef _WIN32
#include <cerrno>
#include <sys/mman.h>
#include <unistd.h>
#endif
@@ -181,6 +183,119 @@ bool RemoveSymlink(const std::string& link) {
#endif
}
class GuardedPaths {
public:
GuardedPaths() {
#ifdef _WIN32
SYSTEM_INFO info{};
GetSystemInfo(&info);
page = info.dwPageSize;
memory = static_cast<char*>(VirtualAlloc(nullptr, 2 * page, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
Require(memory != nullptr);
DWORD previous = 0;
Require(VirtualProtect(memory + page, page, PAGE_NOACCESS, &previous) != 0);
#else
const long size = sysconf(_SC_PAGESIZE);
Require(size > 0);
page = static_cast<std::size_t>(size);
memory = static_cast<char*>(mmap(nullptr, 2 * page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
Require(memory != MAP_FAILED);
Require(mprotect(memory + page, page, PROT_NONE) == 0);
#endif
}
~GuardedPaths() {
#ifdef _WIN32
Require(VirtualFree(memory, 0, MEM_RELEASE) != 0);
#else
Require(munmap(memory, 2 * page) == 0);
#endif
}
char* Unreadable() const { return memory + page; }
char* Crossing() const { return memory + page - 3; }
char* Boundary() const { return memory + page - 2; }
private:
char* memory = nullptr;
std::size_t page = 0;
};
void CheckUnreadablePaths(const std::string& renameSource, int directory) {
constexpr int Fault = 14;
constexpr int KernelFault = static_cast<int>(0x8002000Eu);
GuardedPaths paths;
std::memcpy(paths.Crossing(), "abc", 3);
std::string copy;
Require(CopyGuestPath_nid_no_patch(paths.Unreadable(), copy) == Fault);
Require(CopyGuestPath_nid_no_patch(paths.Crossing(), copy) == Fault);
paths.Boundary()[0] = '.';
paths.Boundary()[1] = '\0';
Require(CopyGuestPath_nid_no_patch(paths.Boundary(), copy) == 0 && copy == ".");
const std::string maximum(1023, 'p');
std::array<char, 1024> unterminated{};
unterminated.fill('p');
Require(CopyGuestPath_nid_no_patch(maximum.c_str(), copy) == 0 && copy == maximum);
Require(CopyGuestPath_nid_no_patch(unterminated.data(), copy) == 63);
const auto posixFault = [&](auto call) {
*__error_nid_postfix() = 0;
Require(call() == -1 && Error() == Fault);
};
FileStat status{};
char linkBuffer[16]{};
posixFault([&] { return open_nid_postfix(paths.Unreadable(), SCE_KERNEL_O_RDONLY, 0); });
posixFault([&] { return chmod_nid_postfix(paths.Unreadable(), 0); });
posixFault([&] { return mkdir_nid_postfix(paths.Unreadable(), 0755); });
posixFault([&] { return rmdir_nid_postfix(paths.Unreadable()); });
posixFault([&] { return unlink_nid_postfix(paths.Unreadable()); });
posixFault([&] { return stat_nid_postfix(paths.Unreadable(), &status); });
posixFault([&] { return lstat_nid_postfix(paths.Unreadable(), &status); });
posixFault([&] { return truncate_nid_postfix(paths.Unreadable(), 0); });
posixFault([&] { return utimes_nid_postfix(paths.Unreadable(), nullptr); });
posixFault([&] { return lutimes_nid_postfix(paths.Unreadable(), nullptr); });
posixFault([&] { return readlink_nid_postfix(paths.Unreadable(), linkBuffer, sizeof(linkBuffer)); });
posixFault([&] { return pathconf_nid_postfix(paths.Unreadable(), 4); });
posixFault([&] { return link_nid_postfix(paths.Unreadable(), "unused-link"); });
posixFault([&] { return link_nid_postfix(renameSource.c_str(), paths.Unreadable()); });
posixFault([&] { return symlink_nid_postfix(paths.Unreadable(), "unused-symlink"); });
posixFault([&] { return symlink_nid_postfix("target", paths.Unreadable()); });
posixFault([&] { return openat_nid_postfix(-100, paths.Unreadable(), SCE_KERNEL_O_RDONLY); });
posixFault([&] { return mkdirat_nid_postfix(-100, paths.Unreadable(), 0755); });
posixFault([&] { return fchmodat_nid_postfix(-100, paths.Unreadable(), 0, 0); });
posixFault([&] { return lchmod_nid_postfix(paths.Unreadable(), 0); });
posixFault([&] { return fstatat_nid_postfix(-100, paths.Unreadable(), &status, 0); });
posixFault([&] { return unlinkat_nid_postfix(-100, paths.Unreadable(), 0); });
posixFault([&] { return readlinkat_nid_postfix(-100, paths.Unreadable(), linkBuffer, sizeof(linkBuffer)); });
posixFault([&] { return renameat_nid_postfix(-100, paths.Unreadable(), -100, "unused-rename"); });
posixFault([&] { return renameat_nid_postfix(-100, renameSource.c_str(), -100, paths.Unreadable()); });
posixFault([&] { return openat_nid_postfix(directory, paths.Unreadable(), SCE_KERNEL_O_RDONLY); });
posixFault([&] { return mkdirat_nid_postfix(directory, paths.Unreadable(), 0755); });
posixFault([&] { return fchmodat_nid_postfix(directory, paths.Unreadable(), 0, 0); });
posixFault([&] { return fstatat_nid_postfix(directory, paths.Unreadable(), &status, 0); });
posixFault([&] { return unlinkat_nid_postfix(directory, paths.Unreadable(), 0); });
posixFault([&] { return readlinkat_nid_postfix(directory, paths.Unreadable(), linkBuffer, sizeof(linkBuffer)); });
posixFault([&] { return renameat_nid_postfix(directory, paths.Unreadable(), directory, "unused-rename"); });
posixFault([&] { return renameat_nid_postfix(directory, "file.txt", directory, paths.Unreadable()); });
*__error_nid_postfix() = 0;
Require(renameat_nid_postfix(directory, "missing", directory, paths.Unreadable()) == -1 && Error() == 2);
*__error_nid_postfix() = 0;
Require(link_nid_postfix("missing", paths.Unreadable()) == -1 && Error() == 2);
posixFault([&] { return shm_open_nid_postfix(paths.Unreadable(), SCE_KERNEL_O_RDWR, 0600); });
posixFault([&] { return shm_unlink_nid_postfix(paths.Unreadable()); });
Require(sceKernelOpen(paths.Unreadable(), SCE_KERNEL_O_RDONLY, 0) == KernelFault);
Require(sceKernelStat(paths.Unreadable(), &status) == KernelFault);
Require(sceKernelMkdir(paths.Unreadable(), 0755) == KernelFault);
Require(sceKernelRmdir(paths.Unreadable()) == KernelFault);
Require(sceKernelUnlink(paths.Unreadable()) == KernelFault);
Require(sceKernelChmod_nid_postfix(paths.Unreadable(), 0) == KernelFault);
Require(sceKernelTruncate_nid_postfix(paths.Unreadable(), 0) == KernelFault);
Require(sceKernelUtimes_nid_postfix(paths.Unreadable(), nullptr) == KernelFault);
Require(sceKernelCheckReachability(paths.Unreadable()) == KernelFault);
Require(sceKernelRename(paths.Unreadable(), ".") == KernelFault);
Require(sceKernelRename(renameSource.c_str(), paths.Unreadable()) == KernelFault);
Require(!std::filesystem::exists("unused-link") && !std::filesystem::exists("unused-symlink") && !std::filesystem::exists("unused-rename"));
}
void CheckSymlinkTargetRestrictions(const std::filesystem::path& root) {
const auto created = (root / "rejected").string();
for (const auto& target : {std::string(".."), std::string("../x"), std::string("a/../b"), std::string("a/../../x"), std::string("/abs")})
@@ -737,6 +852,7 @@ int main() {
CheckSymlinkTargetRestrictions(root);
const int base = sceKernelOpen(root.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0);
Require(base >= 0);
CheckUnreadablePaths(file, base);
const int relative = openat_nid_postfix(base, "file.txt", SCE_KERNEL_O_RDONLY);
char byte = 0;
Require(relative >= 0 && sceKernelRead(relative, &byte, 1) == 1 && byte == 'x');