Skip to content
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

Tar: Only treat reparse points marked as junctions or symlinks as actual tar symlinks #89102

Closed
wants to merge 10 commits into from

Conversation

carlossanlop
Copy link
Member

The current code only checks if it's a reparse point and adds the filesystem object as a symlink, which is wrong for many types of reparse points. Only those marked as junctions or symlinks should be treated as tar symlinks.

I added a test to confirm that a junction is stored as symlink. I am unsure how to create other types of reparse points so I didn't add tests for that, but since those are rarer cases, I would prefer to tackle them separately if requested.

cc @billfreist, thanks for reporting this.

Fixes #82949

… point is a junction or a symbolic link, in which case the filesystem object will be treated as a tar symbolic link. All other reparse points will be treated as regular files.
…specified format and consume it in TarWriter.Unix.cs and TarWriter.Windows.cs.
…ve, verify they are added as symbolic links entries.
@ghost
Copy link

ghost commented Jul 18, 2023

Tagging subscribers to this area: @dotnet/area-system-formats-tar
See info in area-owners.md if you want to be subscribed.

Issue Details

The current code only checks if it's a reparse point and adds the filesystem object as a symlink, which is wrong for many types of reparse points. Only those marked as junctions or symlinks should be treated as tar symlinks.

I added a test to confirm that a junction is stored as symlink. I am unsure how to create other types of reparse points so I didn't add tests for that, but since those are rarer cases, I would prefer to tackle them separately if requested.

cc @billfreist, thanks for reporting this.

Fixes #82949

Author: carlossanlop
Assignees: carlossanlop
Labels:

area-System.Formats.Tar

Milestone: -

[InlineData(TarEntryFormat.Ustar)]
[InlineData(TarEntryFormat.Pax)]
[InlineData(TarEntryFormat.Gnu)]
public void Add_Junction_As_SymbolicLink(TarEntryFormat format)
Copy link
Member

Choose a reason for hiding this comment

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

Can you run these tests without elevation? I think it's missing a can create symlinks condition. I've added them a few times when tests failed locally.

Copy link
Member Author

Choose a reason for hiding this comment

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

Starting with Windows 10 Insiders build 14972, symlinks can be created without needing to elevate the console as administrator. https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10/

If we still have tests running older Windows versions, they should break in the CI.

Copy link
Member Author

Choose a reason for hiding this comment

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

Junctions are ancient, they don't require elevation AFAIK. They ran without problem in my local machine. The CI should also cry if something goes wrong.

This new test does not create symlinks in the disk by the way, it adds an entry to the tar archive as a symlink. The only thing that gets created in disk is the junction.

bool isDirectory = (attributes & FileAttributes.Directory) != 0;

Interop.Kernel32.WIN32_FIND_DATA data = default;
Interop.Kernel32.GetFindData(fullPath, isDirectory, ignoreAccessDenied: false, ref data);
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be called only when the file is a reparse point?

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right.

Comment on lines +30 to +31
if (data.dwReserved0 is Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK or
Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT)
Copy link
Member

Choose a reason for hiding this comment

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

This looks like another data point in favor of having an API for ReparseTags #1908.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed.

@jozkee
Copy link
Member

jozkee commented Jul 18, 2023

I am unsure how to create other types of reparse points so I didn't add tests for that

You can test IO_REPARSE_TAG_APPEXECLINK by trying to archive one file with such ReparseTag:

[Fact]
[PlatformSpecific(TestPlatforms.Windows)]
public void UnsupportedLink_ReturnsNull()
{
string unsupportedLinkPath = GetAppExecLinkPath();
if (unsupportedLinkPath is null)
{
return;
}
Assert.Null(File.ResolveLinkTarget(unsupportedLinkPath, false));
Assert.Null(File.ResolveLinkTarget(unsupportedLinkPath, true));
}

@carlossanlop
Copy link
Member Author

You can test IO_REPARSE_TAG_APPEXECLINK by trying to archive one file with such ReparseTag:

Thanks @jozkee, I forgot about that.

I wrote a test that attempts to verify what happens when adding an AppExecLink. I reused the method that we have to retrieve the first AppExecLink from the WindowsApps folder. In my case, it's accesschk.exe, which is a binary that is installed by the Sysinternals Suite. I confirmed it's an actual AppExecLink because it's 0KB in length.

It seems that my current code would throw an IOException when FileStream attempts to create a file handle to it (see the callkstack below). Which means we can't treat AppExecLinks as regular files.

I think for now, we should just throw an IOException on the Tar code when we encounter an unsupported ReparsePoint. There might be a way to crack open an AppExecLink, retrieve some internal information it holds, and then store it in the DataStream, but we don't have an actual entry type to indicate this is an AppExecLink file, which differs from a regular file and from a symlink. So if the user generates a tar archive containing one of these, then tries to extract it, it will be extracted as something that it is not.

