diff --git a/core/libs/prx/libSceAvPlayer/src/Source.cpp b/core/libs/prx/libSceAvPlayer/src/Source.cpp index d6baf7b3d..768358e69 100644 --- a/core/libs/prx/libSceAvPlayer/src/Source.cpp +++ b/core/libs/prx/libSceAvPlayer/src/Source.cpp @@ -3,6 +3,7 @@ #include "prx/libSceAvPlayer/include/AvPlayer.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/FilesystemError.hpp" #include #include @@ -243,7 +244,9 @@ public: format->pb = replacement->Context(); format->flags |= AVFMT_FLAG_CUSTOM_IO; } else { - const auto resolved = ResolvePath_nid_no_patch(path.c_str()).u8string(); + std::filesystem::path hostPath; + if (ResolvePathError_nid_no_patch(path.c_str(), hostPath) != 0) return false; + const auto resolved = hostPath.u8string(); url = "file:" + std::string(resolved.begin(), resolved.end()); } if (const int error = avformat_open_input(&format, replacement ? nullptr : url.c_str(), nullptr, nullptr); error < 0) { diff --git a/core/libs/prx/libSceFont/src/Open.cpp b/core/libs/prx/libSceFont/src/Open.cpp index ae87bf31b..ef118bd83 100644 --- a/core/libs/prx/libSceFont/src/Open.cpp +++ b/core/libs/prx/libSceFont/src/Open.cpp @@ -15,6 +15,7 @@ #include #include "prx/libSceFont/include/FontInternal.hpp" +#include "prx/libc/include/FilesystemError.hpp" namespace { @@ -280,7 +281,11 @@ int APS5_VABI sceFontOpenFontFile(FontLibrary library, const char* path, std::ui return SCE_FONT_ERROR_INVALID_LIBRARY; } if (!pFontHandle) return SCE_FONT_ERROR_INVALID_PARAMETER; - const std::filesystem::path hostPath = path ? ResolvePath_nid_no_patch(path) : std::filesystem::path{}; + std::filesystem::path hostPath; + if (path && ResolvePathError_nid_no_patch(path, hostPath) != 0) { + *pFontHandle = nullptr; + return SCE_FONT_ERROR_FS_OPEN_FAILED; + } const std::string hostPathString = hostPath.string(); std::uint32_t libraryLock = 0; const int beginRc = BeginOpen(library, pFontHandle, libraryLock); diff --git a/core/libs/prx/libc/include/FilesystemError.hpp b/core/libs/prx/libc/include/FilesystemError.hpp index e31bedd2a..4bec836c8 100644 --- a/core/libs/prx/libc/include/FilesystemError.hpp +++ b/core/libs/prx/libc/include/FilesystemError.hpp @@ -2,6 +2,7 @@ #define CORE_LIBS_PRX_LIBC_INCLUDE_FILESYSTEMERROR_HPP #include +#include "General.hpp" inline int FilesystemError(const std::error_code& error) { if (error == std::errc::no_such_file_or_directory) return 2; @@ -22,4 +23,14 @@ inline int FilesystemError(const std::error_code& error) { return 5; } +inline int ResolvePathError_nid_no_patch(const char* path, std::filesystem::path& resolved) { + try { + resolved = ResolvePath_nid_no_patch(path); + return 0; + } catch (const std::filesystem::filesystem_error& error) { + if (error.code() == std::errc::illegal_byte_sequence) return 2; + return FilesystemError(error.code()); + } +} + #endif diff --git a/core/libs/prx/libc/src/Directory.cpp b/core/libs/prx/libc/src/Directory.cpp index c3eefced4..27c7332b1 100644 --- a/core/libs/prx/libc/src/Directory.cpp +++ b/core/libs/prx/libc/src/Directory.cpp @@ -1,5 +1,6 @@ #include "prx/libc/include/GuestDirectory.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/FilesystemError.hpp" #include #include #include @@ -79,7 +80,7 @@ void* APS5_VABI opendir_nid_postfix(const char* path) { if (!*path) { errno = 2; return nullptr; } try { auto directory = std::make_unique(); - directory->path = ResolvePath_nid_no_patch(path); + if (const int error = ResolvePathError_nid_no_patch(path, directory->path)) { errno = error; return nullptr; } directory->native = OpenNative(directory->path); if (!directory->native) { errno = DirectoryError(errno); return nullptr; } return directory.release(); diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index 20527e876..d6e255918 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -13,6 +13,7 @@ #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/FilesystemError.hpp" #include "prx/libc/include/GuestArena.hpp" struct GuestFileMode { @@ -83,7 +84,15 @@ FileStream* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode const auto parsed = ParseFileMode(mode); if (!parsed) { errno = 22; return nullptr; } try { - const std::filesystem::path path = *filename ? ResolvePath_nid_no_patch(filename) : std::filesystem::path{}; + std::filesystem::path path; + if (*filename) { + if (const int error = ResolvePathError_nid_no_patch(filename, path)) { + stream->Close(); + if (stream->IsDynamic()) delete stream; + errno = error; + return nullptr; + } + } if (stream->Reopen(path, parsed->native.c_str())) { if (!path.empty() && parsed->writes) RecordWrittenPath_nid_no_patch(path); return stream; @@ -103,7 +112,8 @@ FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode) errno = EINVAL; return nullptr; } - const std::filesystem::path fpath = ResolvePath_nid_no_patch(filename); + std::filesystem::path fpath; + if (const int error = ResolvePathError_nid_no_patch(filename, fpath)) { errno = error; return nullptr; } const auto abs_path = fpath.string(); std::unique_ptr handle(OpenNative(fpath, parsed->native), std::fclose); if (!handle) { diff --git a/core/libs/prx/libc/src/General.cpp b/core/libs/prx/libc/src/General.cpp index 9ac3ec004..fefc33af4 100644 --- a/core/libs/prx/libc/src/General.cpp +++ b/core/libs/prx/libc/src/General.cpp @@ -119,13 +119,24 @@ std::filesystem::path HostPath(WorkingDirectory& state, const std::filesystem::p if (auto aliased = ResolveAlias(guest.relative_path().generic_string())) return *aliased; return ResolveHostPath(state.root, guest.relative_path()).make_preferred(); } -bool HostDirectory(WorkingDirectory& state, const std::filesystem::path& guest) { - try { - std::error_code error; - return std::filesystem::is_directory(HostPath(state, guest), error); - } catch (const std::filesystem::filesystem_error&) { - return false; +void RequireHostDirectory(WorkingDirectory& state, const std::filesystem::path& guest) { + const auto host = HostPath(state, guest); + std::error_code error; + const auto status = std::filesystem::status(host, error); + if (std::filesystem::is_directory(status)) return; + if (!error) error = std::make_error_code(std::filesystem::exists(status) ? std::errc::not_a_directory : std::errc::no_such_file_or_directory); +#ifdef _WIN32 + if (error == std::errc::no_such_file_or_directory) { + for (auto prefix = host.parent_path(); prefix.has_relative_path(); prefix = prefix.parent_path()) { + std::error_code prefixError; + const auto prefixStatus = std::filesystem::status(prefix, prefixError); + if (!std::filesystem::exists(prefixStatus)) continue; + if (!std::filesystem::is_directory(prefixStatus)) error = std::make_error_code(std::errc::not_a_directory); + break; + } } +#endif + throw std::filesystem::filesystem_error("Guest path component is not a directory", host, error); } std::filesystem::path Resolve(WorkingDirectory& state, const char* path) { std::string text(path); @@ -141,11 +152,7 @@ std::filesystem::path Resolve(WorkingDirectory& state, const char* path) { for (auto part = relative.begin(); part != relative.end(); ++part) { if (*part == "..") { const auto parent = guest.lexically_normal(); - if (parent.has_relative_path() && !HostDirectory(state, parent)) { - auto host = HostPath(state, parent); - for (; part != relative.end(); ++part) host /= *part; - return host.make_preferred(); - } + if (parent.has_relative_path()) RequireHostDirectory(state, parent); } guest /= *part; } diff --git a/core/libs/prx/libkernel/Apr/src/Apr.cpp b/core/libs/prx/libkernel/Apr/src/Apr.cpp index 8aa37a332..7df214ab0 100644 --- a/core/libs/prx/libkernel/Apr/src/Apr.cpp +++ b/core/libs/prx/libkernel/Apr/src/Apr.cpp @@ -2,6 +2,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/FilesystemError.hpp" #include "prx/libc/include/PreciseWait.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/Apr/include/AprCommandBuffer.hpp" @@ -107,7 +108,8 @@ int _fail(int guestErrno) { bool _resolve(const char* guestPath, std::uint32_t* id, std::uint64_t* size) { if (!guestPath) return false; - auto hostPath = ResolvePath_nid_no_patch(guestPath); + std::filesystem::path hostPath; + if (ResolvePathError_nid_no_patch(guestPath, hostPath) != 0) return false; std::uint64_t bytes = 0; if (!_fileSize(hostPath, bytes)) return false; std::lock_guard lock(g_filesLock); diff --git a/core/libs/prx/libkernel/File/src/Open.cpp b/core/libs/prx/libkernel/File/src/Open.cpp index ca7cbe5f1..c11dc6d81 100644 --- a/core/libs/prx/libkernel/File/src/Open.cpp +++ b/core/libs/prx/libkernel/File/src/Open.cpp @@ -1,6 +1,7 @@ #include "prx/libkernel/File/include/FileFlags.hpp" #include "prx/libkernel/File/include/NativeStat.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/FilesystemError.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/File/include/File.hpp" #include "prx/libkernel/File/include/DirectoryDescriptor.hpp" @@ -142,7 +143,8 @@ int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode) { const int fd = File::OpenRandomDevice(); return fd < 0 ? SceErrorFromErrno(errno) : fd; } - auto native = ResolvePath_nid_no_patch(path); + std::filesystem::path native; + if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error); #ifdef _WIN32 if ((flags & (SCE_KERNEL_O_DIRECTORY | SCE_KERNEL_O_CREAT)) == SCE_KERNEL_O_DIRECTORY) { std::error_code error; @@ -229,11 +231,7 @@ int APS5_VABI sceKernelStat(const char* path, FileStat* sb) { } if (*path == '\0') return SceErrorFromErrno(2); std::filesystem::path native; - try { - native = ResolvePath_nid_no_patch(path); - } catch (const std::filesystem::filesystem_error&) { - return SceErrorFromErrno(2); - } + if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error); std::error_code error; constexpr int GuestEnotdir = 20; const auto status = std::filesystem::status(native, error); @@ -252,7 +250,8 @@ int APS5_VABI sceKernelUnlink(const char* path) { throw std::invalid_argument(std::string(__func__) + ": path is null"); } if (*path == '\0') return SceErrorFromErrno(2); - auto native = ResolvePath_nid_no_patch(path); + std::filesystem::path native; + if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error); constexpr int GuestEperm = 1; std::error_code error; if (std::filesystem::is_directory(std::filesystem::symlink_status(native, error))) return SceErrorFromErrno(GuestEperm); diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index 255a3d9fe..d383ac3c8 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -4,6 +4,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/FilesystemError.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/File/include/File.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" @@ -436,7 +437,8 @@ extern "C" { int APS5_VABI chmod_nid_postfix(const char* path, int mode) { if (const int error = PathError(path)) return PosixFailure(error); - const auto native = ResolvePath_nid_no_patch(path); + 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)); return 0; } @@ -575,7 +577,8 @@ 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); - auto native = ResolvePath_nid_no_patch(path); + std::filesystem::path native; + if (const int error = ResolvePathError_nid_no_patch(path, native)) return PosixFailure(error); if (NativeMkdir(native, mode) != 0) { const int nativeError = errno; const bool blockedByFile = (nativeError == ENOENT || nativeError == EINVAL) && HasNonDirectoryPrefix(native); @@ -685,7 +688,9 @@ int APS5_VABI stat_nid_postfix(const char* path, FileStat* 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); - if (!File::FillLinkStat(ResolvePath_nid_no_patch(path), sb)) return PosixResult(SceErrorFromErrno(errno)); + 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)); return 0; } @@ -698,8 +703,10 @@ int APS5_VABI sceKernelCheckReachability(const char* path) { if (path == nullptr) return SCE_KERNEL_ERROR_EINVAL; if (*path == '\0') 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)) return SceKernelError(error); std::error_code error; - return std::filesystem::exists(ResolvePath_nid_no_patch(path), error) ? 0 : SCE_KERNEL_ERROR_ENOENT; + return std::filesystem::exists(native, error) ? 0 : SCE_KERNEL_ERROR_ENOENT; } int APS5_VABI sceKernelFstat(int d, FileStat* sb) { @@ -808,7 +815,8 @@ int APS5_VABI sceKernelMkdir(const char* path, uint16_t mode) { (void)mode; if (path == nullptr) throw std::invalid_argument("sceKernelMkdir: path is null"); if (!*path) return SceErrorFromErrno(GUEST_ENOENT); - auto native = ResolvePath_nid_no_patch(path); + 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(); std::error_code error; if (std::filesystem::exists(native, error)) return SceErrorFromErrno(GUEST_EEXIST); @@ -1001,10 +1009,12 @@ int64_t APS5_VABI pwritev_nid_postfix(int d, const KernelIovec* iov, int iovcnt, int APS5_VABI sceKernelRename(const char* from, const char* to) { if (from == nullptr || to == nullptr) throw std::invalid_argument("sceKernelRename: path is null"); if (*from == '\0' || *to == '\0') return SceErrorFromErrno(GUEST_ENOENT); - const auto source = ResolvePath_nid_no_patch(from); + std::filesystem::path source; + if (const int error = ResolvePathError_nid_no_patch(from, source)) return SceKernelError(error); std::error_code error; if (!std::filesystem::exists(source, error)) return SceErrorFromErrno(GUEST_ENOENT); - const auto destination = ResolvePath_nid_no_patch(to); + std::filesystem::path destination; + if (const int result = ResolvePathError_nid_no_patch(to, destination)) return SceKernelError(result); std::filesystem::rename(source, destination, error); if (error) return SceErrorFromErrno(GUEST_EIO); RecordWrittenPath_nid_no_patch(source); @@ -1015,7 +1025,8 @@ int APS5_VABI sceKernelRename(const char* from, const char* to) { int APS5_VABI sceKernelRmdir(const char* path) { if (path == nullptr) throw std::invalid_argument("sceKernelRmdir: path is null"); if (*path == '\0') return SceErrorFromErrno(GUEST_ENOENT); - const auto native = ResolvePath_nid_no_patch(path); + std::filesystem::path native; + if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error); std::error_code error; if (!std::filesystem::is_directory(native, error)) { const bool blockedByFile = HasNonDirectoryPrefix(native) || std::filesystem::exists(WithoutTrailingSeparator(native), error); @@ -1040,7 +1051,8 @@ 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); - const auto native = ResolvePath_nid_no_patch(path); + 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); return 0; } @@ -1059,7 +1071,8 @@ int APS5_VABI sceKernelTruncate_nid_postfix(const char* path, std::int64_t lengt if (path == nullptr) throw std::invalid_argument("sceKernelTruncate: path is null"); if (*path == '\0') return SceErrorFromErrno(GUEST_ENOENT); if (length < 0) return SceErrorFromErrno(GUEST_EINVAL); - const auto native = ResolvePath_nid_no_patch(path); + std::filesystem::path native; + if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error); std::error_code error; const auto status = std::filesystem::status(native, error); if (!std::filesystem::exists(status)) return SceErrorFromErrno(GUEST_ENOENT); @@ -1082,7 +1095,8 @@ int APS5_VABI sceKernelUtimes_nid_postfix(const char* path, const KernelTimeval* if (times[i].tv_usec < 0 || times[i].tv_usec >= 1000000) return SCE_KERNEL_ERROR_EINVAL; } } - const auto native = ResolvePath_nid_no_patch(path); + 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); return 0; } diff --git a/core/libs/prx/libkernel_sys/Export.cpp b/core/libs/prx/libkernel_sys/Export.cpp index f477e9aa2..df4d2e486 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -15,6 +15,7 @@ #endif #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/FilesystemError.hpp" #include "prx/libkernel/File/include/DirectoryDescriptor.hpp" #ifdef _WIN32 #ifndef NOMINMAX @@ -75,7 +76,8 @@ std::int64_t APS5_VABI readlink_nid_postfix(const char* path, char* buffer, std: if (path == nullptr) return Fail(GuestEfault); if (*path == '\0') return Fail(GuestEnoent); if (size > static_cast(std::numeric_limits::max())) return Fail(GuestEinval); - const auto native = ResolvePath_nid_no_patch(path); + std::filesystem::path native; + if (const int error = ResolvePathError_nid_no_patch(path, native)) return Fail(error); #ifdef _WIN32 const DWORD attributes = GetFileAttributesW(native.wstring().c_str()); if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { @@ -99,8 +101,9 @@ std::int64_t APS5_VABI readlink_nid_postfix(const char* path, char* buffer, std: int APS5_VABI link_nid_postfix(const char* existing, const char* created) { if (existing == nullptr || created == nullptr) return static_cast(Fail(GuestEfault)); if (*existing == '\0' || *created == '\0') return static_cast(Fail(GuestEnoent)); - const auto source = ResolvePath_nid_no_patch(existing); - const auto destination = ResolvePath_nid_no_patch(created); + std::filesystem::path source, destination; + if (const int error = ResolvePathError_nid_no_patch(existing, source)) return static_cast(Fail(error)); + if (const int error = ResolvePathError_nid_no_patch(created, destination)) return static_cast(Fail(error)); std::error_code error; const auto status = std::filesystem::symlink_status(source, error); if (!std::filesystem::exists(status)) return static_cast(Fail(GuestEnoent)); diff --git a/core/libs/tests/GuestAmpr.cpp b/core/libs/tests/GuestAmpr.cpp index 43b223fac..b0d349ad2 100644 --- a/core/libs/tests/GuestAmpr.cpp +++ b/core/libs/tests/GuestAmpr.cpp @@ -576,6 +576,10 @@ void TestResolveFailures() { Require(sceKernelAprResolveFilepathsToIdsAndFileSizesForEach(paths, 2, ids, sizes, results) == 1); Require(results[0] == 0 && results[1] == static_cast(0x80020002)); Require(ids[1] == 0xffffffffu && sizes[0] == 4 && sizes[1] == 0); + for (const char* invalid : {"ampr_resolve_missing.bin/../ampr_resolve_existing.bin", "ampr_resolve_existing.bin/../ampr_resolve_existing.bin"}) { + Require(sceKernelAprResolveFilepathsToIds(&invalid, 1, ids, &failed) == -1); + Require(*__error_nid_postfix() == 2 && failed == 0 && ids[0] == 0xffffffffu); + } std::remove(paths[0]); } diff --git a/core/libs/tests/GuestAvPlayer.cpp b/core/libs/tests/GuestAvPlayer.cpp index dad55fead..d6ca10995 100644 --- a/core/libs/tests/GuestAvPlayer.cpp +++ b/core/libs/tests/GuestAvPlayer.cpp @@ -723,6 +723,8 @@ void TestPlayback() { Check(sceAvPlayerStreamCount(player) == OperationFailed, "stream count without a source"); Check(!sceAvPlayerIsActive(player), "active without a source"); Check(sceAvPlayerAddSource(player, "/app0/missing.mp4") == OperationFailed, "missing file accepted"); + Check(sceAvPlayerAddSource(player, "/app0/missing/../avplayer.mp4") == OperationFailed, "missing parent accepted"); + Check(sceAvPlayerAddSource(player, "/app0/avplayer.mp4/../avplayer.mp4") == OperationFailed, "file parent accepted"); Check(sceAvPlayerAddSource(player, "/app0/avplayer.mp4") == 0, "add source failed"); Check(sceAvPlayerAddSource(player, "/app0/avplayer.mp4") == OperationFailed, "second source accepted"); Check(WaitFor([&] { return events.Seen(EventReady); }), "ready event missing"); diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index e808b0acd..5474dee06 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -1,5 +1,8 @@ #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/FileStream.hpp" +#include "prx/libkernel/File/include/File.hpp" #include #include #include @@ -51,6 +54,13 @@ int APS5_VABI sceKernelUnlink(const char*); int APS5_VABI sceKernelRename(const char*, const char*); int APS5_VABI sceKernelRmdir(const char*); int APS5_VABI sceKernelMkdir(const char*, unsigned short); +int APS5_VABI sceKernelRename(const char*, const char*); +int APS5_VABI sceKernelCheckReachability(const char*); +int APS5_VABI access_nid_postfix(const char*, int); +int APS5_VABI chdir_nid_postfix(const char*); +void* APS5_VABI opendir_nid_postfix(const char*); +FileStream* APS5_VABI fopen_nid_postfix(const char*, const char*); +FileStream* APS5_VABI freopen_nid_postfix(const char*, const char*, FileStream*); int* APS5_VABI __error_nid_postfix(); int APS5_VABI pipe_nid_postfix(int*); std::int64_t APS5_VABI read_nid_postfix(int, void*, std::size_t); @@ -76,7 +86,91 @@ static void Check(bool value, int line) { } } #define Require(value) Check((value), __LINE__) + +static void TestParentTraversal() { + const auto root = std::filesystem::path("anyps5-parent-test-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + Require(std::filesystem::create_directories(root / "Existing")); + const auto present = (root / "present.txt").generic_string(); + { std::ofstream file(present); file << "preserved"; } + const auto directory = (root / "victim").generic_string(); + Require(std::filesystem::create_directory(directory)); + const auto checkInvalid = [&](const std::string& prefix, int error) { + const auto path = prefix + "/present.txt"; + const auto missingLeaf = prefix + "/new.txt"; + const int kernelError = static_cast(0x80020000u | static_cast(error)); + FileStat status{}; + Require(stat_nid_postfix(path.c_str(), &status) == -1 && *__error_nid_postfix() == error); + Require(lstat_nid_postfix(path.c_str(), &status) == -1 && *__error_nid_postfix() == error); + Require(sceKernelStat(path.c_str(), &status) == kernelError); + Require(open_nid_postfix(path.c_str(), 0, 0) == -1 && *__error_nid_postfix() == error); + Require(sceKernelOpen(path.c_str(), 0x601, 0600) == kernelError); + Require(sceKernelOpen(missingLeaf.c_str(), 0x601, 0600) == kernelError); + Require(access_nid_postfix(path.c_str(), 0) == -1 && *__error_nid_postfix() == error); + Require(sceKernelCheckReachability(path.c_str()) == kernelError); + Require(chmod_nid_postfix(path.c_str(), 0400) == -1 && *__error_nid_postfix() == error); + Require(sceKernelChmod_nid_postfix(path.c_str(), 0400) == kernelError); + Require(sceKernelTruncate_nid_postfix(path.c_str(), 0) == kernelError); + Require(sceKernelUtimes_nid_postfix(path.c_str(), nullptr) == kernelError); + Require(unlink_nid_postfix(path.c_str()) == -1 && *__error_nid_postfix() == error); + Require(sceKernelUnlink(path.c_str()) == kernelError); + Require(remove_nid_postfix(path.c_str()) == -1 && *__error_nid_postfix() == error); + Require(rename_nid_postfix(path.c_str(), missingLeaf.c_str()) == -1 && *__error_nid_postfix() == error); + Require(rename_nid_postfix(present.c_str(), missingLeaf.c_str()) == -1 && *__error_nid_postfix() == error); + Require(sceKernelRename(path.c_str(), missingLeaf.c_str()) == kernelError); + Require(sceKernelRename(present.c_str(), missingLeaf.c_str()) == kernelError); + const auto victim = prefix + "/victim"; + Require(rmdir_nid_postfix(victim.c_str()) == -1 && *__error_nid_postfix() == error); + Require(sceKernelRmdir(victim.c_str()) == kernelError); + Require(remove_nid_postfix(victim.c_str()) == -1 && *__error_nid_postfix() == error); + Require(mkdir_nid_postfix(missingLeaf.c_str(), 0700) == -1 && *__error_nid_postfix() == error); + Require(sceKernelMkdir(missingLeaf.c_str(), 0700) == kernelError); + Require(chdir_nid_postfix(prefix.c_str()) == -1 && *__error_nid_postfix() == error); + Require(opendir_nid_postfix(prefix.c_str()) == nullptr && *__error_nid_postfix() == error); + Require(fopen_nid_postfix(path.c_str(), "wb") == nullptr && *__error_nid_postfix() == error); + auto* stream = fopen_nid_postfix(present.c_str(), "rb"); + Require(stream != nullptr); + const int descriptor = stream->GuestState().descriptor; + Require(freopen_nid_postfix(path.c_str(), "wb", stream) == nullptr && *__error_nid_postfix() == error); + Require(!DescriptorIsOpen_nid_no_patch(descriptor)); + std::ifstream file(present); + std::string contents; + std::getline(file, contents); + Require(contents == "preserved" && std::filesystem::is_directory(directory)); + Require(!std::filesystem::exists(root / "new.txt")); + }; + checkInvalid(root.generic_string() + "/missing/..", 2); + checkInvalid(root.generic_string() + "/present.txt/..", 20); + checkInvalid(root.generic_string() + "/present.txt/child/../..", 20); + checkInvalid("/" + root.generic_string() + "/missing/..", 2); + checkInvalid(root.generic_string() + "/missing/../../" + root.generic_string(), 2); + AddPathAlias_nid_no_patch("parent-test-mount", std::filesystem::absolute(root).string().c_str()); + checkInvalid("/PARENT-TEST-MOUNT/missing/..", 2); + checkInvalid("/parent-test-mount/present.txt/..", 20); + FileStat status{}; + Require(sceKernelStat("/parent-test-mount/existing/../present.txt", &status) == 0 && status.st_size == 9); + RemovePathAlias_nid_no_patch("parent-test-mount"); + std::error_code linkError; + std::filesystem::create_directory_symlink("Existing", root / "directory-link", linkError); + if (!linkError) { + Require(sceKernelStat((root.generic_string() + "/directory-link/../present.txt").c_str(), &status) == 0 && status.st_size == 9); + std::filesystem::create_symlink("present.txt", root / "file-link"); + std::filesystem::create_directory_symlink("missing", root / "dangling-link"); + checkInvalid(root.generic_string() + "/file-link/..", 20); + checkInvalid(root.generic_string() + "/dangling-link/..", 2); + } + Require(chdir_nid_postfix(root.generic_string().c_str()) == 0); + Require(sceKernelStat("missing/../present.txt", &status) == static_cast(0x80020002u)); + Require(sceKernelStat("present.txt/..", &status) == static_cast(0x80020014u)); + Require(sceKernelStat("EXISTING/../present.txt", &status) == 0 && status.st_size == 9); + const int created = sceKernelOpen("Existing/../new.txt", 0x601, 0600); + Require(created >= 0 && sceKernelClose(created) == 0); + Require(chdir_nid_postfix("/") == 0); + std::filesystem::remove_all(root); +} + int main() { + TestParentTraversal(); Require(chmod_nid_postfix(nullptr, 0600) == -1 && *__error_nid_postfix() == 14); Require(sceKernelChmod_nid_postfix(nullptr, 0600) == static_cast(0x8002000eu)); Require(chmod_nid_postfix("", 0600) == -1 && *__error_nid_postfix() == 2); diff --git a/core/libs/tests/GuestFont.cpp b/core/libs/tests/GuestFont.cpp index f87745e78..fccc8be80 100644 --- a/core/libs/tests/GuestFont.cpp +++ b/core/libs/tests/GuestFont.cpp @@ -25,6 +25,7 @@ int APS5_VABI sceFontSupportSystemFonts(FontLibrary); int APS5_VABI sceFontSupportExternalFonts(FontLibrary, std::uint32_t, std::uint32_t); int APS5_VABI sceFontOpenFontSet(FontLibrary, std::uint32_t, std::uint32_t, const FontOpenDetail*, FontHandle*); int APS5_VABI sceFontOpenFontMemory(FontLibrary, const void*, std::uint32_t, const FontOpenDetail*, FontHandle*); +int APS5_VABI sceFontOpenFontFile(FontLibrary, const char*, std::uint32_t, const FontOpenDetail*, FontHandle*); int APS5_VABI sceFontCloseFont(FontHandle); int APS5_VABI sceFontSetScriptLanguage(FontHandle, int, int); int APS5_VABI sceFontGetScriptLanguage(FontHandle, int, int*); @@ -202,6 +203,8 @@ int main() { Require(sceFontOpenFontMemory(library, notAFont, sizeof(notAFont), nullptr, &font) == SCE_FONT_ERROR_NO_SUPPORT_FUNCTION && font == nullptr); Require(sceFontSupportExternalFonts(library, 4, 0x52) == SCE_FONT_OK); Require(sceFontSupportExternalFonts(library, 4, 0x52) == SCE_FONT_ERROR_ALREADY_SPECIFIED); + font = reinterpret_cast(&memory); + Require(sceFontOpenFontFile(library, "anyps5-missing-font-parent/../font.ttf", 1, nullptr, &font) == SCE_FONT_ERROR_FS_OPEN_FAILED && font == nullptr); Require(sceFontOpenFontMemory(library, nullptr, 0, nullptr, &font) == SCE_FONT_ERROR_INVALID_PARAMETER && font == nullptr); Require(sceFontOpenFontMemory(library, notAFont, sizeof(notAFont), nullptr, &font) == SCE_FONT_ERROR_NO_SUPPORT_FORMAT && font == nullptr); diff --git a/core/libs/tests/GuestKernelSys.cpp b/core/libs/tests/GuestKernelSys.cpp index 38ea7bb25..a4e869020 100644 --- a/core/libs/tests/GuestKernelSys.cpp +++ b/core/libs/tests/GuestKernelSys.cpp @@ -67,6 +67,16 @@ int main() { Require(close_nid_postfix(descriptor) == 0); Require(fstatfs_nid_postfix(-1, viaPublic) == -1 && Error() == 9); char buffer[64]; + for (const auto& component : {std::string("missing"), std::string("file.txt")}) { + const auto prefix = root.generic_string() + "/" + component + "/../"; + const auto invalid = prefix + "file.txt"; + const auto created = prefix + "new.txt"; + const int expected = component == "missing" ? 2 : 20; + Require(readlink_nid_postfix(invalid.c_str(), buffer, sizeof(buffer)) == -1 && Error() == expected); + Require(link_nid_postfix(invalid.c_str(), created.c_str()) == -1 && Error() == expected); + Require(link_nid_postfix(file.c_str(), created.c_str()) == -1 && Error() == expected); + Require(std::filesystem::hard_link_count(file) == 1 && !std::filesystem::exists(root / "new.txt")); + } Require(readlink_nid_postfix(file.c_str(), buffer, sizeof(buffer)) == -1 && Error() == 22); Require(readlink_nid_postfix(root.string().c_str(), buffer, sizeof(buffer)) == -1 && Error() == 22); Require(readlink_nid_postfix((root / "missing").string().c_str(), buffer, sizeof(buffer)) == -1 && Error() == 2); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 0c72b62a7..75f825b6c 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -571,7 +571,7 @@ Throughout the project, every function at every stage either **does exactly what - [Running application ID](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - `sceSystemServiceGetAppIdOfRunningBigApp` reports 1 as the running title's app ID. The value is AnyPS5's own: the console's shell assigns app IDs and their values are not known here, so other calls that take an app ID must use the same one. `sceSystemServiceKillApp` with that ID and the arguments -1, 0 and 0, which [SwanStationPS5](https://github.com/darkxex/SwanStationPS5/blob/4cdc981d1a64ffb7f2af73f7a27a9e91a3cb5eb4/src/main.c#L1050-L1058) passes to close itself and [ps5-payload-dev's hbldr](https://github.com/ps5-payload-dev/websrv/blob/46374edee6627885abac717de24da7a1d73af6f1/src/ps5/hbldr.c#L425-L435) passes to close the running game, ends the title as its own `exit(0)` would: the guest's shutdown and `atexit` handlers run and the process exits with status 0. On the console the call returns and the shell then ends the title, and whether those handlers run there is unknown; here there is no shell, so the call does not return. Another app ID or other arguments throw. - [sceNpCheckNpAvailability](../../core/libs/prx/libSceNpManager/Export.cpp) (libSceNpManager) - the `(req_id, online_id)` signature and the errors follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0fe263a4760dfbfa973366890061749b4af0de97/src/core/libraries/np/np_manager.cpp) with no user signed in to PSN: a null online id is `SCE_NP_ERROR_INVALID_ARGUMENT`, and any other online id matches no user, `SCE_NP_ERROR_USER_NOT_FOUND`; the request id is not checked, as in the other NpManager requests - [Temporary app-content storage](../../core/libs/prx/libSceAppContent/Export.cpp) tracks one `/temp0` mount, preserves its files on unmount, and rejects new path resolutions until remount. Mount, format, space-query and unmount errors follow [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/sys/sys_path.pas); the pointer ABI is also declared by [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/AppContent/AppContent.cs). Existing open host descriptors remain usable after unmount; console behaviour with open files is unverified. Host filesystem failures still throw. -- [Guest path resolution](../../core/libs/prx/libc/src/General.cpp) falls back to ASCII case-insensitive component lookup, following [shadPS4's host filesystem](https://github.com/shadps4-emu/shadPS4/blob/945dbc3cc3eee80ac3e053b438502ed936fa6bb2/src/core/file_sys/backends/host_fs.cpp). PPSA03747 requests `Skyrim.INI`, `Data/Video` and lowercase archive/plugin names that differ from the dump's filenames. Exact host names take precedence; multiple fallback matches throw instead of selecting an arbitrary file. Existing parent names are resolved when creating a new file, and guest mount aliases use the same ASCII comparison. Results are not cached, so renames and newly created files remain visible. Unicode case folding and PS5 behaviour for case-colliding host entries are unverified; host roots are used as supplied. +- [Guest path resolution](../../core/libs/prx/libc/src/General.cpp) falls back to ASCII case-insensitive component lookup, following [shadPS4's host filesystem](https://github.com/shadps4-emu/shadPS4/blob/945dbc3cc3eee80ac3e053b438502ed936fa6bb2/src/core/file_sys/backends/host_fs.cpp). PPSA03747 requests `Skyrim.INI`, `Data/Video` and lowercase archive/plugin names that differ from the dump's filenames. Exact host names take precedence; multiple fallback matches throw instead of selecting an arbitrary file. Existing parent names are resolved when creating a new file, and guest mount aliases use the same ASCII comparison. Results are not cached, so renames and newly created files remain visible. Unicode case folding and PS5 behaviour for case-colliding host entries are unverified; host roots are used as supplied. Explicit native Windows drive and UNC paths bypass guest parent-component validation. - [localtime, mktime](../../core/libs/prx/libc/src/Time.cpp) (libc) on Windows convert through the CRT's `localtime_s` and `mktime`, which accept about 1970 to January 3001 UTC and apply the CRT's daylight-saving rule; outside that range `localtime` returns null and `mktime` returns -1, where the console converts. `gmtime` converts every time whose year fits an `int` on both hosts. - [strftime](../../core/libs/prx/libc/src/Time.cpp) (libc) expands FreeBSD's `%k`, `%l`, `%s`, `%v` and `%+` itself, ignoring a leading `E` or `O` as FreeBSD does, and passes every other conversion to the host's `strftime`. On Windows the conversions the CRT lacks (`%P` and the `%-`, `%_` and `%0` padding flags) still produce nothing, and the padding flags are not supported on the five new conversions at all (`%-k`, `%_k` and `%0k` go to the host), an unknown conversion, a doubled `E` or `O` modifier (`%EE`) or a trailing lone `%` returns 0 where FreeBSD prints `%` and the character, and a standalone `%z` or `%Z` uses the host's zone instead of the broken-down time's `tm_zone`. `%s` follows FreeBSD 9 and 11 and is the local-time `mktime` of the broken-down time (current FreeBSD uses `tm_gmtoff`), which is -1 on Windows outside the range above. `%+` is the C locale's `%a %b %e %H:%M:%S %Z %Y` with `%Z` taken from `tm_zone`, or from `tzname[tm_isdst != 0]` when it is null. - [LINE_STIPPLE_TEX](../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp) (recompiler) reads as 0: line stipple is not emulated. RADV enables the input only as the required pixel input of a shader that reads no interpolants, because it initializes the fewest VGPRs ([Mesa](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/vulkan/radv_shader.c) `radv_compute_spi_ps_input`), and never reads it