vfs: stop --links objects called "...rclonelink" appearing as ".." in directory listings

When reading a directory with --links the VFS skipped entries called "."
and ".." before removing the ".rclonelink" suffix, so an object called
"...rclonelink" (or "..rclonelink" or ".rclonelink") produced a directory
entry called ".." (or "." or "") whose path resolved to the parent
directory. The same happened for names added back from the VFS cache.

The check is now done after the suffix is removed.

See GHSA-55qp-jrwr-x827
This commit is contained in:
Nick Craig-Wood committed 2026-10-08 22:49:33 +01:00
1 parent 4d1bd4c71d
commit 93ba2d40f0
2 files changed
+44 -3

No files matched your search

+8 -3
View File
@@ -478,6 +478,9 @@ func (d *Dir) AddVirtual(leaf string, size int64, isDir bool) {
// since the path came from the cache it may have fs.LinkSuffix,
// so remove it and mark the *File accordingly
leaf, isLink = strings.CutSuffix(leaf, fs.LinkSuffix)
if leaf == "" || leaf == "." || leaf == ".." {
return
}
}
f := newFile(d, dPath, nil, leaf)
if isLink {
@@ -724,12 +727,14 @@ func (d *Dir) _readDirFromEntries(entries fs.DirEntries, dirTree dirtree.DirTree
mv := d._newManageVirtuals()
for _, entry := range entries {
name := path.Base(entry.Remote())
if name == "." || name == ".." {
continue
}
if d.vfs.Opt.Links {
name, _ = strings.CutSuffix(name, fs.LinkSuffix)
}
// Check after removing the suffix so "...rclonelink" can't
// become an entry called ".."
if name == "" || name == "." || name == ".." {
continue
}
node := d.items[name]
if mv.add(d, name) {
continue
+36
View File
@@ -16,6 +16,7 @@ import (
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/operations"
"github.com/rclone/rclone/fstest"
"github.com/rclone/rclone/vfs/vfscommon"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -33,6 +34,41 @@ func dirCreate(t *testing.T) (r *fstest.Run, vfs *VFS, dir *Dir, item fstest.Ite
return r, vfs, node.(*Dir), file1
}
// TestDirLinksInvalidName checks that with --links an object called
// "...rclonelink" doesn't become a directory entry called "..", which
// would address the parent, whether it comes from a listing or from
// the cache.
func TestDirLinksInvalidName(t *testing.T) {
opt := vfscommon.Opt
opt.Links = true
r, vfs := newTestVFSOpt(t, &opt)
file1 := r.WriteObject(context.Background(), "dir/.."+fs.LinkSuffix, "target", t1)
file2 := r.WriteObject(context.Background(), "dir/file1", "file1 contents", t1)
r.CheckRemoteItems(t, file1, file2)
node, err := vfs.Stat("dir")
require.NoError(t, err)
dir := node.(*Dir)
checkEntries := func() {
t.Helper()
entries, err := dir.ReadDirAll()
require.NoError(t, err)
var names []string
for _, entry := range entries {
names = append(names, entry.Name())
}
assert.Equal(t, []string{"file1"}, names)
}
checkEntries()
for _, leaf := range []string{".." + fs.LinkSuffix, "." + fs.LinkSuffix, fs.LinkSuffix} {
dir.AddVirtual(leaf, 6, false)
}
checkEntries()
}
func TestDirMethods(t *testing.T) {
_, vfs, dir, _ := dirCreate(t)