Skip to content

Commit 3016f90

Browse files
committed
Fix symlink traversal cycle guard (#1711)
1 parent a29ac30 commit 3016f90

2 files changed

Lines changed: 64 additions & 1 deletion

File tree

src/CodeIndex/Indexer/Scanning/FileIndexer.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1365,6 +1365,31 @@ private bool ShouldSkipDirectoryLink(string subDir, List<ScanError> errors, Hash
13651365
return true;
13661366
}
13671367

1368+
private static string GetDirectoryTraversalIdentity(string directory)
1369+
{
1370+
if (!IsReparsePoint(directory))
1371+
return directory;
1372+
1373+
try
1374+
{
1375+
DirectoryInfo info = new(LongPath.EnsureWindowsPrefix(directory));
1376+
var target = info.ResolveLinkTarget(returnFinalTarget: true);
1377+
if (target?.FullName is { Length: > 0 } targetPath)
1378+
return targetPath;
1379+
}
1380+
catch (FileNotFoundException)
1381+
{
1382+
}
1383+
catch (DirectoryNotFoundException)
1384+
{
1385+
}
1386+
catch (IOException)
1387+
{
1388+
}
1389+
1390+
return directory;
1391+
}
1392+
13681393
internal static FileProbeStatus GetFileIndexability(string filePath)
13691394
{
13701395
if (OperatingSystem.IsWindows() && IsWindowsDevicePath(filePath))
@@ -2074,7 +2099,7 @@ private bool EnumerateDirectory(
20742099
continue;
20752100
}
20762101

2077-
var resolvedSubDir = NormalizePathForComparison(subDir);
2102+
var resolvedSubDir = NormalizePathForComparison(GetDirectoryTraversalIdentity(subDir));
20782103
if (!visitedDirectories.Add(resolvedSubDir))
20792104
{
20802105
var subRelative = ToRelativePath(subDir);

tests/CodeIndex.Tests/FileIndexerTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3136,6 +3136,44 @@ public void ScanFiles_FollowSymlinksInternal_FollowsInTreeDirectorySymlinkOnce()
31363136
}
31373137
}
31383138

3139+
[Fact]
3140+
public void ScanFiles_FollowSymlinksInternal_SkipsCycleToProjectRoot()
3141+
{
3142+
if (OperatingSystem.IsWindows())
3143+
return;
3144+
3145+
var tempDir = Path.Combine(Path.GetTempPath(), $"cdidx-symlink-cycle-{Guid.NewGuid():N}");
3146+
Directory.CreateDirectory(tempDir);
3147+
try
3148+
{
3149+
File.WriteAllText(Path.Combine(tempDir, "app.py"), "print('app')\n");
3150+
Directory.CreateSymbolicLink(Path.Combine(tempDir, "self"), tempDir);
3151+
3152+
var indexer = new FileIndexer(
3153+
tempDir,
3154+
ignoreCase: false,
3155+
ignoreRuleRoot: null,
3156+
maxFileSizeBytes: null,
3157+
directoryIgnoreCaseProbe: null,
3158+
symlinkPolicy: FileIndexer.SymlinkPolicy.Internal);
3159+
3160+
var result = indexer.ScanFilesDetailed();
3161+
3162+
Assert.Single(result.Files);
3163+
Assert.Contains(
3164+
result.Errors,
3165+
error => error.Path == "self"
3166+
&& error.Severity == FileIndexer.ScanIssueSeverity.Warning
3167+
&& error.Message.Contains("already scanned", StringComparison.OrdinalIgnoreCase));
3168+
Assert.False(result.HadErrors);
3169+
}
3170+
finally
3171+
{
3172+
if (Directory.Exists(tempDir))
3173+
Directory.Delete(tempDir, true);
3174+
}
3175+
}
3176+
31393177
[Fact]
31403178
public void ScanFiles_DescendsIntoSubmoduleHostedUnderSkipDir()
31413179
{

0 commit comments

Comments
 (0)