-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Add support for AppX reparse points #10331
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1845,11 +1845,10 @@ private void Dir( | |
| // a) the user has asked to with the -FollowSymLinks switch parameter and | ||
SteveL-MSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
SteveL-MSFT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
SteveL-MSFT marked this conversation as resolved.
Show resolved
Hide resolved
SteveL-MSFT marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // b) the directory pointed to by the symlink has not already been visited, | ||
| // preventing symlink loops. | ||
| // c) it is not a name surrogate making it not a symlink | ||
| // c) it is not a reparse point with a target | ||
| if (tracker == null) | ||
| { | ||
| if (InternalSymbolicLinkLinkCodeMethods.IsReparsePoint(recursiveDirectory) && | ||
| InternalSymbolicLinkLinkCodeMethods.IsNameSurrogateReparsePoint(recursiveDirectory.FullName)) | ||
| if (InternalSymbolicLinkLinkCodeMethods.IsReparsePointWithTarget(recursiveDirectory)) | ||
| { | ||
| continue; | ||
| } | ||
|
|
@@ -2001,8 +2000,7 @@ string ToModeString(FileSystemInfo fileSystemInfo) | |
| public static string NameString(PSObject instance) | ||
| { | ||
| return instance?.BaseObject is FileSystemInfo fileInfo | ||
| ? (InternalSymbolicLinkLinkCodeMethods.IsReparsePoint(fileInfo) && | ||
| InternalSymbolicLinkLinkCodeMethods.IsNameSurrogateReparsePoint(fileInfo.FullName)) | ||
| ? InternalSymbolicLinkLinkCodeMethods.IsReparsePointWithTarget(fileInfo) | ||
| ? $"{fileInfo.Name} -> {InternalSymbolicLinkLinkCodeMethods.GetTarget(instance)}" | ||
| : fileInfo.Name | ||
| : string.Empty; | ||
|
|
@@ -7704,6 +7702,8 @@ public static class InternalSymbolicLinkLinkCodeMethods | |
|
|
||
| private const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003; | ||
|
|
||
| private const uint IO_REPARSE_TAG_APPEXECLINK = 0x8000001B; | ||
SteveL-MSFT marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| private const string NonInterpretedPathPrefix = @"\??\"; | ||
|
|
||
| private const int MAX_PATH = 260; | ||
|
|
@@ -7790,6 +7790,17 @@ private struct REPARSE_DATA_BUFFER_MOUNTPOINT | |
| public byte[] PathBuffer; | ||
| } | ||
|
|
||
| [StructLayout(LayoutKind.Sequential)] | ||
| private struct REPARSE_DATA_BUFFER_APPEXECLINK | ||
| { | ||
| public uint ReparseTag; | ||
| public ushort ReparseDataLength; | ||
| public ushort Reserved; | ||
| public uint StringCount; | ||
| [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3FF0)] | ||
| public byte[] StringList; | ||
| } | ||
|
|
||
| [StructLayout(LayoutKind.Sequential)] | ||
| private struct BY_HANDLE_FILE_INFORMATION | ||
| { | ||
|
|
@@ -7989,13 +8000,23 @@ private static string WinInternalGetLinkType(string filePath) | |
|
|
||
| REPARSE_DATA_BUFFER_SYMBOLICLINK reparseDataBuffer = Marshal.PtrToStructure<REPARSE_DATA_BUFFER_SYMBOLICLINK>(outBuffer); | ||
|
|
||
| if (reparseDataBuffer.ReparseTag == IO_REPARSE_TAG_SYMLINK) | ||
| linkType = "SymbolicLink"; | ||
| else if (reparseDataBuffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) | ||
| linkType = "Junction"; | ||
| else | ||
| switch (reparseDataBuffer.ReparseTag) | ||
| { | ||
| linkType = IsHardLink(ref dangerousHandle) ? "HardLink" : null; | ||
| case IO_REPARSE_TAG_SYMLINK: | ||
| linkType = "SymbolicLink"; | ||
| break; | ||
|
|
||
| case IO_REPARSE_TAG_MOUNT_POINT: | ||
| linkType = "Junction"; | ||
| break; | ||
|
|
||
| case IO_REPARSE_TAG_APPEXECLINK: | ||
| linkType = "AppExeCLink"; | ||
| break; | ||
|
|
||
| default: | ||
| linkType = IsHardLink(ref dangerousHandle) ? "HardLink" : null; | ||
| break; | ||
| } | ||
|
|
||
| return linkType; | ||
|
|
@@ -8026,22 +8047,25 @@ internal static bool IsReparsePoint(FileSystemInfo fileInfo) | |
| return fileInfo.Attributes.HasFlag(System.IO.FileAttributes.ReparsePoint); | ||
| } | ||
|
|
||
| internal static bool IsNameSurrogateReparsePoint(string filePath) | ||
| internal static bool IsReparsePointWithTarget(FileSystemInfo fileInfo) | ||
| { | ||
| if (!IsReparsePoint(fileInfo)) | ||
| { | ||
| return false; | ||
| } | ||
| #if !UNIX | ||
| var data = new WIN32_FIND_DATA(); | ||
| using (SafeFileHandle handle = FindFirstFileEx(filePath, FINDEX_INFO_LEVELS.FindExInfoBasic, ref data, FINDEX_SEARCH_OPS.FindExSearchNameMatch, IntPtr.Zero, 0)) | ||
| using (SafeFileHandle handle = FindFirstFileEx(fileInfo.FullName, FINDEX_INFO_LEVELS.FindExInfoBasic, ref data, FINDEX_SEARCH_OPS.FindExSearchNameMatch, IntPtr.Zero, 0)) | ||
| { | ||
| // Name surrogates are reparse points that point to other named entities local to the filesystem (like symlinks) | ||
| // Name surrogates (0x20000000) are reparse points that point to other named entities local to the filesystem (like symlinks) | ||
| // In the case of OneDrive, they are not surrogates and would be safe to recurse into. | ||
| // This code is equivalent to the IsReparseTagNameSurrogate macro: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-isreparsetagnamesurrogate | ||
| if (!handle.IsInvalid && (data.dwReserved0 & 0x20000000) == 0) | ||
| if (!handle.IsInvalid && (data.dwReserved0 & 0x20000000) == 0 && (data.dwReserved0 & IO_REPARSE_TAG_APPEXECLINK) != IO_REPARSE_TAG_APPEXECLINK) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why have the two checks of dwReserved0 a different form?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The first half is the equivalent of the
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I meant why we do not use
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The IO_REPARSE_TAGs aren't bitflags, so in the case of OneDrive, dwReserved0 has a value of 0x9000601A. We want this check to succeed so we don't try to access the file and end up downloading it, so the first half passes, the second half resolves to 0x8000001A which also passes (as IO_REPARSE_TAG_APPEXECLINK is 0x8000001B). If we change it to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be just The current comparison There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not an expert on reparse tags, either. Microsoft's sample code compares to The Reparse Point Tag Request instructions ask the driver developer to specify how the flags in the highest bits of the 32-bit reparse tag should be set. This seems to imply that the developer is not allowed to flip those flags on their own later. https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/reparse-point-tag-request Some information about the 0x10000000 bit and the
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I reviewed all code I found. The tags is absolute values and I think a right way is to do simple comparison. For reference https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/c8e77b37-3909-4fe6-a4ea-2b9d423b1ee4 It seems we use I'd use explicit values otherwise we can fall in tricky bug. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checking the 0x20000000 flag (IsReparseTagNameSurrogate) can be OK, if you only care about the coarse meaning of the reparse tag and not about the format of the reparse data.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @iSazonov do you want to submit a new PR to clean this up?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done #10431 |
||
| { | ||
| return false; | ||
| } | ||
| } | ||
| #endif | ||
| // true means the reparse point is a symlink | ||
| return true; | ||
| } | ||
|
|
||
|
|
@@ -8205,25 +8229,33 @@ private static string WinInternalGetTarget(SafeFileHandle handle) | |
| throw new Win32Exception(lastError); | ||
| } | ||
|
|
||
| // Unmarshal to symbolic link to look for tags. | ||
| REPARSE_DATA_BUFFER_SYMBOLICLINK reparseDataBuffer = Marshal.PtrToStructure<REPARSE_DATA_BUFFER_SYMBOLICLINK>(outBuffer); | ||
|
|
||
| if (reparseDataBuffer.ReparseTag != IO_REPARSE_TAG_SYMLINK && reparseDataBuffer.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT) | ||
| return null; | ||
|
|
||
| string targetDir = null; | ||
|
|
||
| if (reparseDataBuffer.ReparseTag == IO_REPARSE_TAG_SYMLINK) | ||
| { | ||
| targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer, reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength); | ||
| } | ||
| REPARSE_DATA_BUFFER_SYMBOLICLINK reparseDataBuffer = Marshal.PtrToStructure<REPARSE_DATA_BUFFER_SYMBOLICLINK>(outBuffer); | ||
|
|
||
| if (reparseDataBuffer.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) | ||
| switch (reparseDataBuffer.ReparseTag) | ||
| { | ||
| // Since this is a junction we need to unmarshal to the correct structure. | ||
| REPARSE_DATA_BUFFER_MOUNTPOINT reparseDataBufferMountPoint = Marshal.PtrToStructure<REPARSE_DATA_BUFFER_MOUNTPOINT>(outBuffer); | ||
| case IO_REPARSE_TAG_SYMLINK: | ||
| targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer, reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength); | ||
| break; | ||
|
|
||
| case IO_REPARSE_TAG_MOUNT_POINT: | ||
| REPARSE_DATA_BUFFER_MOUNTPOINT reparseMountPointDataBuffer = Marshal.PtrToStructure<REPARSE_DATA_BUFFER_MOUNTPOINT>(outBuffer); | ||
| targetDir = Encoding.Unicode.GetString(reparseMountPointDataBuffer.PathBuffer, reparseMountPointDataBuffer.SubstituteNameOffset, reparseMountPointDataBuffer.SubstituteNameLength); | ||
| break; | ||
|
|
||
| case IO_REPARSE_TAG_APPEXECLINK: | ||
| REPARSE_DATA_BUFFER_APPEXECLINK reparseAppExeDataBuffer = Marshal.PtrToStructure<REPARSE_DATA_BUFFER_APPEXECLINK>(outBuffer); | ||
| // The target file is at index 2 | ||
| if (reparseAppExeDataBuffer.StringCount >= 3) | ||
| { | ||
| string temp = Encoding.Unicode.GetString(reparseAppExeDataBuffer.StringList); | ||
| targetDir = temp.Split('\0')[2]; | ||
| } | ||
| break; | ||
|
|
||
| targetDir = Encoding.Unicode.GetString(reparseDataBufferMountPoint.PathBuffer, reparseDataBufferMountPoint.SubstituteNameOffset, reparseDataBufferMountPoint.SubstituteNameLength); | ||
| default: | ||
| return null; | ||
| } | ||
|
|
||
| if (targetDir != null && targetDir.StartsWith(NonInterpretedPathPrefix, StringComparison.OrdinalIgnoreCase)) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.