Skip to content

Fix Windows case sensitive zip extraction - #131202

Merged
alinpahontu2912 merged 5 commits into
dotnet:mainfrom
alinpahontu2912:windows_zip_case_sensitivity
Aug 10, 2026
Merged

alinpahontu2912 merged 5 commits into
dotnet:mainfrom
alinpahontu2912:windows_zip_case_sensitivity

Conversation

@alinpahontu2912

Copy link
Copy Markdown
Member

Fixes #131066

Update checks for zip extraction to better protect case sensitive environments.

@alinpahontu2912
alinpahontu2912 requested review from a team and Copilot July 22, 2026 10:21
@alinpahontu2912 alinpahontu2912 changed the title fix possible extracton escape via case insensitive path check Fix Windows case sensitive zip extraction Jul 22, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @karelz, @dotnet/area-system-io-compression
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens System.IO.Compression.ZipFile extraction path validation to prevent a traversal-style escape that can occur when the destination-root containment check is case-insensitive but the underlying filesystem treats differently-cased sibling directories as distinct.

Changes:

  • Tighten ZipArchiveEntry extraction containment validation: after resolving the full destination path, require both the existing platform comparison and an ordinal root-prefix match on case-insensitive platforms to detect case-only sibling escapes.
  • Add regression coverage for the case-insensitive sibling scenario (e.g., extracting ../dest/pwn.txt into a root named Dest).
  • Add tests ensuring benign .. (that resolves back inside the root) and . segments still extract successfully.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs Adds tests for the newly rejected case-insensitive sibling escape and for allowed . / in-root .. segments.
src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs Strengthens destination-root prefix validation by adding an ordinal root-prefix requirement on case-insensitive platforms.

@GrabYourPitchforks GrabYourPitchforks left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Holding for offline feedback.

@GrabYourPitchforks
GrabYourPitchforks dismissed their stale review July 27, 2026 01:12

Sent reference materials & framing documents offline. Unblocking. (I've not reviewed the PR.)

Copilot AI review requested due to automatic review settings July 28, 2026 08:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs:239

  • The rationale comment is a bit misleading: a resolved path can share the root's exact casing and still have traversed outside the root and back in (e.g. "../Dest/x"). The important property you're enforcing is preventing escape + re-descent into a differently-cased sibling on case-insensitive platforms. Rewording this avoids baking incorrect reasoning into a security-sensitive check.
            // Reject entries that resolve outside the destination root. GetFullPath collapses "." and ".."
            // but never re-cases the segments it keeps. The root is combined in verbatim with a trailing
            // separator. That means a resolved path that shares the root's exact casing never climbed above the root;
            // one that matches the root only case-insensitively did climb out and re-descend under a
            // different spelling (e.g. "../dest/x" into root "Dest"), which is a distinct directory on a

Copilot AI review requested due to automatic review settings August 6, 2026 14:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs:231

  • The new destination-boundary check fails when fullDestination already ends with a directory separator (notably for drive/share roots like C:\ or \\server\share\). In that case fileDestinationPath[fullDestination.Length] is the first character of the next segment (or the path ends), so extraction incorrectly throws IO_ExtractingResultsInOutside for valid in-root entries.

To keep the intended ordinal (case-sensitive) check while handling roots and avoiding prefix collisions (e.g., Dest vs Destinations), compare against a destination prefix that always ends in a separator, and allow the normalized path to equal the destination itself (for directory entries like . / subdir/..).

            // Ensure the path stays within the destination directory boundary
            if (!fileDestinationPath.StartsWith(fullDestination, StringComparison.Ordinal) ||
                fileDestinationPath.Length <= fullDestination.Length ||
                fileDestinationPath[fullDestination.Length] != Path.DirectorySeparatorChar)
            {

Copilot AI review requested due to automatic review settings August 7, 2026 12:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs:109

  • The comment uses present tense to describe a “case-insensitive” destination-root prefix check, but the implementation under test now uses StringComparison.Ordinal. Rewording this as historical behavior avoids confusion about what the current code does and why the test exists.
            // An entry that normalizes into a differently-cased sibling of the destination root must be
            // rejected. On case-insensitive platforms (Windows, macOS, iOS, tvOS) the destination-root
            // prefix check is case-insensitive, so extracting "../dest/pwn.txt" into a root named "Dest"
            // would otherwise be treated as staying inside the root even though the file system can keep
            // "Dest" and "dest" as distinct directories.

Copilot AI review requested due to automatic review settings August 7, 2026 13:44
@alinpahontu2912
alinpahontu2912 requested a review from rzikm August 7, 2026 13:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs:235

  • The boundary check intentionally uses StringComparison.Ordinal (instead of platform-specific comparison) to handle directories that are case-sensitive even on typically case-insensitive platforms. Consider capturing that rationale in the comment so it isn’t accidentally reverted later.
            // Ensure the path stays within the destination directory boundary.

src/libraries/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs:109

  • This comment describes the destination-root prefix check as case-insensitive on Windows/macOS, but the implementation now uses an Ordinal comparison. Updating the wording to focus on per-directory/volume case sensitivity avoids the comment going stale/misleading.
            // An entry that normalizes into a differently-cased sibling of the destination root must be
            // rejected. On case-insensitive platforms (Windows, macOS, iOS, tvOS) the destination-root
            // prefix check is case-insensitive, so extracting "../dest/pwn.txt" into a root named "Dest"
            // would otherwise be treated as staying inside the root even though the file system can keep
            // "Dest" and "dest" as distinct directories.

@rzikm rzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, Thanks!

@alinpahontu2912
alinpahontu2912 enabled auto-merge (squash) August 10, 2026 07:43
@alinpahontu2912

Copy link
Copy Markdown
Member Author

/ba-g failures not related to my change

@alinpahontu2912
alinpahontu2912 merged commit d22185a into dotnet:main Aug 10, 2026
82 of 85 checks passed
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-rc1 milestone Aug 11, 2026
jtschuster pushed a commit to jtschuster/runtime that referenced this pull request Aug 11, 2026
Fixes dotnet#131066

Update checks for zip extraction to better protect case sensitive
environments.
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 11, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Zip Windows extraction is always case insensitive

5 participants