mirror of
https://github.com/junegunn/fzf.git
synced 2026-03-23 14:47:02 +08:00
Skip symlinks targeting ancestors of walker root to prevent resource exhaustion
When --walker=follow is used, symlinks like Wine's z: -> / cause fzf to traverse the entire root filesystem. fastwalk's built-in loop detection only catches this on the second pass, but a single pass through / already causes severe CPU and memory exhaustion. This fix resolves each symlink-to-directory target to its absolute real path and skips it if it is an ancestor of (or equal to) the walker root. Close #4710
This commit is contained in:
@@ -27,6 +27,7 @@ CHANGELOG
|
||||
- With the reduced per-entry cost, the cache now has broader coverage.
|
||||
- fish: Improved command history (CTRL-R) (#4703) (@bitraid)
|
||||
- Bug fixes
|
||||
- `--walker=follow` no longer follows symlinks whose target is an ancestor of the walker root, avoiding severe resource exhaustion when a symlink points outside the tree (e.g. Wine's `z:` → `/`) (#4710)
|
||||
- Fixed AWK tokenizer not treating a new line character as whitespace
|
||||
- Fixed `--{accept,with}-nth` removing trailing whitespaces with a non-default `--delimiter`
|
||||
- Fixed OSC8 hyperlinks being mangled when the URL contains unicode characters (#4707)
|
||||
|
||||
@@ -274,6 +274,24 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
|
||||
ToSlash: fastwalk.DefaultToSlash(),
|
||||
Sort: fastwalk.SortFilesFirst,
|
||||
}
|
||||
|
||||
// When following symlinks, precompute the absolute real paths of walker
|
||||
// roots so we can skip symlinks that point to an ancestor. fastwalk's
|
||||
// built-in loop detection (shouldTraverse) catches loops on the second
|
||||
// pass, but a single pass through a symlink like z: -> / already
|
||||
// traverses the entire root filesystem, causing severe resource
|
||||
// exhaustion. Skipping ancestor symlinks prevents this entirely.
|
||||
var absRoots []string
|
||||
if opts.follow {
|
||||
for _, root := range roots {
|
||||
if real, err := filepath.EvalSymlinks(root); err == nil {
|
||||
if abs, err := filepath.Abs(real); err == nil {
|
||||
absRoots = append(absRoots, filepath.Clean(abs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ignoresBase := []string{}
|
||||
ignoresFull := []string{}
|
||||
ignoresSuffix := []string{}
|
||||
@@ -307,6 +325,24 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
|
||||
if isDirSymlink && !opts.follow {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
// Skip symlinks whose target is an ancestor of (or equal to)
|
||||
// any walker root. Following such symlinks would traverse a
|
||||
// superset of the tree we're already walking.
|
||||
if isDirSymlink && len(absRoots) > 0 {
|
||||
if target, err := filepath.EvalSymlinks(path); err == nil {
|
||||
if abs, err := filepath.Abs(target); err == nil {
|
||||
abs = filepath.Clean(abs)
|
||||
if abs == string(os.PathSeparator) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
for _, absRoot := range absRoots {
|
||||
if absRoot == abs || strings.HasPrefix(absRoot, abs+string(os.PathSeparator)) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
isDir := de.IsDir() || isDirSymlink
|
||||
if isDir {
|
||||
base := filepath.Base(path)
|
||||
|
||||
Reference in New Issue
Block a user