mirror of
https://github.com/kopia/kopia.git
synced 2026-04-26 17:09:07 -04:00
fix(general): prevent infinite loop while resolving ignore file symlinks (#4413)
* test symlink infinite loop * remove spurious error wrap * cleanup error messages * remove unneeded intermediate vars * check error in loop in fs.GetAllEntries While cur is expected to be `nil` when there's an error, this makes the check explicit. Ref: - #2037 - #4190
This commit is contained in:
10
fs/entry.go
10
fs/entry.go
@@ -72,19 +72,15 @@ type Directory interface {
|
||||
func IterateEntries(ctx context.Context, dir Directory, cb func(context.Context, Entry) error) error {
|
||||
iter, err := dir.Iterate(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "in fs.IterateEntries, creating iterator for directory %s", dir.Name())
|
||||
return errors.Wrapf(err, "cannot iterate directory '%q'", dir.Name())
|
||||
}
|
||||
|
||||
defer iter.Close()
|
||||
|
||||
cur, err := iter.Next(ctx)
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err, "in fs.IterateEntries, on first iteration")
|
||||
}
|
||||
|
||||
for cur != nil {
|
||||
if err2 := cb(ctx, cur); err2 != nil {
|
||||
return errors.Wrapf(err2, "in fs.IterateEntries, while calling callback on file %s", cur.Name())
|
||||
return errors.Wrapf(err2, "callback failed on '%q'", cur.Name())
|
||||
}
|
||||
|
||||
cur, err = iter.Next(ctx)
|
||||
@@ -136,7 +132,7 @@ func GetAllEntries(ctx context.Context, d Directory) ([]Entry, error) {
|
||||
defer iter.Close()
|
||||
|
||||
cur, err := iter.Next(ctx)
|
||||
for cur != nil {
|
||||
for err == nil && cur != nil {
|
||||
entries = append(entries, cur)
|
||||
cur, err = iter.Next(ctx)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
var (
|
||||
log = logging.Module("ignorefs")
|
||||
errSymlinkNotAFile = errors.New("Symlink does not link to a file")
|
||||
errTooManySymlinks = errors.New("too many levels of symbolic links")
|
||||
)
|
||||
|
||||
// IgnoreCallback is a function called by ignorefs to report whenever a file or directory is being ignored while listing its parent.
|
||||
@@ -188,12 +189,12 @@ func (d *ignoreDirectory) Iterate(ctx context.Context) (fs.DirectoryIterator, er
|
||||
|
||||
thisContext, err := d.buildContext(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "in ignoreDirectory.Iterate, when building context")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inner, err := d.Directory.Iterate(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "in ignoreDirectory.Iterate, when creating iterator")
|
||||
return nil, errors.Wrap(err, "cannot create iterator")
|
||||
}
|
||||
|
||||
it := ignoreDirIteratorPool.Get().(*ignoreDirIterator) //nolint:forcetypeassert
|
||||
@@ -275,7 +276,9 @@ func (d *ignoreDirectory) Child(ctx context.Context, name string) (fs.Entry, err
|
||||
}
|
||||
|
||||
func resolveSymlink(ctx context.Context, entry fs.Symlink) (fs.File, error) {
|
||||
for {
|
||||
const maxSymlinkFollow = 30
|
||||
|
||||
for range maxSymlinkFollow {
|
||||
target, err := entry.Resolve(ctx)
|
||||
if err != nil {
|
||||
link, _ := entry.Readlink(ctx)
|
||||
@@ -292,6 +295,8 @@ func resolveSymlink(ctx context.Context, entry fs.Symlink) (fs.File, error) {
|
||||
return nil, errors.Wrapf(errSymlinkNotAFile, "%s does not eventually link to a file", entry.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.Wrapf(errTooManySymlinks, "cannot resolve '%q'", entry.Name())
|
||||
}
|
||||
|
||||
func (d *ignoreDirectory) buildContext(ctx context.Context) (*ignoreContext, error) {
|
||||
|
||||
31
fs/ignorefs/resolve_test.go
Normal file
31
fs/ignorefs/resolve_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package ignorefs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/kopia/kopia/fs"
|
||||
"github.com/kopia/kopia/internal/mockfs"
|
||||
"github.com/kopia/kopia/internal/testlogging"
|
||||
)
|
||||
|
||||
func TestNoInfiniteResolveLink(t *testing.T) {
|
||||
root := mockfs.NewDirectory()
|
||||
|
||||
root.AddSymlink("a", "./b", 0)
|
||||
root.AddSymlink("b", "./c", 0)
|
||||
root.AddSymlink("c", "./a", 0)
|
||||
|
||||
ctx := testlogging.Context(t)
|
||||
e, err := root.Child(ctx, "b")
|
||||
require.NoError(t, err)
|
||||
|
||||
s, ok := e.(fs.Symlink)
|
||||
require.True(t, ok)
|
||||
|
||||
f, err := resolveSymlink(ctx, s)
|
||||
|
||||
require.ErrorIs(t, err, errTooManySymlinks)
|
||||
require.Nil(t, f)
|
||||
}
|
||||
@@ -120,12 +120,10 @@ func (fsl *filesystemSymlink) Readlink(ctx context.Context) (string, error) {
|
||||
func (fsl *filesystemSymlink) Resolve(ctx context.Context) (fs.Entry, error) {
|
||||
target, err := filepath.EvalSymlinks(fsl.fullPath())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "while reading symlink %s", fsl.fullPath())
|
||||
return nil, errors.Wrapf(err, "cannot resolve symlink for '%q'", fsl.fullPath())
|
||||
}
|
||||
|
||||
entry, err := NewEntry(target)
|
||||
|
||||
return entry, err
|
||||
return NewEntry(target)
|
||||
}
|
||||
|
||||
func (e *filesystemErrorEntry) ErrorInfo() error {
|
||||
|
||||
Reference in New Issue
Block a user