-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
TarEntry: remove some unneeded checks when extracting symbolic and hard links. #85378
Changes from all commits
92538c8
bfbcddd
051c519
3fb26ad
adebf26
d678440
84f9bc1
eaa0b3f
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 |
---|---|---|
|
@@ -330,59 +330,65 @@ internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, b | |
Debug.Assert(!string.IsNullOrEmpty(destinationDirectoryPath)); | ||
Debug.Assert(Path.IsPathFullyQualified(destinationDirectoryPath)); | ||
|
||
destinationDirectoryPath = Path.TrimEndingDirectorySeparator(destinationDirectoryPath); | ||
|
||
string? fileDestinationPath = GetSanitizedFullPath(destinationDirectoryPath, Name); | ||
string name = ArchivingUtils.SanitizeEntryFilePath(Name, preserveDriveRoot: true); | ||
string? fileDestinationPath = GetFullDestinationPath( | ||
destinationDirectoryPath, | ||
Path.IsPathFullyQualified(name) ? name : Path.Join(Path.GetDirectoryName(destinationDirectoryPath), name)); | ||
if (fileDestinationPath == null) | ||
{ | ||
throw new IOException(SR.Format(SR.TarExtractingResultsFileOutside, Name, destinationDirectoryPath)); | ||
throw new IOException(SR.Format(SR.TarExtractingResultsFileOutside, name, destinationDirectoryPath)); | ||
} | ||
|
||
string? linkTargetPath = null; | ||
if (EntryType is TarEntryType.SymbolicLink or TarEntryType.HardLink) | ||
{ | ||
if (string.IsNullOrEmpty(LinkName)) | ||
if (EntryType is TarEntryType.SymbolicLink) | ||
{ | ||
// LinkName is an absolute path, or path relative to the fileDestinationPath directory. | ||
// 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. | ||
string linkName = ArchivingUtils.SanitizeEntryFilePath(LinkName, preserveDriveRoot: true); | ||
string? linkDestination = GetFullDestinationPath( | ||
destinationDirectoryPath, | ||
Path.IsPathFullyQualified(linkName) ? linkName : Path.Join(Path.GetDirectoryName(fileDestinationPath), linkName)); | ||
if (linkDestination is null) | ||
{ | ||
throw new InvalidDataException(SR.TarEntryHardLinkOrSymlinkLinkNameEmpty); | ||
throw new IOException(SR.Format(SR.TarExtractingResultsLinkOutside, linkName, destinationDirectoryPath)); | ||
} | ||
|
||
linkTargetPath = GetSanitizedFullPath(destinationDirectoryPath, | ||
Path.IsPathFullyQualified(LinkName) ? LinkName : Path.Join(Path.GetDirectoryName(fileDestinationPath), LinkName)); | ||
|
||
if (linkTargetPath == null) | ||
// Use the linkName for creating the symbolic link. | ||
linkTargetPath = linkName; | ||
} | ||
else if (EntryType is TarEntryType.HardLink) | ||
{ | ||
// LinkName is path relative to the destinationDirectoryPath. | ||
// We don't check if the LinkName is empty. In that case, creation of the link will fail because a hard link can't target a directory. | ||
string linkName = ArchivingUtils.SanitizeEntryFilePath(LinkName, preserveDriveRoot: false); | ||
string? linkDestination = GetFullDestinationPath( | ||
destinationDirectoryPath, | ||
Path.Join(destinationDirectoryPath, linkName)); | ||
if (linkDestination is null) | ||
{ | ||
throw new IOException(SR.Format(SR.TarExtractingResultsLinkOutside, LinkName, destinationDirectoryPath)); | ||
throw new IOException(SR.Format(SR.TarExtractingResultsLinkOutside, linkName, destinationDirectoryPath)); | ||
} | ||
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. What do we do if the hard link target does not exist at the moment of extraction i.e: on 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. Yes it fails, and that is the proper thing to do. note: .NET's tar writer doesn't detect hard links and treats them like regular files. There is an open issue to handle them: #74404. |
||
|
||
// after TarExtractingResultsLinkOutside validation, preserve the original | ||
// symlink target path (to match behavior of other utilities). | ||
linkTargetPath = LinkName; | ||
// Use the target path for creating the hard link. | ||
linkTargetPath = linkDestination; | ||
} | ||
|
||
return (fileDestinationPath, linkTargetPath); | ||
} | ||
|
||
// If the path can be extracted in the specified destination directory, returns the full path with sanitized file name. Otherwise, returns null. | ||
private static string? GetSanitizedFullPath(string destinationDirectoryFullPath, string path) | ||
// Returns the full destination path if the path is the destinationDirectory or a subpath. Otherwise, returns null. | ||
private static string? GetFullDestinationPath(string destinationDirectoryFullPath, string qualifiedPath) | ||
{ | ||
destinationDirectoryFullPath = PathInternal.EnsureTrailingSeparator(destinationDirectoryFullPath); | ||
Debug.Assert(Path.IsPathFullyQualified(qualifiedPath), $"{qualifiedPath} is not qualified"); | ||
Debug.Assert(PathInternal.EndsInDirectorySeparator(destinationDirectoryFullPath), "caller must ensure the path ends with a separator."); | ||
|
||
string fullyQualifiedPath = Path.IsPathFullyQualified(path) ? path : Path.Combine(destinationDirectoryFullPath, path); | ||
string normalizedPath = Path.GetFullPath(fullyQualifiedPath); // Removes relative segments | ||
string? fileName = Path.GetFileName(normalizedPath); | ||
if (string.IsNullOrEmpty(fileName)) // It's a directory | ||
{ | ||
fileName = PathInternal.DirectorySeparatorCharAsString; | ||
} | ||
string fullPath = Path.GetFullPath(qualifiedPath); // Removes relative segments | ||
|
||
string sanitizedPath = Path.Join(Path.GetDirectoryName(normalizedPath), ArchivingUtils.SanitizeEntryFilePath(fileName)); | ||
return sanitizedPath.StartsWith(destinationDirectoryFullPath, PathInternal.StringComparison) ? sanitizedPath : null; | ||
return fullPath.StartsWith(destinationDirectoryFullPath, PathInternal.StringComparison) ? fullPath : null; | ||
} | ||
|
||
// Extracts the current entry into the filesystem, regardless of the entry type. | ||
private void ExtractToFileInternal(string filePath, string? linkTargetPath, bool overwrite) | ||
{ | ||
VerifyPathsForEntryType(filePath, linkTargetPath, overwrite); | ||
VerifyDestinationPath(filePath, overwrite); | ||
|
||
if (EntryType is TarEntryType.RegularFile or TarEntryType.V7RegularFile or TarEntryType.ContiguousFile) | ||
{ | ||
|
@@ -401,7 +407,7 @@ private Task ExtractToFileInternalAsync(string filePath, string? linkTargetPath, | |
{ | ||
return Task.FromCanceled(cancellationToken); | ||
} | ||
VerifyPathsForEntryType(filePath, linkTargetPath, overwrite); | ||
VerifyDestinationPath(filePath, overwrite); | ||
|
||
if (EntryType is TarEntryType.RegularFile or TarEntryType.V7RegularFile or TarEntryType.ContiguousFile) | ||
{ | ||
|
@@ -423,7 +429,7 @@ private void CreateNonRegularFile(string filePath, string? linkTargetPath) | |
case TarEntryType.Directory: | ||
case TarEntryType.DirectoryList: | ||
// Mode must only be used for the leaf directory. | ||
// VerifyPathsForEntryType ensures we're only creating a leaf. | ||
// VerifyDestinationPath ensures we're only creating a leaf. | ||
Debug.Assert(Directory.Exists(Path.GetDirectoryName(filePath))); | ||
Debug.Assert(!Directory.Exists(filePath)); | ||
|
||
|
@@ -476,8 +482,8 @@ private void CreateNonRegularFile(string filePath, string? linkTargetPath) | |
} | ||
} | ||
|
||
// Verifies if the specified paths make sense for the current type of entry. | ||
private void VerifyPathsForEntryType(string filePath, string? linkTargetPath, bool overwrite) | ||
// Verifies there's a writable destination. | ||
private static void VerifyDestinationPath(string filePath, bool overwrite) | ||
tmds marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
string? directoryPath = Path.GetDirectoryName(filePath); | ||
// If the destination contains a directory segment, need to check that it exists | ||
|
@@ -503,35 +509,6 @@ private void VerifyPathsForEntryType(string filePath, string? linkTargetPath, bo | |
throw new IOException(SR.Format(SR.IO_AlreadyExists_Name, filePath)); | ||
} | ||
File.Delete(filePath); | ||
|
||
if (EntryType is TarEntryType.SymbolicLink or TarEntryType.HardLink) | ||
tmds marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
if (!string.IsNullOrEmpty(linkTargetPath)) | ||
{ | ||
string? targetDirectoryPath = Path.GetDirectoryName(linkTargetPath); | ||
// If the destination target contains a directory segment, need to check that it exists | ||
if (!string.IsNullOrEmpty(targetDirectoryPath) && !Path.Exists(targetDirectoryPath)) | ||
{ | ||
throw new IOException(SR.Format(SR.TarSymbolicLinkTargetNotExists, filePath, linkTargetPath)); | ||
} | ||
|
||
if (EntryType is TarEntryType.HardLink) | ||
{ | ||
if (!Path.Exists(linkTargetPath)) | ||
{ | ||
throw new IOException(SR.Format(SR.TarHardLinkTargetNotExists, filePath, linkTargetPath)); | ||
} | ||
else if (Directory.Exists(linkTargetPath)) | ||
{ | ||
throw new IOException(SR.Format(SR.TarHardLinkToDirectoryNotAllowed, filePath, linkTargetPath)); | ||
} | ||
} | ||
} | ||
else | ||
{ | ||
throw new InvalidDataException(SR.TarEntryHardLinkOrSymlinkLinkNameEmpty); | ||
} | ||
} | ||
} | ||
|
||
// Extracts the current entry as a regular file into the specified destination. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can we assume that LinkName will never be an absolute path for hard links?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're using
Path.Join
to interpret theLinkName
as a relative path.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Someone could use a
TarWriter
to manually write a Hard Link entry with LinkName = "C:\my\absolute\path". I don't think is something worth validating because likely no tools would produce it but is something to have in mind.