diff --git a/rpcs3/Crypto/unpkg.cpp b/rpcs3/Crypto/unpkg.cpp index c9a49348a5..bffa5ea500 100644 --- a/rpcs3/Crypto/unpkg.cpp +++ b/rpcs3/Crypto/unpkg.cpp @@ -877,6 +877,25 @@ bool package_reader::fill_data(std::map& all_instal return false; } + std::error_code path_ec; + auto install_path = std::filesystem::weakly_canonical(m_install_path, path_ec); + if (path_ec) + { + pkg_log.warning("Failed to canonicalize installation path '%s' (%s); falling back to lexical normalization.", m_install_path, path_ec.message()); + install_path = std::filesystem::path(m_install_path).lexically_normal(); + } + + if (install_path.empty()) + { + pkg_log.error("Failed to normalize installation path for '%s'", m_install_path); + return false; + } + + const auto is_inside_install_path = [&install_path](const std::filesystem::path& path) + { + return std::mismatch(install_path.begin(), install_path.end(), path.begin(), path.end()).first == install_path.end(); + }; + m_install_entries.clear(); m_bootable_file_path.clear(); m_entry_indexer = 0; @@ -914,7 +933,46 @@ bool package_reader::fill_data(std::map& all_instal std::string_view name = fmt::trim_back_sv(name_buf, "\0"sv); + const std::filesystem::path entry_path{name}; + if (entry_path.is_absolute()) + { + num_failures++; + pkg_log.error("PKG entry path is absolute: '%s'", name); + break; + } + + for (const auto& component : entry_path) + { + if (component == "." || component == "..") + { + num_failures++; + pkg_log.error("PKG entry path contains a special component: '%s'", name); + break; + } + } + + if (num_failures) + { + break; + } + std::string path = m_install_path + vfs::escape(name); + path_ec.clear(); + auto canonical_path = std::filesystem::weakly_canonical(path, path_ec); + if (path_ec) + { + pkg_log.warning("Failed to canonicalize package path '%s' (%s); falling back to lexical normalization.", path, path_ec.message()); + canonical_path = std::filesystem::path(path).lexically_normal(); + } + + if (canonical_path.empty() || !is_inside_install_path(canonical_path)) + { + num_failures++; + pkg_log.error("PKG entry path escapes installation directory: '%s'", name); + break; + } + + path = canonical_path.string(); if (entry.pad || (entry.type & ~PKG_FILE_ENTRY_KNOWN_BITS)) { @@ -952,15 +1010,7 @@ bool package_reader::fill_data(std::map& all_instal default: { // TODO: check for valid utf8 characters - const std::string true_path = std::filesystem::path(path).lexically_normal().string(); - if (true_path.empty()) - { - num_failures++; - pkg_log.error("Failed to normalize package path for '%s'", path); - break; - } - - auto map_ptr = &*all_install_entries.try_emplace(true_path).first; + auto map_ptr = &*all_install_entries.try_emplace(path).first; m_install_entries.push_back({ .weak_reference = map_ptr,