Skip to content
Merged
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
68 changes: 68 additions & 0 deletions 3rdparty/interface/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,74 @@ bool Common::findLnfsPath(const QString &target, Compare func)
}


bool extractPathIsWithinTarget(const QString &extractRoot, const QString &absoluteDestPath)
{
const QFileInfo rootFi(extractRoot);
const QString rootCanon = rootFi.canonicalFilePath();
if (rootCanon.isEmpty()) {
return false;
}

const QString destAbs = QFileInfo(absoluteDestPath).absoluteFilePath();
QString path = destAbs;

while (true) {
QFileInfo fi(path);
if (fi.exists()) {
const QString canon = fi.canonicalFilePath();
if (canon.isEmpty()) {
return false;
}
if (!canon.startsWith(rootCanon + QDir::separator()) && canon != rootCanon) {
return false;
}
return true;
}
const QString parent = fi.path();
if (parent == path || parent.isEmpty()) {
break;
}
path = parent;
}
return rootFi.exists();
}

bool symlinkTargetIsWithinTarget(const QString &extractRoot, const QString &symlinkFilePath, const QString &symlinkTarget)
{
const QFileInfo rootFi(extractRoot);
const QString rootCanon = rootFi.canonicalFilePath();
if (rootCanon.isEmpty()) {
return false;
}

if (symlinkTarget.isEmpty()) {
return false;
}

const QString linkParent = QFileInfo(symlinkFilePath).path();
const QString resolved = QDir::cleanPath(QDir(linkParent).absoluteFilePath(symlinkTarget));
QString path = resolved;

while (true) {
QFileInfo fi(path);
if (fi.exists()) {
const QString canon = fi.canonicalFilePath();
if (canon.isEmpty()) {
return false;
}
return canon.startsWith(rootCanon + QDir::separator()) || canon == rootCanon;
}

const QString parent = fi.path();
if (parent == path || parent.isEmpty()) {
break;
}
path = parent;
}

return resolved.startsWith(rootCanon + QDir::separator()) || resolved == rootCanon;
}

bool IsMtpFileOrDirectory(QString path) noexcept {
const static QRegExp regexp("((/run/user/[0-9]+/gvfs/mtp:)|(/root/.gvfs/mtp:)).+");
return regexp.exactMatch(path);
Expand Down
12 changes: 11 additions & 1 deletion 3rdparty/interface/common.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2022 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

Expand Down Expand Up @@ -58,4 +58,14 @@ class Common: public QObject
*/
bool IsMtpFileOrDirectory(QString path) noexcept;

/**
* 解压安全:校验目标绝对路径在真实文件系统解析后仍位于解压根目录内(防止符号链接路径逃逸)。
*/
bool extractPathIsWithinTarget(const QString &extractRoot, const QString &absoluteDestPath);

/**
* 解压安全:ZIP 内符号链接目标解析后须位于解压根目录内。
*/
bool symlinkTargetIsWithinTarget(const QString &extractRoot, const QString &symlinkFilePath, const QString &symlinkTarget);

#endif
2 changes: 2 additions & 0 deletions 3rdparty/libarchive/libarchive/libarchiveplugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,8 @@ int LibarchivePlugin::extractionFlags() const
{
int result = ARCHIVE_EXTRACT_TIME;
result |= ARCHIVE_EXTRACT_SECURE_NODOTDOT;
result |= ARCHIVE_EXTRACT_SECURE_SYMLINKS;
result |= ARCHIVE_EXTRACT_SECURE_NOABSOLUTEPATHS;

// TODO: Don't use arksettings here
/*if ( ArkSettings::preservePerms() )
Expand Down
21 changes: 21 additions & 0 deletions 3rdparty/libminizipplugin/libminizipplugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,14 @@ ErrorType LibminizipPlugin::extractEntry(unzFile zipfile, unz_file_info file_inf
strFileName = strFileName.remove(0, options.strDestination.size());
}

while (strFileName.contains(QStringLiteral("../"))) {
qInfo() << "skipped ../ path component(s) in " << strFileName;
strFileName = strFileName.replace(QStringLiteral("../"), QString());
}
if (strFileName.contains(QLatin1Char('\\'))) {
strFileName = strFileName.replace(QLatin1Char('\\'), QDir::separator());
}

emit signalCurFileName(strFileName); // 发送当前正在解压的文件名

bool bIsDirectory = strFileName.endsWith(QDir::separator()); // 是否为文件夹
Expand All @@ -293,6 +301,19 @@ ErrorType LibminizipPlugin::extractEntry(unzFile zipfile, unz_file_info file_inf

// 解压完整文件名(含路径)
QString strDestFileName = options.strTargetPath + QDir::separator() + strFileName;

const QString cleanTargetPath = QDir::cleanPath(QDir(options.strTargetPath).absolutePath());
const QString cleanDestPath = QDir::cleanPath(QDir(strDestFileName).absolutePath());
if (!cleanDestPath.startsWith(cleanTargetPath + QDir::separator()) &&
cleanDestPath != cleanTargetPath) {
qInfo() << "Path traversal detected! Rejected path: " << strFileName;
return ET_FileWriteError;
}
if (!extractPathIsWithinTarget(options.strTargetPath, strDestFileName)) {
qInfo() << "Rejected path (symlink escape or out of root):" << strDestFileName;
return ET_FileWriteError;
}

QFile file(strDestFileName);

if (bIsDirectory) { // 文件夹
Expand Down
11 changes: 11 additions & 0 deletions 3rdparty/libzipplugin/libzipplugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,11 @@ ErrorType LibzipPlugin::extractEntry(zip_t *archive, zip_int64_t index, const Ex
return ET_FileWriteError;
}

if (!extractPathIsWithinTarget(options.strTargetPath, strDestFileName)) {
qInfo() << "Rejected path (symlink escape or out of root):" << strDestFileName;
return ET_FileWriteError;
}

QFile file(strDestFileName);

// Store parent mtime.
Expand Down Expand Up @@ -951,6 +956,12 @@ ErrorType LibzipPlugin::extractEntry(zip_t *archive, zip_int64_t index, const Ex
const auto readBytes = zip_fread(zipFile, buf, zip_uint64_t(READBYTES));
if (readBytes > 0) {
QString strBuf = QString(buf).toLocal8Bit();
if (!symlinkTargetIsWithinTarget(options.strTargetPath, strDestFileName, strBuf)) {
qInfo() << "Symlink target escapes extract root, rejected:" << strBuf;
zip_fclose(zipFile);
emit signalFileWriteErrorName(strBuf);
return ET_FileWriteError;
}
if (QFile::link(strBuf, strDestFileName)) {
qInfo() << "Symlink's created:" << buf << strFileName;
} else {
Expand Down
Loading