Skip to content

Commit fae7cd2

Browse files
committed
Allow links outside the extraction directory
1 parent 1622e1a commit fae7cd2

3 files changed

Lines changed: 92 additions & 17 deletions

File tree

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

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -349,16 +349,7 @@ internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, b
349349
throw new InvalidDataException(SR.TarEntryHardLinkOrSymlinkLinkNameEmpty);
350350
}
351351

352-
linkTargetPath = GetSanitizedFullPath(destinationDirectoryPath,
353-
Path.IsPathFullyQualified(LinkName) ? LinkName : Path.Join(Path.GetDirectoryName(fileDestinationPath), LinkName));
354-
355-
if (linkTargetPath == null)
356-
{
357-
throw new IOException(SR.Format(SR.TarExtractingResultsLinkOutside, LinkName, destinationDirectoryPath));
358-
}
359-
360-
// after TarExtractingResultsLinkOutside validation, preserve the original
361-
// symlink target path (to match behavior of other utilities).
352+
// preserve the original symlink target path (to match behavior of other utilities).
362353
linkTargetPath = LinkName;
363354
}
364355

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

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,21 +78,62 @@ public void ExtractEntry_ManySubfolderSegments_NoPrecedingDirectoryEntries()
7878
[InlineData(TarEntryType.HardLink)]
7979
public void Extract_LinkEntry_TargetOutsideDirectory(TarEntryType entryType)
8080
{
81+
using TempDirectory tempDirectory = new();
82+
string symlinkName = tempDirectory.GenerateRandomFilePath();
83+
File.WriteAllText(symlinkName, new string('x', 100));
84+
8185
using MemoryStream archive = new MemoryStream();
8286
using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true))
8387
{
8488
UstarTarEntry entry = new UstarTarEntry(entryType, "link");
85-
entry.LinkName = PlatformDetection.IsWindows ? @"C:\Windows\System32\notepad.exe" : "/usr/bin/nano";
89+
entry.LinkName = symlinkName;
90+
writer.WriteEntry(entry);
91+
}
92+
93+
archive.Seek(0, SeekOrigin.Begin);
94+
95+
using TempDirectory root = new TempDirectory();
96+
97+
Exception exception = Record.Exception(() => TarFile.ExtractToDirectory(archive, root.Path, overwriteFiles: true));
98+
Assert.Null(exception);
99+
100+
string symlinkPath = Path.Join(root.Path, "link");
101+
Assert.True(File.Exists(symlinkPath));
102+
103+
if (entryType is TarEntryType.SymbolicLink)
104+
{
105+
FileInfo? fileInfo = new(symlinkPath);
106+
Assert.Equal(symlinkName, fileInfo.LinkTarget);
107+
}
108+
}
109+
110+
[Theory]
111+
[InlineData("foo")]
112+
[InlineData("../../foo")]
113+
[InlineData("/usr/tmp/foo")]
114+
[InlineData(@"C:\tmp\foo")]
115+
public void Extract_SymbolicLinkEntryWithExistingOrNonExistingPaths_TargetOutsideDirectoryPreservesOriginalPaths(string symlinkName)
116+
{
117+
using MemoryStream archive = new();
118+
using (TarWriter writer = new(archive, TarEntryFormat.Ustar, leaveOpen: true))
119+
{
120+
UstarTarEntry entry = new UstarTarEntry(TarEntryType.SymbolicLink, "link");
121+
entry.LinkName = symlinkName;
86122
writer.WriteEntry(entry);
87123
}
88124

89125
archive.Seek(0, SeekOrigin.Begin);
90126

91127
using TempDirectory root = new TempDirectory();
92128

93-
Assert.Throws<IOException>(() => TarFile.ExtractToDirectory(archive, root.Path, overwriteFiles: false));
129+
Exception exception = Record.Exception(() => TarFile.ExtractToDirectory(archive, root.Path, overwriteFiles: true));
130+
Assert.Null(exception);
131+
132+
string symlinkPath = Path.Join(root.Path, "link");
133+
Assert.True(File.Exists(symlinkPath));
94134

95-
Assert.Equal(0, Directory.GetFileSystemEntries(root.Path).Count());
135+
FileInfo? fileInfo = new(symlinkPath);
136+
Assert.Equal(symlinkName, fileInfo.LinkTarget);
96137
}
97138