Thoughts?

      System.IO.IOException : The file cannot be accessed by the system. : 'C:\Users\calope\AppData\Local\Microsoft\WindowsApps\accesschk.exe'
        Stack Trace:
          C:\Users\calope\source\repos\runtime\src\libraries\System.Private.CoreLib\src\Microsoft\Win32\SafeHandles\SafeFileHandle.Windows.cs(114,0): at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile(String fullPath, FileMode mode, FileAccess access, FileShare share
  , FileOptions options)
          C:\Users\calope\source\repos\runtime\src\libraries\System.Private.CoreLib\src\Microsoft\Win32\SafeHandles\SafeFileHandle.Windows.cs(47,0): at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileO
  ptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
          C:\Users\calope\source\repos\runtime\src\libraries\System.Private.CoreLib\src\System\IO\Strategies\OSFileStreamStrategy.cs(46,0): at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions option
  s, Int64 preallocationSize, Nullable`1 unixCreateMode)
          C:\Users\calope\source\repos\runtime\src\libraries\System.Private.CoreLib\src\System\IO\Strategies\SyncWindowsFileStreamStrategy.cs(15,0): at System.IO.Strategies.SyncWindowsFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share,
  FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
          C:\Users\calope\source\repos\runtime\src\libraries\System.Private.CoreLib\src\System\IO\Strategies\FileStreamHelpers.Windows.cs(35,0): at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(String path, FileMode mode, FileAccess access, FileShare share, Fil
  eOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
          C:\Users\calope\source\repos\runtime\src\libraries\System.Private.CoreLib\src\System\IO\Strategies\FileStreamHelpers.cs(24,0): at System.IO.Strategies.FileStreamHelpers.ChooseStrategy(FileStream fileStream, String path, FileMode mode, FileAccess access, FileShare
   share, Int32 bufferSize, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
          C:\Users\calope\source\repos\runtime\src\libraries\System.Private.CoreLib\src\System\IO\FileStream.cs(222,0): at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, Int64 preallocationS
  ize)
          C:\Users\calope\source\repos\runtime\src\libraries\System.Private.CoreLib\src\System\IO\FileStream.cs(146,0): at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
          C:\Users\calope\source\repos\runtime\src\libraries\System.Formats.Tar\src\System\Formats\Tar\TarWriter.Windows.cs(79,0): at System.Formats.Tar.TarWriter.ConstructEntryForWriting(String fullPath, String entryName, FileOptions fileOptions)
          C:\Users\calope\source\repos\runtime\src\libraries\System.Formats.Tar\src\System\Formats\Tar\TarWriter.cs(166,0): at System.Formats.Tar.TarWriter.ReadFileFromDiskAndWriteToArchiveStreamAsEntry(String fullPath, String entryName)
          C:\Users\calope\source\repos\runtime\src\libraries\System.Formats.Tar\src\System\Formats\Tar\TarWriter.cs(139,0): at System.Formats.Tar.TarWriter.WriteEntry(String fileName, String entryName)
          C:\Users\calope\source\repos\runtime\src\libraries\System.Formats.Tar\tests\TarWriter\TarWriter.WriteEntry.File.Tests.Windows.cs(72,0): at System.Formats.Tar.Tests.TarWriter_WriteEntry_File_Tests.Add_AppExecLink_As_RegularFile(TarEntryFormat format)
             at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
          C:\Users\calope\source\repos\runtime\src\coreclr\System.Private.CoreLib\src\System\Reflection\MethodBaseInvoker.CoreCLR.cs(36,0): at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
          C:\Users\calope\source\repos\runtime\src\libraries\System.Private.CoreLib\src\System\Reflection\MethodBaseInvoker.cs(177,0): at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
      System.Formats.Tar.Tests.TarWriter_WriteEntry_File_Tests.Add_AppExecLink_As_RegularFile(format: Ustar) [FAIL]

@ericstj
Copy link
Member

ericstj commented Sep 25, 2023

@carlossanlop what's the next step to move this one forward?

@carlossanlop
Copy link
Member Author

@carlossanlop what's the next step to move this one forward?

Make sure we have tests that confirm my changes are working, haven't been able to get back to it yet. Need to find a way to test adding a OneDrive cloud file. Maybe I could add manual tests.

@carlossanlop carlossanlop marked this pull request as draft October 30, 2023 14:25
@jozkee jozkee added the needs-author-action An issue or pull request that requires more info or actions from the author. label Oct 31, 2023
@ghost ghost added the no-recent-activity label Nov 15, 2023
@ghost
Copy link

ghost commented Nov 15, 2023

This pull request has been automatically marked no-recent-activity because it has not had any activity for 14 days. It will be closed if no further activity occurs within 14 more days. Any new comment (by anyone, not necessarily the author) will remove no-recent-activity.

@ghost
Copy link

ghost commented Nov 29, 2023

This pull request will now be closed since it had been marked no-recent-activity but received no further activity in the past 14 days. It is still possible to reopen or comment on the pull request, but please note that it will be locked if it remains inactive for another 30 days.

@ghost ghost closed this Nov 29, 2023
@github-actions github-actions bot locked and limited conversation to collaborators Dec 29, 2023
This pull request was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Formats.Tar needs-author-action An issue or pull request that requires more info or actions from the author. no-recent-activity
Projects
None yet
Development

Successfully merging this pull request may close these issues.

TarWriter on Windows Server treats File Deduplication reparse point flag as symbolic links
6 participants