Skip to content

fix: Fix path traversal vulnerability in zip extraction (bug #232873)#332

Merged
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
LiHua000:master
Dec 9, 2025
Merged

fix: Fix path traversal vulnerability in zip extraction (bug #232873)#332
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
LiHua000:master

Conversation

@LiHua000
Copy link
Copy Markdown
Contributor

@LiHua000 LiHua000 commented Dec 8, 2025

  • Replace single-pass "../" removal with loop to remove all occurrences
  • Add final path validation to ensure extracted files stay within target directory

Log: fix CITIVD

Bug: https://pms.uniontech.com/bug-view-342883.html

Summary by Sourcery

Harden zip extraction against path traversal attacks by sanitizing entry names and validating destination paths.

Bug Fixes:

  • Ensure all "../" components are removed from zip entry paths before extraction to prevent directory traversal.
  • Reject extraction when the computed destination path resolves outside the intended target directory.

- Replace single-pass "../" removal with loop to remove all occurrences
- Add final path validation to ensure extracted files stay within target directory

Log: fix CITIVD

Bug: https://pms.uniontech.com/bug-view-342883.html
@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

我来对这段代码修改进行审查:

  1. 安全性改进:
  • 原代码只检查一次"../"的存在,新代码使用while循环确保所有的"../"都被移除,这更好地防止了路径遍历攻击。
  • 新增了额外的安全检查,通过QDir::cleanPath()和absolutePath()规范化路径,并确保解压路径在目标目录内,这是很好的安全措施。
  1. 代码质量改进:
  • 注释更加清晰,明确说明了修改的目的(防止路径遍历攻击)。
  • 代码结构更加清晰,安全检查逻辑集中在一起。
  1. 潜在改进建议:
  • 路径清理部分可以考虑使用QDir::cleanPath()一次性处理所有路径相关的问题,而不是手动替换"../"。
  • 可以考虑添加对符号链接的检查,因为符号链接也可能导致路径遍历攻击。
  1. 性能考虑:
  • while循环可能会影响性能,但考虑到安全性的重要性,这是可接受的。
  • 路径规范化操作会增加一些开销,但这是必要的安全措施。
  1. 其他建议:
  • 可以考虑将路径验证逻辑提取为一个单独的函数,提高代码复用性和可维护性。
  • 建议添加更多的错误日志,记录被拒绝的具体原因。

总体来说,这个修改显著提高了代码的安全性,有效防止了路径遍历攻击。代码质量也有所提升,注释更加清晰。虽然可能会带来轻微的性能开销,但考虑到安全性的重要性,这是值得的。建议在后续版本中考虑上述改进建议,进一步完善代码。

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Dec 8, 2025

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Strengthens zip extraction security by robustly stripping "../" path traversal components from entry names and enforcing a final filesystem-level containment check to ensure extracted files stay within the configured target directory.

Sequence diagram for secure zip entry extraction with path traversal protection

sequenceDiagram
    participant Caller
    participant LibzipPlugin
    participant QDir
    participant QFile

    Caller->>LibzipPlugin: extractEntry(archive, index, options)
    LibzipPlugin->>LibzipPlugin: trans2uft8(statBuffer.name, m_mapFileCode[index])
    LibzipPlugin->>LibzipPlugin: strFileName = result
    loop Remove all ../ components
        LibzipPlugin->>LibzipPlugin: while strFileName.contains(../)
        LibzipPlugin->>LibzipPlugin: strFileName.replace(../, empty)
    end
    LibzipPlugin->>LibzipPlugin: strDestFileName = options.strTargetPath + separator + strFileName

    LibzipPlugin->>QDir: QDir(options.strTargetPath).absolutePath()
    QDir-->>LibzipPlugin: targetAbsPath
    LibzipPlugin->>LibzipPlugin: cleanTargetPath = QDir::cleanPath(targetAbsPath)

    LibzipPlugin->>QDir: QDir(strDestFileName).absolutePath()
    QDir-->>LibzipPlugin: destAbsPath
    LibzipPlugin->>LibzipPlugin: cleanDestPath = QDir::cleanPath(destAbsPath)

    alt cleanDestPath outside cleanTargetPath
        LibzipPlugin-->>Caller: return ET_FileWriteError
    else cleanDestPath inside cleanTargetPath
        LibzipPlugin->>QFile: QFile(strDestFileName)
        QFile-->>LibzipPlugin: file handle
        LibzipPlugin-->>Caller: return extraction result
    end
Loading

Class diagram for LibzipPlugin extractEntry security enhancements

classDiagram
    class LibzipPlugin {
        - CommonType m_common
        - MapType m_mapFileCode
        + ErrorType extractEntry(zip_t* archive, zip_int64_t index, const ExtractOptions& options)
    }

    class QDir {
        + QDir(QString path)
        + QString absolutePath()
        + static QString cleanPath(QString path)
        + static QString separator()
    }

    class QFile {
        + QFile(QString fileName)
        + bool open()
        + void close()
    }

    class ExtractOptions {
        + QString strTargetPath
    }

    LibzipPlugin --> QDir : uses
    LibzipPlugin --> QFile : uses
    LibzipPlugin --> ExtractOptions : uses
    LibzipPlugin --> CommonType : has
    LibzipPlugin --> MapType : has
Loading

File-Level Changes

Change Details Files
Harden zip extraction against path traversal by normalizing entry names and validating that the final destination path is confined to the target directory before writing files.
  • Replace single-pass "../" substring removal on archive entry names with a loop that removes all occurrences to avoid partial sanitization
  • Build the destination path from the sanitized entry name and target directory as before
  • Normalize both target directory and destination path to absolute, cleaned forms using QDir::cleanPath and absolutePath
  • Add a containment check that rejects extraction if the resolved destination path does not start with the normalized target directory path, allowing equality only when the destination is exactly the target directory
  • On containment check failure, log the rejected path and abort extraction with ET_FileWriteError instead of writing the file
3rdparty/libzipplugin/libzipplugin.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • The while(strFileName.contains("../")) removal is brittle and may mangle legitimate file names (and miss variations like ..\); it would be safer to rely on QDir::cleanPath on the joined path and validate the resulting absolute path rather than manually stripping "../" substrings.
  • The final path check using startsWith(cleanTargetPath + QDir::separator()) may behave unexpectedly on case-insensitive filesystems or when strTargetPath lacks a trailing separator; consider normalizing case (where appropriate) and enforcing that cleanTargetPath always ends with a separator before the comparison.
  • Currently strDestFileName is computed before the security check and then used directly; to avoid accidental divergence, consider reusing cleanDestPath (or building a single canonical path variable) both for validation and for the actual file open.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `while(strFileName.contains("../"))` removal is brittle and may mangle legitimate file names (and miss variations like `..\`); it would be safer to rely on `QDir::cleanPath` on the joined path and validate the resulting absolute path rather than manually stripping `"../"` substrings.
- The final path check using `startsWith(cleanTargetPath + QDir::separator())` may behave unexpectedly on case-insensitive filesystems or when `strTargetPath` lacks a trailing separator; consider normalizing case (where appropriate) and enforcing that `cleanTargetPath` always ends with a separator before the comparison.
- Currently `strDestFileName` is computed before the security check and then used directly; to avoid accidental divergence, consider reusing `cleanDestPath` (or building a single canonical path variable) both for validation and for the actual file open.

## Individual Comments

### Comment 1
<location> `3rdparty/libzipplugin/libzipplugin.cpp:864-866` </location>
<code_context>
     QString strDestFileName = options.strTargetPath + QDir::separator() + strFileName;

+    // Additional security check: ensure the final path is within the target directory
+    QString cleanTargetPath = QDir::cleanPath(QDir(options.strTargetPath).absolutePath());
+    QString cleanDestPath = QDir::cleanPath(QDir(strDestFileName).absolutePath());
+    if (!cleanDestPath.startsWith(cleanTargetPath + QDir::separator()) &&
+        cleanDestPath != cleanTargetPath) {
+        qInfo() << "Path traversal detected! Rejected path: " << strFileName;
</code_context>

<issue_to_address>
**issue:** The path containment check can reject valid extractions when the target is the filesystem root.

When `options.strTargetPath` is the filesystem root (e.g. `"/"`), `cleanTargetPath` remains `"/"`, so the check becomes `cleanDestPath.startsWith("//")`. A path like `"/tmp/file"` will fail both this and the equality check, and be incorrectly rejected even though it is under the intended root. If targeting the root directory is supported, you’ll need to special-case `cleanTargetPath == QDir::rootPath()` or otherwise adjust the prefix logic to correctly handle root paths.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +864 to +866
QString cleanTargetPath = QDir::cleanPath(QDir(options.strTargetPath).absolutePath());
QString cleanDestPath = QDir::cleanPath(QDir(strDestFileName).absolutePath());
if (!cleanDestPath.startsWith(cleanTargetPath + QDir::separator()) &&
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: The path containment check can reject valid extractions when the target is the filesystem root.

When options.strTargetPath is the filesystem root (e.g. "/"), cleanTargetPath remains "/", so the check becomes cleanDestPath.startsWith("//"). A path like "/tmp/file" will fail both this and the equality check, and be incorrectly rejected even though it is under the intended root. If targeting the root directory is supported, you’ll need to special-case cleanTargetPath == QDir::rootPath() or otherwise adjust the prefix logic to correctly handle root paths.

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: LiHua000, lzwind

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@LiHua000
Copy link
Copy Markdown
Contributor Author

LiHua000 commented Dec 9, 2025

/merge

@deepin-bot deepin-bot bot merged commit 63d943b into linuxdeepin:master Dec 9, 2025
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants