FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Set-Location fails when target is accessible through non-accessible intermediate directories on Windows · Issue #27899 · PowerShell/PowerShell · GitHub

Set-Location fails when target is accessible through non-accessible intermediate directories on Windows #27899

Description

Prerequisites

Steps to reproduce

On Windows, create a directory hierarchy where the user cannot access intermediate directories directly, but can traverse them due to the standard SeChangeNotifyPrivilege / Bypass traverse checking privilege and has explicit access to a descendant.

The following example uses:

D:\x\y\q\z

where z is the accessible target.

The ACL setup needs to be performed from an elevated PowerShell. $TestUser is the non-elevated user that will run the reproduction:

$TestUser = 'DOMAIN\User'

$Sid = (
    [System.Security.Principal.NTAccount]::new($TestUser)
).Translate([System.Security.Principal.SecurityIdentifier]).Value

$Principal = "*$Sid"

New-Item -ItemType Directory -Path 'D:\x\y\q\z' -Force | Out-Null
Set-Content -LiteralPath 'D:\x\y\q\z\test.txt' -Value 'This is a test'

# Deny access to the intermediate directories themselves.
# These ACEs don't inherit to descendants.
icacls 'D:\x\y'   /deny "$Principal`:F"
icacls 'D:\x\y\q' /deny "$Principal`:F"

# Explicitly grant access to the final target and its descendants.
icacls 'D:\x\y\q\z' /grant:r "$Principal`:(OI)(CI)(M)"

Start a new, non-elevated PowerShell process as $TestUser.

Verify that the standard Windows bypass-traverse privilege is present:

whoami /priv | Select-String SeChangeNotifyPrivilege

For example:

SeChangeNotifyPrivilege    Bypass traverse checking    Enabled

The target directory is accessible even though an intermediate path component isn't:

Get-Item -LiteralPath 'D:\x\y\q'

Result:

Get-Item: Access to the path 'D:\x\y\q' is denied.
Get-Item: Cannot find path 'D:\x\y\q' because it does not exist.

However, accessing the descendant succeeds:

Get-Item -LiteralPath 'D:\x\y\q\z'
Get-ChildItem -LiteralPath 'D:\x\y\q\z'
Get-Content -LiteralPath 'D:\x\y\q\z\test.txt'

The process current directory can also be set to the target:

[Environment]::CurrentDirectory = 'D:\x\y\q\z'
[Environment]::CurrentDirectory

Result:

D:\x\y\q\z

Finally:

Set-Location -LiteralPath 'D:\x\y\q\z'

Expected behavior

Set-Location should succeed:

PS D:\x\y\q\z>

The target directory is accessible and Windows permits traversal through the intermediate directories using SeChangeNotifyPrivilege ("Bypass traverse checking").

Direct navigation to an inaccessible target should still fail, for example:

Set-Location -LiteralPath 'D:\x\y\q'

Actual behavior

Set-Location fails while inspecting the inaccessible intermediate component:

Set-Location: Access to the path 'D:\x\y\q' is denied.

This differs from direct filesystem access to the same final target, which succeeds.

Error details

On current PowerShell master, the exception originates from FileSystemProvider path normalization:

System.UnauthorizedAccessException: Access to the path 'D:\x\y\q' is denied.
   at System.IO.FileSystemInfo.EnsureDataInitialized()
   at System.IO.FileSystemInfo.get_Attributes()
   at Microsoft.PowerShell.Commands.FileSystemProvider.GetFileSystemInfo(String path, Boolean& isContainer)
   at Microsoft.PowerShell.Commands.FileSystemProvider.NormalizeThePath(String basepath, Stack`1 tokenizedPathStack)
   at Microsoft.PowerShell.Commands.FileSystemProvider.NormalizeRelativePathHelper(String path, String basePath)
   at Microsoft.PowerShell.Commands.FileSystemProvider.NormalizeRelativePath(String path, String basePath)
   at System.Management.Automation.Provider.NavigationCmdletProvider.NormalizeRelativePath(String path, String basePath, CmdletProviderContext context)
   at System.Management.Automation.SessionStateInternal.NormalizeRelativePath(ProviderInfo provider, String path, String basePath, CmdletProviderContext context)

NormalizeThePath() builds the path component by component and calls:

var fsinfo = GetFileSystemInfo(currentPath, out bool _);

GetFileSystemInfo() retrieves FileSystemInfo.Attributes.

As a result, PowerShell requires metadata access to intermediate path components while normalizing the path. This can fail even though Windows itself permits traversal through those components and access to the final target.

Additional context

I tested a proof-of-concept change against current master that ignores UnauthorizedAccessException from GetFileSystemInfo() only for intermediate path components:

FileSystemInfo fsinfo = null;

try
{
    fsinfo = GetFileSystemInfo(currentPath, out bool _);
}
catch (UnauthorizedAccessException) when (tokenizedPathStack.Count > 0)
{
    // Access to an intermediate path component isn't required when
    // the operating system permits traversal through that component.
}

This is intended only as a proof of concept, not necessarily as the proposed implementation.

With this change, the reproduction above behaves as follows:

Get-Item D:\x\y\q             -> Access denied
Get-Item D:\x\y\q\z           -> succeeds
Get-ChildItem D:\x\y\q\z      -> succeeds
Get-Content ...\z\test.txt    -> succeeds

Set-Location D:\x\y\q         -> Access denied
Set-Location D:\x\y\q\z       -> succeeds

Path normalization also retains the expected distinction between an accessible and inaccessible final target:

Set-Location 'D:\x\y\q\z\..\z'
# succeeds, resolves to D:\x\y\q\z

Set-Location 'D:\x\y\q\z\..'
# Access denied, resolves to D:\x\y\q

This suggests that requiring metadata access to every intermediate path component in FileSystemProvider.NormalizeThePath() is what prevents Set-Location from respecting Windows bypass-traverse semantics.

Environment data

Name                           Value
----                           -----
PSVersion                      7.6.5
PSEdition                      Core
GitCommitId                    7.6.5
OS                             Microsoft Windows 10.0.26200
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.4
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

and

Name                           Value
----                           -----
PSVersion                      7.7.0-preview.3
PSEdition                      Core
GitCommitId                    7.7.0-preview.3-40-g2dc81fde9b0485883f4689287724503739540443
OS                             Microsoft Windows 10.0.26200
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.4
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Visuals

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs-TriageThe issue is new and needs to be triaged by a work group.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions


    Back | FazBrowse Home | New Git URL