Fix PKG extraction path traversal

PKG entry names could contain traversal components and escape the
installation directory. Reject absolute and special components, then
require the resolved target to remain below the installation root
before creating or writing files.
This commit is contained in:
Acts1631
2026-09-10 18:32:18 +03:00
committed by Elad
parent 973bb520ac
commit 09bb101de1
+59 -9
View File
@@ -877,6 +877,25 @@ bool package_reader::fill_data(std::map<std::string, install_entry*>& 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<std::string, install_entry*>& 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<std::string, install_entry*>& 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,