fix zip path traversal

this makes sure that the file is in the given root

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit 56936cf485)
This commit is contained in:
Trial97 2026-02-15 10:22:37 +02:00 committed by github-actions[bot]
parent 8788b7338a
commit 30daf048b2
5 changed files with 42 additions and 3 deletions

View file

@ -233,7 +233,7 @@ std::optional<QStringList> extractSubDir(ArchiveReader* zip, const QString& subd
<< target;
return false;
}
if (!f->writeFile(ext, target_file_path)) {
if (!f->writeFile(ext, target_file_path, target)) {
qWarning() << "Failed to extract file" << original_name << "to" << target_file_path;
return false;
}

View file

@ -23,7 +23,9 @@
#include <archive_entry.h>
#include <QDir>
#include <QFileInfo>
#include <QUrl>
#include <memory>
#include <optional>
namespace MMCZip {
QStringList ArchiveReader::getFiles()
@ -128,7 +130,37 @@ static int copy_data(struct archive* ar, struct archive* aw, bool notBlock = fal
}
}
bool willEscapeRoot(const QDir& root, archive_entry* entry)
{
const char* entryPathC = archive_entry_pathname(entry);
const char* linkTargetC = archive_entry_symlink(entry);
const char* hardlinkC = archive_entry_hardlink(entry);
if (!entryPathC || (!linkTargetC && !hardlinkC))
return false;
QString entryPath = QString::fromUtf8(entryPathC);
QString linkTarget = linkTargetC ? QString::fromUtf8(linkTargetC) : QString::fromUtf8(hardlinkC);
QString linkFullPath = root.filePath(entryPath);
auto rootDir = QUrl::fromLocalFile(root.absolutePath());
if (!rootDir.isParentOf(QUrl::fromLocalFile(linkFullPath)))
return true;
QDir linkDir = QFileInfo(linkFullPath).dir();
if (!QDir::isAbsolutePath(linkTarget)) {
linkTarget = (linkTargetC ? linkDir : root).filePath(linkTarget);
}
return !rootDir.isParentOf(QUrl::fromLocalFile(QDir::cleanPath(linkTarget)));
}
bool ArchiveReader::File::writeFile(archive* out, QString targetFileName, bool notBlock)
{
return writeFile(out, targetFileName, {}, notBlock);
};
bool ArchiveReader::File::writeFile(archive* out, QString targetFileName, std::optional<QDir> root, bool notBlock)
{
auto entry = m_entry;
std::unique_ptr<archive_entry, decltype(&archive_entry_free)> entryClone(nullptr, &archive_entry_free);
@ -138,6 +170,10 @@ bool ArchiveReader::File::writeFile(archive* out, QString targetFileName, bool n
auto nameUtf8 = targetFileName.toUtf8();
archive_entry_set_pathname_utf8(entry, nameUtf8.constData());
}
if (root.has_value() && willEscapeRoot(root.value(), entry)) {
qCritical() << "Failed to write header to entry:" << filename() << "-" << "file outside root";
return false;
}
if (archive_write_header(out, entry) < ARCHIVE_OK) {
qCritical() << "Failed to write header to entry:" << filename() << "-" << archive_error_string(out);
return false;

View file

@ -19,8 +19,10 @@
#include <QByteArray>
#include <QDateTime>
#include <QDir>
#include <QStringList>
#include <memory>
#include <optional>
struct archive;
struct archive_entry;
@ -49,6 +51,7 @@ class ArchiveReader {
QByteArray readAll(int* outStatus = nullptr);
bool skip();
bool writeFile(archive* out, QString targetFileName = "", bool notBlock = false);
bool writeFile(archive* out, QString targetFileName, std::optional<QDir> root, bool notBlock = false);
private:
int readNextHeader();

View file

@ -95,7 +95,7 @@ auto ExtractZipTask::extractZip() -> ZipResult
return false;
}
if (!f->writeFile(ext, target_file_path)) {
if (!f->writeFile(ext, target_file_path, target)) {
result = ZipResult(tr("Failed to extract file %1 to %2").arg(original_name, target_file_path));
return false;
}

View file

@ -53,7 +53,7 @@ static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibH
name = replaceSuffix(name, ".jnilib", ".dylib");
}
QString absFilePath = directory.absoluteFilePath(name);
return f->writeFile(ext, absFilePath);
return f->writeFile(ext, absFilePath, directory);
});
}