-
Notifications
You must be signed in to change notification settings - Fork 70
fix: recognize host-level UNC roots in path containment checks #1327
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
crowecawcaw
wants to merge
31
commits into
aws-deadline:mainline
Choose a base branch
from
crowecawcaw:fix/unc-host-path-containment
base: mainline
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 5 commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
e56265f
fix: recognize host-level UNC roots in path containment checks
crowecawcaw fb66df6
fix: recognize UNC roots before Python 3.11, fold extended-length pre…
crowecawcaw 890c058
fix: stop ntpath.isabs reading a UNC share as a relative path
crowecawcaw aa4f499
fix: keep a rooted, driveless root absolute on every Python version
crowecawcaw a1d3b23
fix: drop rooted, driveless known-asset roots on Windows
crowecawcaw d8f2f77
test: bind the mapped drive letter before the skip
crowecawcaw 90bfdd5
fix: drop the bare UNC anchor from known-asset roots
crowecawcaw 7b1c27b
ci: run the SMB path test post-merge, not only on dispatch
crowecawcaw d698d62
Merge branch 'mainline' into fix/unc-host-path-containment
crowecawcaw cc26271
ci: fail the SMB suite rather than skip it when it cannot run
crowecawcaw 3baa219
Merge branch 'mainline' into fix/unc-host-path-containment
crowecawcaw 0bcc8bf
fix: use component containment for the archive extraction guard
crowecawcaw baa5b9d
test: pin the share-root '..' clamp instead of a false escape
crowecawcaw 02191cf
fix: make the archive extraction guard testable off Windows
crowecawcaw d1c5bf7
refactor: extract the hook PATH-value guard to make it testable
crowecawcaw fa4f7a9
test: cover the absolute PATH-default check on Windows spellings
crowecawcaw 9a65ce9
test: close the gaps an audit of these tests found
crowecawcaw cc49598
ci: gate pull requests on the SMB suite, and stop pretending elsewhere
crowecawcaw 7db87fd
test: stop the SMB cases from choosing their own assertions
crowecawcaw c113ca1
fix: resolve path_module at call time, not in the signature
crowecawcaw 2fc4e6f
test: make platform-gated cases assert everywhere, and two raises spe…
crowecawcaw 412a317
test: give the pathlib oracle a disagreement floor
crowecawcaw 5b6b64e
fix: accept a host-level UNC download root
crowecawcaw c62f7fc
chore: ban commonprefix on the path modules the code actually passes …
crowecawcaw c4857bd
test: wait for the message, not for the thread that sends it
crowecawcaw efc6150
fix: collapse '~' by path component, not by string prefix
crowecawcaw df07688
ci: run the SMB suite on every pull request and before a release
crowecawcaw adba15c
test: cover five behaviors that survived mutation
crowecawcaw 45479a9
Merge branch 'mainline' into fix/unc-host-path-containment
crowecawcaw fa6e95a
test: spell the accepted download root for the host
crowecawcaw a2bd9d7
chore: narrow the click.Path prompt results to str
crowecawcaw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| name: Windows SMB Path Test | ||
|
|
||
| # Validates UNC path containment against a real SMB share, which lexical ntpath modeling | ||
| # cannot do. Regression coverage for issue #1321. | ||
| # | ||
| # Not part of Code Quality: creating a share needs administrator rights, and the loopback | ||
| # share is slower and more environment-dependent than a unit test. | ||
| on: | ||
| workflow_dispatch: | ||
| workflow_call: | ||
| inputs: | ||
| tag: | ||
| description: Git ref (tag/branch/SHA) to test. Defaults to the triggering ref. | ||
| required: false | ||
| type: string | ||
| default: '' | ||
|
|
||
| jobs: | ||
| test: | ||
| name: UNC Containment (real SMB) | ||
| runs-on: windows-latest | ||
| timeout-minutes: 15 | ||
| steps: | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| ref: ${{ inputs.tag || github.ref }} | ||
|
|
||
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | ||
| with: | ||
| python-version: '3.12' | ||
|
|
||
| - name: Confirm SMB prerequisites | ||
| # Fail with a clear message here rather than having every test skip itself, | ||
| # which would look like a pass. | ||
| shell: pwsh | ||
| run: | | ||
| $admin = ([Security.Principal.WindowsPrincipal] ` | ||
| [Security.Principal.WindowsIdentity]::GetCurrent() | ||
| ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) | ||
| if (-not $admin) { throw "Administrator rights are required to create an SMB share." } | ||
| Get-Service LanmanServer, LanmanWorkstation | Format-Table -AutoSize | ||
| Start-Service LanmanServer | ||
| Start-Service LanmanWorkstation | ||
| # Developer Mode lets a non-elevated process create symlinks; the escape | ||
| # test needs one and skips itself otherwise. | ||
| $key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock' | ||
| New-Item -Path $key -Force | Out-Null | ||
| Set-ItemProperty -Path $key -Name AllowDevelopmentWithoutDevLicense -Value 1 -Type DWord | ||
|
|
||
| - name: Install | ||
| run: | | ||
| python -m pip install --upgrade pip hatch | ||
|
|
||
| - name: Run the SMB path tests | ||
| shell: pwsh | ||
| run: | | ||
| hatch run pytest test/integ/windows_smb -v --no-cov -p no:randomly | ||
|
|
||
| - name: Report skips | ||
|
crowecawcaw marked this conversation as resolved.
Outdated
|
||
| # A skipped SMB test is indistinguishable from a passing one in the summary, | ||
| # so surface the count explicitly. | ||
| if: always() | ||
| shell: pwsh | ||
| run: | | ||
| hatch run pytest test/integ/windows_smb --no-cov -q -rs 2>&1 | | ||
| Select-String -Pattern 'SKIPPED|passed|failed' | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
|
|
||
| """ | ||
| Summarizing a group of paths for display. | ||
|
|
||
| Kept out of ``_path_utils`` because this is presentation rather than a trust decision, and | ||
| it carries cases that only a displayed string cares about -- unresolved ``..`` runs and | ||
| preserving the caller's spelling -- which a reader auditing containment should not have to | ||
| read past. | ||
|
|
||
| Like the containment helpers, this is purely lexical and never raises. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import os | ||
| from typing import Any, Sequence | ||
|
|
||
| from ._path_utils import _PARDIR, _UNC_ANCHOR, _split_anchored | ||
|
|
||
| __all__ = [ | ||
| "common_ancestor", | ||
| ] | ||
|
|
||
|
|
||
| def _leading_pardir_count(parts: list[str]) -> int: | ||
| """Count the leading '..' run that ``normpath`` could not resolve. | ||
|
|
||
| Counted on parts rather than whole components because an anchor can precede the run | ||
| ('C:..\\x' is the parent of the working directory on drive C:). | ||
| """ | ||
| count = 0 | ||
| for part in parts: | ||
| if part != _PARDIR: | ||
| break | ||
| count += 1 | ||
| return count | ||
|
|
||
|
|
||
| def common_ancestor(paths: Sequence[Any], *, path_module: Any = os.path) -> str: | ||
| """Return the deepest directory containing every path in ``paths``. | ||
|
|
||
| This is ``os.path.commonpath`` without the exceptions: paths in unrelated spaces return | ||
| ``""`` rather than raising, and a UNC host is a valid answer for paths on different | ||
| shares of one server. The result keeps the first path's spelling and, like | ||
| ``commonpath``, is purely lexical. | ||
| """ | ||
| if not paths: | ||
| return "" | ||
|
|
||
| split = [_split_anchored(p, path_module, normalize_case=True) for p in paths] | ||
| normalized = [([a] if a else []) + parts for a, parts in split] | ||
| anchor, spelled_parts = _split_anchored(paths[0], path_module, normalize_case=False) | ||
| spelled = ([anchor] if anchor else []) + spelled_parts | ||
|
|
||
| # '..' and '../..' are rooted at different unknown places, so runs of differing depth | ||
| # share nothing. Comparing them positionally would return the shallower path, which is | ||
| # not an ancestor of the deeper one -- os.path.commonpath has that bug. | ||
| if len({_leading_pardir_count(parts) for _, parts in split}) > 1: | ||
| return "" | ||
|
|
||
| shared = min(len(components) for components in normalized) | ||
| while shared > 0 and any(other[:shared] != normalized[0][:shared] for other in normalized): | ||
| shared -= 1 | ||
| if shared == 0: | ||
| return "" | ||
| # Matching only the bare anchor means different servers, so no shared directory. | ||
| if shared == 1 and normalized[0][0] == _UNC_ANCHOR: | ||
| return "" | ||
|
|
||
| # The anchor carries its own separator, so it abuts the first part directly. | ||
| if anchor: | ||
| return anchor + path_module.sep.join(spelled[1:shared]) | ||
| return path_module.sep.join(spelled[:shared]) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.