Skip to content

Commit 74b0096

Browse files
Fix Windows Tar Paths (#128367)
Addresses Windows symlink bug
1 parent b04ba98 commit 74b0096

2 files changed

Lines changed: 26 additions & 0 deletions

File tree

src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,13 @@ internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, b
379379
// LinkName is an absolute path, or path relative to the fileDestinationPath directory.
380380
// We don't check if the LinkName is empty. In that case, creation of the link will fail because link targets can't be empty.
381381
string linkName = ArchivingUtils.SanitizeEntryFilePath(LinkName, preserveDriveRoot: true);
382+
// On Windows, reject rooted-but-not-fully-qualified symlink targets (e.g., "\Windows\win.ini").
383+
// Unlike files, symlink targets are resolved at access time, not extraction time,
384+
// so Path.GetFullPath here cannot reliably predict what drive the OS will resolve them against.
385+
if (OperatingSystem.IsWindows() && Path.IsPathRooted(linkName) && !Path.IsPathFullyQualified(linkName))
386+
{
387+
throw new IOException(SR.Format(SR.TarExtractingResultsLinkOutside, linkName, destinationDirectoryPath));
388+
}
382389
string? linkDestination = GetFullDestinationPath(
383390
destinationDirectoryPath,
384391
Path.IsPathFullyQualified(linkName) ? linkName : Path.Join(Path.GetDirectoryName(fileDestinationPath), linkName));

src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.Windows.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,24 @@ public void Extract_SpecialFiles_Windows_ThrowsInvalidOperation()
2727

2828
Assert.Equal(0, Directory.GetFileSystemEntries(destination).Count());
2929
}
30+
31+
[ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))]
32+
public void ExtractToDirectory_RejectsSymlinkWithRootedTargetOutsideDestination()
33+
{
34+
using TempDirectory root = new TempDirectory();
35+
string destDir = Path.Combine(root.Path, "dest");
36+
Directory.CreateDirectory(destDir);
37+
// A rooted but ambiguous path.
38+
string rootedLinkTarget = @"\Temp\temp.ini";
39+
string tarPath = Path.Combine(root.Path, "windows_symlink.tar");
40+
using (FileStream stream = new FileStream(tarPath, FileMode.Create, FileAccess.Write))
41+
using (TarWriter writer = new TarWriter(stream, leaveOpen: false))
42+
{
43+
writer.WriteEntry(new PaxTarEntry(TarEntryType.SymbolicLink, "outside.txt") { LinkName = rootedLinkTarget });
44+
}
45+
Assert.Throws<IOException>(() => TarFile.ExtractToDirectory(tarPath, destDir, overwriteFiles: true));
46+
Assert.Empty(Directory.EnumerateFileSystemEntries(destDir));
47+
}
48+
3049
}
3150
}

0 commit comments

Comments
 (0)