#include "fs_common.hpp" #include "pxi.hpp" #include #include #include namespace HLE { CommonPath CommonPath::FromUtf16(std::u16string_view utf16_data) { while (!utf16_data.empty() && utf16_data.back() == 0) { utf16_data.remove_suffix(1); } std::wstring_convert,char16_t> conversion; return { Utf8PathType { conversion.to_bytes(utf16_data.begin(), utf16_data.end()) } }; } CommonPath CommonPath::FromUtf16(std::basic_string_view utf16_data) { while (!utf16_data.empty() && utf16_data.back() == 0) { utf16_data.remove_suffix(1); } std::vector char16_data(utf16_data.size()); std::copy(utf16_data.begin(), utf16_data.end(), char16_data.begin()); std::wstring_convert,char16_t> conversion; return { Utf8PathType { conversion.to_bytes(&*char16_data.begin(), &*char16_data.end()) } }; } CommonPath::CommonPath(OS::Thread& thread, uint32_t type, uint32_t num_bytes, const IPC::StaticBuffer& path) : CommonPath(type, num_bytes, [&,offset=0]() mutable { return thread.ReadMemory(path.addr + offset++); }) { } CommonPath::CommonPath(OS::Thread& thread, uint32_t type, uint32_t num_bytes, const PXI::PXIBuffer& path) : CommonPath(type, num_bytes, [&,offset=0]() mutable { return path.Read(thread, offset++); }) { } template CommonPath::CommonPath(uint32_t type, uint32_t num_bytes, ReadByte&& read) { switch (type) { case 1: // empty path { data = Utf8PathType { }; break; } case 2: // binary path { data = BinaryPathType { }; auto& path_data = std::get(data); if (num_bytes > max_length) { throw std::runtime_error(fmt::format("Path length {} exceeded maximum {}", num_bytes, path_data.capacity())); } ranges::generate_n(ranges::back_inserter(path_data), num_bytes, read); break; } case 3: // ASCII path { data = Utf8PathType { }; auto& path = std::get(data); path.reserve(num_bytes); ranges::generate_n(ranges::back_inserter(path), num_bytes, read); // Strip characters past the null terminator path.erase(ranges::find(path, '\0'), path.end()); break; } case 4: // UTF-16 path { if (num_bytes % 2) throw std::runtime_error("Expected path size for UTF16 to be a multiple of 2"); // Read in data as UTF-16 with host byte order std::u16string utf16_data; utf16_data.reserve(num_bytes / 2); auto ReadFromAddr = [&]() -> char16_t { uint16_t low = read(); uint32_t high = read(); return low | (high << uint16_t { 8 }); }; ranges::generate_n(ranges::back_inserter(utf16_data), num_bytes / 2, ReadFromAddr); // Strip characters past the null terminator utf16_data.erase(ranges::find(utf16_data, u'\0'), utf16_data.end()); *this = FromUtf16(utf16_data); break; } default: throw std::runtime_error(fmt::format("Unknown path type {}", type)); } } ValidatedHostPath PathValidator::ValidateAndGetSandboxedTreePath(const Utf8PathType& utf8_path) const { auto path = utf8_path.to_std_path().lexically_normal(); auto path_elements = std::distance(path.begin(), path.end()); if (path_elements == 0 || *path.begin() != "/") { test_mode ? throw IPC::IPCError { 0, 0xe0e046be } // TODO: Throw PathValidator errors instead : throw std::runtime_error("Path does not start with '/'"); } if (path_elements > 1 && *std::next(path.begin()) == "..") { test_mode ? throw IPC::IPCError { 0, 0xe0e046be } : throw std::runtime_error("Path escapes root directory"); } // const uint32_t max_filename_length = 0x10; // TODO: Where does this come from? Seems overly short for HostArchives const uint32_t max_filename_length = 0x100; if (std::prev(path.end())->u8string().size() > max_filename_length) { test_mode ? throw IPC::IPCError { 0, 0xe0e046c7 } : throw std::runtime_error("Filename too long"); } // Append path to the sandbox root and double-check that we won't leak out of it auto sandbox_root = GetSandboxHostRoot(); if (!sandbox_root.is_absolute()) { // TODO: Required for cfg // throw std::runtime_error("Sandbox root is not an absolute path: " + sandbox_root.string()); } sandbox_root = canonical(sandbox_root); auto new_path = sandbox_root; new_path += utf8_path; new_path = new_path.lexically_normal(); if (new_path.begin() != std::search(new_path.begin(), new_path.end(), sandbox_root.begin(), sandbox_root.end())) { throw std::runtime_error(fmt::format("Requested path leaked out of sandbox: Got path {}, expected a child of {}", new_path, sandbox_root)); } return ValidatedHostPath { std::move(new_path) }; } }