From bcc51de516e0de8b1495c7c01d0d2475c4e7fde7 Mon Sep 17 00:00:00 2001 From: Mohamadreza Nakhleh Date: Sun, 23 Nov 2025 21:35:22 +0330 Subject: [PATCH 1/4] fix: CreateSubdirectory failing for root directories Path.TrimEndingDirectorySeparator preserves trailing separators for root paths like "C:\" or "/", causing boundary validation to check the wrong index. Added explicit trim of trailing separator from trimmedCurrentPath to fix the boundary check for root directory cases. Signed-off-by: Mohamadreza Nakhleh --- .../System.Private.CoreLib/src/System/IO/DirectoryInfo.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/DirectoryInfo.cs b/src/libraries/System.Private.CoreLib/src/System/IO/DirectoryInfo.cs index 835ab22793730c..bf6e206dfb0d84 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/DirectoryInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/DirectoryInfo.cs @@ -85,6 +85,12 @@ public DirectoryInfo CreateSubdirectory(string path) ReadOnlySpan trimmedNewPath = Path.TrimEndingDirectorySeparator(newPath.AsSpan()); ReadOnlySpan trimmedCurrentPath = Path.TrimEndingDirectorySeparator(FullPath.AsSpan()); + // If trimmedCurrentPath still has a trailing separator (root directory case like "C:\" or "/"), + // remove it for proper boundary checking + if (trimmedCurrentPath.Length > 0 && IsDirectorySeparator(trimmedCurrentPath[trimmedCurrentPath.Length - 1])) + { + trimmedCurrentPath = trimmedCurrentPath.Slice(0, trimmedCurrentPath.Length - 1); + } // We want to make sure the requested directory is actually under the subdirectory. if (trimmedNewPath.StartsWith(trimmedCurrentPath, PathInternal.StringComparison) // Allow the exact same path, but prevent allowing "..\FooBar" through when the directory is "Foo" From f87932e09b6c2927c4e610cc52a2c20a116542b1 Mon Sep 17 00:00:00 2001 From: Mohamadreza Nakhleh Date: Sun, 23 Nov 2025 22:10:35 +0330 Subject: [PATCH 2/4] typo: use PathInternal for IsDirectorySeparator Signed-off-by: Mohamadreza Nakhleh --- .../System.Private.CoreLib/src/System/IO/DirectoryInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/DirectoryInfo.cs b/src/libraries/System.Private.CoreLib/src/System/IO/DirectoryInfo.cs index bf6e206dfb0d84..89e3dda45bf434 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/DirectoryInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/DirectoryInfo.cs @@ -87,7 +87,7 @@ public DirectoryInfo CreateSubdirectory(string path) // If trimmedCurrentPath still has a trailing separator (root directory case like "C:\" or "/"), // remove it for proper boundary checking - if (trimmedCurrentPath.Length > 0 && IsDirectorySeparator(trimmedCurrentPath[trimmedCurrentPath.Length - 1])) + if (trimmedCurrentPath.Length > 0 && PathInternal.IsDirectorySeparator(trimmedCurrentPath[trimmedCurrentPath.Length - 1])) { trimmedCurrentPath = trimmedCurrentPath.Slice(0, trimmedCurrentPath.Length - 1); } From fd1987ecf907a12297ca1227317dafffaf9a9368 Mon Sep 17 00:00:00 2001 From: Mohamadreza Nakhleh Date: Mon, 24 Nov 2025 02:04:18 +0330 Subject: [PATCH 3/4] test: add a test to verify the fix Signed-off-by: Mohamadreza Nakhleh --- .../Directory/EnumerableTests.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/EnumerableTests.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/EnumerableTests.cs index 20f61989594b09..bc70eea36e6e4e 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/EnumerableTests.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/EnumerableTests.cs @@ -55,6 +55,39 @@ public void FileEnumeratorIsThreadSafe_ParallelForEach() } } + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void CreateSubdirectory_RootDriveSubfolder() + { + // Get the root of the OS drive (e.g., "C:\") + string rootDrive = Path.GetPathRoot(Environment.SystemDirectory); + + // Create a DirectoryInfo for the root drive + DirectoryInfo rootDirectory = new DirectoryInfo(rootDrive); + + // Create a unique test folder name to avoid conflicts + string testFolderName = $"TestFolder_{Guid.NewGuid():N}"; + + try + { + // Create a subdirectory directly in the root + DirectoryInfo subDirectory = rootDirectory.CreateSubdirectory(testFolderName); + + // Verify it was created + Assert.True(subDirectory.Exists); + Assert.Equal(Path.Combine(rootDrive, testFolderName), subDirectory.FullName); + } + finally + { + // Cleanup + string testFolderPath = Path.Combine(rootDrive, testFolderName); + if (Directory.Exists(testFolderPath)) + { + Directory.Delete(testFolderPath, recursive: true); + } + } + } + [Fact] public void EnumerateDirectories_NonBreakingSpace() { From f6bbb02648ce3522bba13024d816ba6ba34e5cd6 Mon Sep 17 00:00:00 2001 From: Mohamadreza Nakhleh Date: Tue, 25 Nov 2025 19:31:30 +0330 Subject: [PATCH 4/4] test: add test for Linux as well Signed-off-by: Mohamadreza Nakhleh --- .../Directory/EnumerableTests.cs | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/EnumerableTests.cs b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/EnumerableTests.cs index bc70eea36e6e4e..ac0ddfb5236d0c 100644 --- a/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/EnumerableTests.cs +++ b/src/libraries/System.Runtime/tests/System.IO.FileSystem.Tests/Directory/EnumerableTests.cs @@ -57,7 +57,7 @@ public void FileEnumeratorIsThreadSafe_ParallelForEach() [Fact] [PlatformSpecific(TestPlatforms.Windows)] - public void CreateSubdirectory_RootDriveSubfolder() + public void CreateSubdirectory_RootDriveSubfolder_Windows() { // Get the root of the OS drive (e.g., "C:\") string rootDrive = Path.GetPathRoot(Environment.SystemDirectory); @@ -88,6 +88,42 @@ public void CreateSubdirectory_RootDriveSubfolder() } } + + [Fact] + [PlatformSpecific(TestPlatforms.Linux)] + public void CreateSubdirectory_RootDriveSubfolder_Linux() + { + // Get the root of the OS drive (e.g., "/") + // can not use string rootDrive = Path.GetPathRoot(Environment.SystemDirectory); as Environment.SystemDirectory is empty for Linux + string rootDrive = "/" + + // Create a DirectoryInfo for the root drive + DirectoryInfo rootDirectory = new DirectoryInfo(rootDrive); + + // Create a unique test folder name to avoid conflicts + string testFolderName = $"TestFolder_{Guid.NewGuid():N}"; + + try + { + // Create a subdirectory directly in the root + DirectoryInfo subDirectory = rootDirectory.CreateSubdirectory(testFolderName); + + // Verify it was created + Assert.True(subDirectory.Exists); + Assert.Equal(Path.Combine(rootDrive, testFolderName), subDirectory.FullName); + } + finally + { + // Cleanup + string testFolderPath = Path.Combine(rootDrive, testFolderName); + if (Directory.Exists(testFolderPath)) + { + Directory.Delete(testFolderPath, recursive: true); + } + } + } + + [Fact] public void EnumerateDirectories_NonBreakingSpace() {