fix zip path traversal

this makes sure that the file is in the given root

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97 2026-02-15 10:22:37 +02:00
parent 437242169d
commit 56936cf485
No known key found for this signature in database
GPG key ID: 55EF5DA53DB36318
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; << target;
return false; 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; qWarning() << "Failed to extract file" << original_name << "to" << target_file_path;
return false; return false;
} }

View file

@ -23,7 +23,9 @@
#include <archive_entry.h> #include <archive_entry.h>
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
#include <QUrl>
#include <memory> #include <memory>
#include <optional>
namespace MMCZip { namespace MMCZip {
QStringList ArchiveReader::getFiles() 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) 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; auto entry = m_entry;
std::unique_ptr<archive_entry, decltype(&archive_entry_free)> entryClone(nullptr, &archive_entry_free); 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(); auto nameUtf8 = targetFileName.toUtf8();
archive_entry_set_pathname_utf8(entry, nameUtf8.constData()); 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) { if (archive_write_header(out, entry) < ARCHIVE_OK) {
qCritical() << "Failed to write header to entry:" << filename() << "-" << archive_error_string(out); qCritical() << "Failed to write header to entry:" << filename() << "-" << archive_error_string(out);
return false; return false;

View file

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

View file

@ -95,7 +95,7 @@ auto ExtractZipTask::extractZip() -> ZipResult
return false; 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)); result = ZipResult(tr("Failed to extract file %1 to %2").arg(original_name, target_file_path));
return false; return false;
} }

View file

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