98139
[ConditionalTheory(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))]

src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,22 +138,65 @@ public async Task ExtractEntry_PodmanImageTarWithRelativeSymlinksPointingInExtra
138138
[InlineData(TarEntryType.HardLink)]
139139
public async Task Extract_LinkEntry_TargetOutsideDirectory_Async(TarEntryType entryType)
140140
{
141+
using TempDirectory tempDirectory = new();
142+
string symlinkName = tempDirectory.GenerateRandomFilePath();
143+
await File.WriteAllTextAsync(symlinkName, new string('x', 100));
144+
141145
await using (MemoryStream archive = new MemoryStream())
142146
{
143147
await using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true))
144148
{
145149
UstarTarEntry entry = new UstarTarEntry(entryType, "link");
146-
entry.LinkName = PlatformDetection.IsWindows ? @"C:\Windows\System32\notepad.exe" : "/usr/bin/nano";
150+
entry.LinkName = symlinkName;
147151
await writer.WriteEntryAsync(entry);
148152
}
149153

150154
archive.Seek(0, SeekOrigin.Begin);
151155

152-
using (TempDirectory root = new TempDirectory())
156+
using TempDirectory root = new TempDirectory();
157+
158+
Exception exception = await Record.ExceptionAsync(() => TarFile.ExtractToDirectoryAsync(archive, root.Path, overwriteFiles: true));
159+
Assert.Null(exception);
160+
161+
string symlinkPath = Path.Join(root.Path, "link");
162+
Assert.True(File.Exists(symlinkPath));
163+
164+
if (entryType is TarEntryType.SymbolicLink)
165+
{
166+
FileInfo? fileInfo = new(symlinkPath);
167+
Assert.Equal(symlinkName, fileInfo.LinkTarget);
168+
}
169+
}
170+
}
171+
172+
[Theory]
173+
[InlineData("foo")]
174+
[InlineData("../../foo")]
175+
[InlineData("/usr/tmp/foo")]
176+
[InlineData(@"C:\tmp\foo")]
177+
public async Task Extract_SymbolicLinkEntryWithExistingOrNonExistingPaths_TargetOutsideDirectoryPreservesOriginalPaths_Async(string symlinkName)
178+
{
179+
await using (MemoryStream archive = new())
180+
{
181+
await using (TarWriter writer = new(archive, TarEntryFormat.Ustar, leaveOpen: true))
153182
{
154-
await Assert.ThrowsAsync<IOException>(() => TarFile.ExtractToDirectoryAsync(archive, root.Path, overwriteFiles: false));
155-
Assert.Equal(0, Directory.GetFileSystemEntries(root.Path).Count());
183+
UstarTarEntry entry = new UstarTarEntry(TarEntryType.SymbolicLink, "link");
184+
entry.LinkName = symlinkName;
185+
await writer.WriteEntryAsync(entry);
156186
}
187+
188+
archive.Seek(0, SeekOrigin.Begin);
189+
190+
using TempDirectory root = new TempDirectory();
191+
192+
Exception exception = await Record.ExceptionAsync(() => TarFile.ExtractToDirectoryAsync(archive, root.Path, overwriteFiles: true));
193+
Assert.Null(exception);
194+
195+
string symlinkPath = Path.Join(root.Path, "link");
196+
Assert.True(File.Exists(symlinkPath));
197+
198+
FileInfo? fileInfo = new(symlinkPath);
199+
Assert.Equal(symlinkName, fileInfo.LinkTarget);
157200
}
158201
}
159202

0 commit comments

Comments
 (0)