Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 66 additions & 11 deletions rpcs3/Crypto/unpkg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ bool package_reader::read_header()
return false;
}

if (u64{umax} / sizeof(PKGEntry) < u64(m_header.file_count))
if (m_header.file_count > PKG_MAX_FILE_COUNT || u64(m_header.file_count) > u64(m_header.data_size) / sizeof(PKGEntry))
{
pkg_log.error("PKG file count is too large! (0x%x)", m_header.file_count);
pkg_log.error("PKG file count is invalid! (count=0x%x, data_size=0x%llx)", m_header.file_count, m_header.data_size);
return false;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -914,7 +933,51 @@ 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 == "..")
{
fmt::throw_exception("PKG entry path contains a parent directory component: '%s'", name);
}

if (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))
{
Expand Down Expand Up @@ -952,15 +1015,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,
Expand Down
1 change: 1 addition & 0 deletions rpcs3/Crypto/unpkg.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ enum : u32
{
PKG_HEADER_SIZE = 0xC0, // sizeof(pkg_header) + sizeof(pkg_unk_checksum)
PKG_HEADER_SIZE2 = 0x280,
PKG_MAX_FILE_COUNT = 1'000'000,
PKG_MAX_FILENAME_SIZE = 256,
};

Expand Down