Skip to content

Fix ResolveLinkTarget for intermediate symlinks - #132042

Open
iamhaseebn wants to merge 5 commits into
dotnet:mainfrom
iamhaseebn:fix-resolvelinktarget-intermediate-symlinks
Open

Fix ResolveLinkTarget for intermediate symlinks#132042
iamhaseebn wants to merge 5 commits into
dotnet:mainfrom
iamhaseebn:fix-resolvelinktarget-intermediate-symlinks

Conversation

@iamhaseebn

Copy link
Copy Markdown

Fixes#118719

ResolveLinkTarget(..., returnFinalTarget: true) followed a symlink chain only when each complete current path was itself a symbolic link. Kubernetes ConfigMap layouts use an intermediate ..data directory symlink, so resolution stopped before reaching the final target.

This change:

  • Resolves relative targets component by component so intermediate symbolic links are followed.
  • Preserves lexical absolute targets and dangling final targets.
  • Retains the existing 40-link traversal limit.
  • Adds shared regression coverage for File, Directory, FileInfo, and DirectoryInfo.

Validation:

  • CoreLib build completed with zero warnings and zero errors.
  • System.IO.FileSystem tests: 9,618 passed, 0 failed, and 59 expected skips.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Aug 8, 2026
@iamhaseebn

Copy link
Copy Markdown
Author

@dotnet-policy-service agree.

@iamhaseebn

Copy link
Copy Markdown
Author

@dotnet-policy-service agree

@teo-tsirpanis

Copy link
Copy Markdown
Contributor

I wonder if this would solve the failures in dotnet/msbuild#14482.

@iamhaseebn

Copy link
Copy Markdown
Author

Thanks for the pointer. I checked the failing Linux and macOS tests in dotnet/msbuild#14482. This change would not fix them: it follows intermediate links after the input path itself has been identified as a symbolic link. In #14482, File.ResolveLinkTarget(Directory.GetCurrentDirectory(), true) is called on the already-resolved physical directory, so it returns null before that traversal is reached. ResolveLinkTarget is therefore not a general realpath replacement for that non-link input.

@jozkee

Copy link
Copy Markdown
Member

On Windows, we try to open the final target and use GetFinalPathNameByHandle. If the target is unreachable, we fall back to manual traversal to preserve dangling-target behavior.

What do you think about following the same approach on Unix—first trying realpath() and then falling back to manual traversal for unreachable targets? This would resolve intermediate symlinks and better align the two implementations.

cc @tmds

@iamhaseebn

Copy link
Copy Markdown
Author

Thanks - updated the Unix path to use realpath() first, with managed traversal fallback for unreachable targets and platform-specific symlink limits, in 5a86c11.

@jeffhandleyjeffhandley added this to the 11.0.0 milestone Aug 16, 2026
@jeffhandley

Copy link
Copy Markdown
Member

@jozkee -- assigning this to you to drive in for .NET 11 RC2

@jozkeejozkee modified the milestones: 11.0.0, 12.0.0Aug 17, 2026
@jozkee

Copy link
Copy Markdown
Member

I'm moving this to 12.0, please see #118719 (comment).

@tmds

tmds commented Aug 24, 2026

Copy link
Copy Markdown
Member

ResolveLinkTarget(..., returnFinalTarget: true)

For returnFinalTarget: false there is also an issue.

vardir=Directory.CreateTempSubdirectory();Directory.CreateDirectory($"{dir}/deep/deeper");Directory.CreateSymbolicLink($"{dir}/deep/deeper/link","../deeper");Directory.CreateSymbolicLink($"{dir}/a","deep/deeper");foreach(varfollowinnew[]{true,false}){vartarget=newDirectoryInfo($"{dir}/a/link").ResolveLinkTarget(follow);Console.WriteLine(target!.Exists);}

With the changes from this PR, this prints:

True
False

The problem is that the FileSystemInfo ends up with a subpath like a/.. for which the full path is '' but the resolved path is deep. (Except for ToString and Name) the FSI members work on the full path.

From a use-case perspective, I don't think users actually want/need returnFinalTarget: false. So rather than trying to fix it, it may be an option to obsolete the method and add an overload which accepts no argument (and has returnFinalTarget: true behavior).

@tmds

tmds commented Aug 24, 2026

Copy link
Copy Markdown
Member

The problem is that the FileSystemInfo ends up with a subpath like a/.. for which the full path is '' but the resolved path is deep. (Except for ToString and Name) the FSI members work on the full path.

Also for returnFinalTarget: true, a non-existing path might cause a subpath like a/.. for the final resolved path which can then cause the same problem. It might be good to also have the realpath semantics of returning null when the target does not exist.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.IOcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

File.ResolveLinkTarget(String, Boolean) doesn't fully resolve a symbolic link created by Kubernetes volume mounts to its full path

5 participants

@iamhaseebn@teo-tsirpanis@jozkee@jeffhandley@tmds