Follow links safely during directory discovery
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type importSelectionOutcome uint8
|
||||
@@ -106,6 +107,10 @@ func selectDirectoryImportCandidates(access *filesystemAccess, basePath string,
|
||||
if !isDirectory {
|
||||
return importSelection{}, []Diagnostic{*importSelectionDiagnostic(root.site, CodeImportInvalid, "directory search root is not a directory")}
|
||||
}
|
||||
rootTarget, targetErr := access.importEntryTarget(root.accessPath, nil, true)
|
||||
if targetErr != nil {
|
||||
return importSelection{}, []Diagnostic{*importDirectoryTargetDiagnostic(root.site, targetErr)}
|
||||
}
|
||||
entries, missing, diagnostic := importDirectoryEntries(access, root.accessPath, root.site, limits, accounting)
|
||||
if diagnostic != nil {
|
||||
return importSelection{}, []Diagnostic{*diagnostic}
|
||||
@@ -113,11 +118,13 @@ func selectDirectoryImportCandidates(access *filesystemAccess, basePath string,
|
||||
if missing {
|
||||
continue
|
||||
}
|
||||
stack := []importDirectoryFrame{{directory: root, depth: 0, entries: entries}}
|
||||
stack := []importDirectoryFrame{{directory: root, depth: 0, entries: entries, identity: rootTarget.identity}}
|
||||
activeDirectories := map[string]struct{}{rootTarget.identity: {}}
|
||||
for len(stack) != 0 {
|
||||
frameIndex := len(stack) - 1
|
||||
frame := &stack[frameIndex]
|
||||
if frame.next >= len(frame.entries) {
|
||||
delete(activeDirectories, frame.identity)
|
||||
stack = stack[:frameIndex]
|
||||
continue
|
||||
}
|
||||
@@ -127,14 +134,26 @@ func selectDirectoryImportCandidates(access *filesystemAccess, basePath string,
|
||||
candidate := selectedImportCandidate{
|
||||
path: normalizedImportCandidatePath(access, accessPath), accessPath: accessPath, site: frame.directory.site,
|
||||
}
|
||||
isDirectory, entryErr := importDirectoryEntryIsDirectory(entry)
|
||||
if entryErr != nil {
|
||||
return importSelection{}, []Diagnostic{*importSelectionDiagnostic(frame.directory.site, CodeImportInvalid, "directory entry could not be inspected")}
|
||||
if !policy.hidden && strings.HasPrefix(entry.Name(), ".") {
|
||||
continue
|
||||
}
|
||||
if isDirectory {
|
||||
target, entryErr := access.importEntryTarget(candidate.accessPath, entry, policy.follow)
|
||||
if entryErr != nil {
|
||||
if errors.Is(entryErr, fs.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
return importSelection{}, []Diagnostic{*importDirectoryTargetDiagnostic(frame.directory.site, entryErr)}
|
||||
}
|
||||
if target.symlink && !policy.follow {
|
||||
continue
|
||||
}
|
||||
if target.dir {
|
||||
if !directoryDepthAllows(policy, frame.depth) {
|
||||
continue
|
||||
}
|
||||
if _, exists := activeDirectories[target.identity]; exists {
|
||||
return importSelection{}, []Diagnostic{*importSelectionDiagnostic(frame.directory.site, CodeImportCycle, "directory traversal cycle detected")}
|
||||
}
|
||||
childMissing, childIsDirectory, childDiagnostic := validateNamedImportTarget(access, candidate, false, true)
|
||||
if childDiagnostic != nil {
|
||||
return importSelection{}, []Diagnostic{*childDiagnostic}
|
||||
@@ -147,7 +166,8 @@ func selectDirectoryImportCandidates(access *filesystemAccess, basePath string,
|
||||
return importSelection{}, []Diagnostic{*childDiagnostic}
|
||||
}
|
||||
if !vanished {
|
||||
stack = append(stack, importDirectoryFrame{directory: candidate, depth: frame.depth + 1, entries: childEntries})
|
||||
activeDirectories[target.identity] = struct{}{}
|
||||
stack = append(stack, importDirectoryFrame{directory: candidate, depth: frame.depth + 1, entries: childEntries, identity: target.identity})
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -188,6 +208,17 @@ type importDirectoryFrame struct {
|
||||
depth int
|
||||
entries []fs.DirEntry
|
||||
next int
|
||||
identity string
|
||||
}
|
||||
|
||||
func importDirectoryTargetDiagnostic(site importFieldSite, err error) *Diagnostic {
|
||||
if isImportLinkCycle(err) {
|
||||
return importSelectionDiagnostic(site, CodeImportCycle, "directory traversal cycle detected")
|
||||
}
|
||||
if errors.Is(err, errImportPathOutsideRoot) {
|
||||
return importSelectionDiagnostic(site, CodeImportOutsideRoot, "import target resolves outside the configuration root")
|
||||
}
|
||||
return importSelectionDiagnostic(site, CodeImportInvalid, "directory entry could not be inspected")
|
||||
}
|
||||
|
||||
func importDirectoryEntries(access *filesystemAccess, directory string, site importFieldSite, limits importLimits, accounting *importAccounting) ([]fs.DirEntry, bool, *Diagnostic) {
|
||||
|
||||
Reference in New Issue
Block a user