fix:Resolve ancestral symlinks correctly (#3783)

* Resolve upstream symlinks correctly

Signed-off-by: Yuntao Hu <victorhu493@gmail.com>

* in case of the root directory

Signed-off-by: Yuntao Hu <victorhu493@gmail.com>

* for static analysis check pass

Signed-off-by: Yuntao Hu <victorhu493@gmail.com>

* add unit test cases for the symlink scenarios

Signed-off-by: Yuntao Hu <victorhu493@gmail.com>

---------

Signed-off-by: Yuntao Hu <victorhu493@gmail.com>
This commit is contained in:
VictorHuu 2025-05-01 02:47:32 +08:00 committed by GitHub
parent 6dca10fe1f
commit 09c3b7cbea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 78 additions and 2 deletions

View File

@ -379,6 +379,28 @@ func (r directoryIndexer) addSymlinkToIndex(p string, info os.FileInfo) (string,
// if the base is set, then we first need to resolve the link,
// before finding it's location in the base
dir, err := filepath.Rel(r.base, filepath.Dir(p))
// if the relative path to the base contains "..",i.e. p is the parent or ancestor of the base
// For example:
// dir: "/root/asymlink" -> "/root/realdir" (linkTarget:"realdir")
// base: "/root/asymlink"
// so the relative path of /root to the "/root/asymlink" is ".."
// we cannot directly concatenate ".." to "/root/symlink",however,
// the parent directory of linkTarget should be "/root"
for strings.HasPrefix(dir, "..") {
if strings.HasPrefix(dir, "../") {
dir = strings.TrimPrefix(dir, "../")
} else {
dir = strings.TrimPrefix(dir, "..")
}
lastSlash := strings.LastIndex(r.base, "/")
if lastSlash != -1 {
r.base = r.base[:lastSlash]
}
// In case of the root directory
if r.base == "" {
r.base = "/"
}
}
if err != nil {
return "", fmt.Errorf("unable to resolve relative path for path=%q: %w", p, err)
}

View File

@ -1,10 +1,12 @@
package fileresolver
import (
"fmt"
"io/fs"
"os"
"path"
"path/filepath"
"runtime"
"sort"
"strings"
"testing"
@ -223,6 +225,55 @@ func TestDirectoryIndexer_index(t *testing.T) {
}
}
func TestDirectoryIndexer_index_for_AncestorSymlinks(t *testing.T) {
// note: this test is testing the effects from NewFromDirectory, indexTree, and addPathToIndex
_, filename, _, ok := runtime.Caller(0)
require.True(t, ok)
dir := filepath.Dir(filename)
tests := []struct {
name string
path string
relative_base string
}{
{
name: "the parent directory has symlink target",
path: "test-fixtures/system_paths/target/symlinks-to-dev",
relative_base: "test-fixtures/system_paths/target/symlinks-to-dev",
},
{
name: "the ancestor directory has symlink target",
path: "test-fixtures/system_paths/target/symlinks-to-hierarchical-dev",
relative_base: "test-fixtures/system_paths/target/symlinks-to-hierarchical-dev/module_1/module_1_1",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
indexer := newDirectoryIndexer("test-fixtures/system_paths/target",
fmt.Sprintf("%v/%v", dir, test.relative_base))
tree, index, err := indexer.build()
require.NoError(t, err)
info, err := os.Stat(test.path)
assert.NoError(t, err)
// note: the index uses absolute paths, so assertions MUST keep this in mind
cwd, err := os.Getwd()
require.NoError(t, err)
p := file.Path(path.Join(cwd, test.path))
assert.Equal(t, true, tree.HasPath(p))
exists, ref, err := tree.File(p)
assert.Equal(t, true, exists)
if assert.NoError(t, err) {
return
}
entry, err := index.Get(*ref.Reference)
require.NoError(t, err)
assert.Equal(t, info.Mode(), entry.Mode)
})
}
}
func TestDirectoryIndexer_index_survive_badSymlink(t *testing.T) {
// test-fixtures/bad-symlinks
// ├── root

View File

@ -811,7 +811,7 @@ func TestDirectoryResolverDoesNotIgnoreRelativeSystemPaths(t *testing.T) {
// 4: within target/
// 1: target/link --> relative path to "place" // NOTE: this is filtered out since it not unique relative to outside_root/link_target/place
// 1: outside_root/link_target/place
assert.Len(t, locations, 5)
assert.Len(t, locations, 6)
// ensure that symlink indexing outside of root worked
testLocation := "test-fixtures/system_paths/outside_root/link_target/place"

View File

@ -833,7 +833,7 @@ func Test_UnindexedDirectoryResolverDoesNotIgnoreRelativeSystemPaths(t *testing.
// 4: within target/
// 1: target/link --> relative path to "place" // NOTE: this is filtered out since it not unique relative to outside_root/link_target/place
// 1: outside_root/link_target/place
assert.Len(t, locations, 5)
assert.Len(t, locations, 6)
// ensure that symlink indexing outside of root worked
testLocation := "../outside_root/link_target/place"