Fix delete and rename following symbolic links (Fixes #1099) - #1100
Merged
Conversation
Remove, Rmdir and Rename resolved their path through hostPath, which follows a symbolic link in the final element and returns what it points at. All three acted on that target instead of on the name the client gave: a delete unlinked the target and left the link, and a rename moved the target out from under it, leaving the link dangling. Resolve the name through hostPathNoFollow for those three. It resolves and contains the parent exactly as hostPath does -- which is what keeps the result inside the share -- but leaves the final element alone, since that element is the thing being operated on rather than a component being traversed. Open, Stat and ReadDir keep following links, which is right for operations that act on contents rather than on a name.
…s-follow-symlinks # Conflicts: # network/smb/smb_v10/server/file_service_test.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linked Issue
Closes #1099Root Cause
hostPathdoes two things: it resolves a share-relative path to a host path throughfilepath.EvalSymlinks, and it checks the result lies under the share root. The resolution is what an operation on a file's contents needs — following a link inside the share to the file it names is correct forOpen,StatandReadDir, and the containment check is what makes it safe.The operations that act on a name rather than on contents used the same resolver.
Remove,RmdirandRenametherefore received the link's target and acted on that, so the entry the client named survived and a different one was destroyed or moved. The rename case is the worst of the three: it leaves the original link dangling and turns what was a link into a regular file at the new name.Fix Description
Add
hostPathNoFollow, which resolves and containment-checks the parent exactly ashostPathalready does for a target that does not exist yet, and joins the final element unresolved.Remove,RmdirandRenameuse it for the names they act on;Renameuses it for both ends, so renaming onto an existing link replaces the link rather than writing through it.Containment is unchanged. Every component but the last is still resolved and checked against the root, and a link can only be traversed through a component that exists — the final element is not traversed at all here, it is the object being operated on. The new test asserts this directly:
Remove("escape/secret.txt"), whereescapeis a link out of the share, is still refused and the outside file is untouched.Rmdiron a link to a directory now returnsErrNotDirectory, becauseos.Lstatreports a symbolic link rather than a directory. That matches POSIX, wherermdiron a symlink fails withENOTDIR, and it is a refusal rather than a destructive action.Removeis what unlinks the link itself.How Verified
TestLocalFileSystemNameOperationsDoNotFollowSymlinkscovers all three operations plus the cases that must not regress. Against the unpatched backend it fails on all three — "deleting the link deleted its target", "Rmdir() removed a directory through a symbolic link", "renaming the link moved its target away" — and passes against the patched one.go build ./...,go vet ./...andgo test ./...are clean; 308 packages pass.TestFileServiceDeleteAndRename,TestFileServiceRefusesTraversalandTestLocalFileSystemRoundTripcontinue to pass.Test Coverage
Added:
network/smb/smb_v10/server/file_service_test.go—TestLocalFileSystemNameOperationsDoNotFollowSymlinks, with six subtests: the three corrected operations, deletion of an ordinary file and directory, and a delete reaching through a link that leaves the share.Modified:
TestLocalFileSystemContainsSymlinkEscape— see below.Scope of Change
network/smb/smb_v10/server/fs_local.go,network/smb/smb_v10/server/file_service_test.goRisk and Rollout
TestLocalFileSystemContainsSymlinkEscapeasserted thatRemoveon a link pointing out of the share fails, and after this change it succeeds. That assertion is updated rather than preserved, and the reasoning should be reviewed rather than taken on trust.The old behaviour was not a containment rule that had been chosen; it fell out of resolving the final element, which is the very thing this fix stops doing. It also conflated two different properties. Reaching through a link to something outside the share must be refused, and still is —
Stat,Openand a delete oflinkdir/secret.txtare all refused, and the test still asserts that. Deleting the link entry is a different matter: the entry belongs to this share, and unlinking it removes only that entry.This was checked rather than assumed. With the fix,
Remove("link.txt")andRemove("linkdir")on links pointing outside leave both the outside file and the outside directory intact — only the link entries go. The updated test asserts that the target is untouched after the link is removed.The alternative was to keep refusing, which would leave an entry a client can list and can never delete. Blast radius is limited to shares that contain symbolic links; a share without them behaves identically.
Notes
SetAttrstill resolves throughhostPath, which is correct: it acts on the attributes of the file a path names, not on the name.