@@ -116,11 +116,23 @@ func (s *symlinkStack) PopLastSymlink() (*os.File, string, bool) {
116116 return tailEntry .dir , tailEntry .remainingPath , true
117117}
118118
119+ const maxUnsafeHallucinateDirectoryTries = 20
120+
121+ var errTooManyFakeDirectories = errors .New ("encountered too many non-existent paths" )
122+
119123// partialLookupInRoot tries to lookup as much of the request path as possible
120124// within the provided root (a-la RESOLVE_IN_ROOT) and opens the final existing
121125// component of the requested path, returning a file handle to the final
122126// existing component and a string containing the remaining path components.
123- func partialLookupInRoot (root * os.File , unsafePath string ) (_ * os.File , _ string , Err error ) {
127+ //
128+ // If unsafeHallucinateDirectories is true, partialLookupInRoot will try to
129+ // emulate the legacy SecureJoin behaviour of treating non-existent paths as
130+ // though they are directories to try to resolve as much of the path as
131+ // possible. In practice, this means that a path like "a/b/doesnotexist/../c"
132+ // will end up being resolved as "a/b/c" if possible. Note that dangling
133+ // symlinks (a symlink that points to a non-existent path) will still result in
134+ // an error being returned, due to how openat2 handles symlinks.
135+ func partialLookupInRoot (root * os.File , unsafePath string , unsafeHallucinateDirectories bool ) (_ * os.File , _ string , Err error ) {
124136 unsafePath = filepath .ToSlash (unsafePath ) // noop
125137
126138 // This is very similar to SecureJoin, except that we operate on the
@@ -129,7 +141,7 @@ func partialLookupInRoot(root *os.File, unsafePath string) (_ *os.File, _ string
129141
130142 // Try to use openat2 if possible.
131143 if hasOpenat2 () {
132- return partialLookupOpenat2 (root , unsafePath )
144+ return partialLookupOpenat2 (root , unsafePath , unsafeHallucinateDirectories )
133145 }
134146
135147 // Get the "actual" root path from /proc/self/fd. This is necessary if the
@@ -168,7 +180,9 @@ func partialLookupInRoot(root *os.File, unsafePath string) (_ *os.File, _ string
168180 defer symlinkStack .Close ()
169181
170182 var (
171- linksWalked int
183+ linksWalked int
184+ hallucinateDirectoryTries int
185+
172186 currentPath string
173187 remainingPath = unsafePath
174188 )
@@ -235,7 +249,12 @@ func partialLookupInRoot(root *os.File, unsafePath string) (_ *os.File, _ string
235249 return nil , "" , fmt .Errorf ("root path moved during lookup: %w" , err )
236250 }
237251 // Make sure the path is what we expect.
238- fullPath := logicalRootPath + nextPath
252+ var fullPath string
253+ if logicalRootPath == "/" {
254+ fullPath = nextPath
255+ } else {
256+ fullPath = logicalRootPath + nextPath
257+ }
239258 if err := checkProcSelfFdPath (fullPath , nextDir ); err != nil {
240259 return nil , "" , fmt .Errorf ("walking into .. had unexpected result: %w" , err )
241260 }
@@ -250,6 +269,21 @@ func partialLookupInRoot(root *os.File, unsafePath string) (_ *os.File, _ string
250269 _ = currentDir .Close ()
251270 return oldDir , remainingPath , nil
252271 }
272+ // If we were asked to "hallucinate" non-existent paths as though
273+ // they are directories, take the remainingPath and clean it so
274+ // that any ".." components that would lead us back to real paths
275+ // can get resolved.
276+ if oldRemainingPath != "" && unsafeHallucinateDirectories {
277+ if newRemainingPath := path .Clean (oldRemainingPath ); newRemainingPath != oldRemainingPath {
278+ hallucinateDirectoryTries ++
279+ if hallucinateDirectoryTries > maxUnsafeHallucinateDirectoryTries {
280+ return nil , "" , fmt .Errorf ("%w: trying to reconcile non-existent subpath %q" , errTooManyFakeDirectories , oldRemainingPath )
281+ }
282+ // Continue the lookup using the new remaining path.
283+ remainingPath = newRemainingPath
284+ continue
285+ }
286+ }
253287 // We have hit a final component that doesn't exist, so we have our
254288 // partial open result. Note that we have to use the OLD remaining
255289 // path, since the lookup failed.
0 commit comments