diff --git a/NuGet.config b/NuGet.config
index 65d0a58fb6c1e6..90cdac30ea9f58 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -9,7 +9,7 @@
-
+
- 5.0.0-2.26307.103
- 5.0.0-2.26307.103
- 5.0.0-2.26307.103
+ 5.0.0-2.26319.104
+ 5.0.0-2.26319.104
+ 5.0.0-2.26319.104
10.0.110
10.0.110
- 10.0.0-beta.26307.103
- 10.0.0-beta.26307.103
- 10.0.0-beta.26307.103
- 10.0.0-beta.26307.103
- 10.0.0-beta.26307.103
- 10.0.0-beta.26307.103
- 10.0.0-beta.26307.103
- 10.0.0-beta.26307.103
- 0.11.5-alpha.26307.103
- 10.0.0-beta.26307.103
- 10.0.0-beta.26307.103
- 10.0.0-beta.26307.103
- 10.0.0-beta.26307.103
- 10.0.0-beta.26307.103
- 10.0.0-beta.26307.103
- 10.0.0-beta.26307.103
- 10.0.0-beta.26307.103
- 2.9.3-beta.26307.103
- 2.9.3-beta.26307.103
- 10.0.0-beta.26307.103
- 5.0.0-2.26307.103
+ 10.0.0-beta.26319.104
+ 10.0.0-beta.26319.104
+ 10.0.0-beta.26319.104
+ 10.0.0-beta.26319.104
+ 10.0.0-beta.26319.104
+ 10.0.0-beta.26319.104
+ 10.0.0-beta.26319.104
+ 10.0.0-beta.26319.104
+ 0.11.5-alpha.26319.104
+ 10.0.0-beta.26319.104
+ 10.0.0-beta.26319.104
+ 10.0.0-beta.26319.104
+ 10.0.0-beta.26319.104
+ 10.0.0-beta.26319.104
+ 10.0.0-beta.26319.104
+ 10.0.0-beta.26319.104
+ 10.0.0-beta.26319.104
+ 2.9.3-beta.26319.104
+ 2.9.3-beta.26319.104
+ 10.0.0-beta.26319.104
+ 5.0.0-2.26319.104
10.0.10
- 10.0.110-servicing.26307.103
+ 10.0.110-servicing.26319.104
10.0.10
10.0.10
- 7.0.3-rc.30803
- 7.0.3-rc.30803
- 7.0.3-rc.30803
- 7.0.3-rc.30803
+ 7.0.3-rc.32004
+ 7.0.3-rc.32004
+ 7.0.3-rc.32004
+ 7.0.3-rc.32004
10.0.10
2.0.10
10.0.10
@@ -80,14 +80,14 @@ This file should be imported by eng/Versions.props
19.1.0-alpha.1.25461.1
19.1.0-alpha.1.25461.1
- 10.0.0-alpha.1.26257.2
- 10.0.0-alpha.1.26257.2
- 10.0.0-alpha.1.26257.2
- 10.0.0-alpha.1.26257.2
- 10.0.0-alpha.1.26257.2
- 10.0.0-alpha.1.26257.2
- 10.0.0-alpha.1.26257.2
- 10.0.0-alpha.1.26257.2
+ 10.0.0-alpha.1.26310.1
+ 10.0.0-alpha.1.26310.1
+ 10.0.0-alpha.1.26310.1
+ 10.0.0-alpha.1.26310.1
+ 10.0.0-alpha.1.26310.1
+ 10.0.0-alpha.1.26310.1
+ 10.0.0-alpha.1.26310.1
+ 10.0.0-alpha.1.26310.1
1.0.0-prerelease.26080.1
1.0.0-prerelease.26080.1
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index bdd088e46d3b75..6e6f91d0f74c35 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -1,5 +1,5 @@
-
+
https://github.com/dotnet/icu
@@ -43,89 +43,89 @@
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
https://github.com/dotnet/runtime-assets
@@ -265,31 +265,31 @@
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
https://github.com/dotnet/xharness
@@ -303,9 +303,9 @@
https://github.com/dotnet/xharness
fa7fbebf9168e8858971f9d0c71b2b08f2f7b106
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
https://dev.azure.com/dnceng/internal/_git/dotnet-optimization
@@ -331,29 +331,29 @@
https://github.com/dotnet/runtime-assets
3910cd6230be3d4d283edd6a52bff27f549dd675
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
https://dev.azure.com/dnceng/internal/_git/dotnet-optimization
@@ -365,53 +365,53 @@
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/dotnet
- fe7283f2bc56523c765bc050be87a97543db180a
+ 47c73391bfb8274e379c74c19694ccabff33bc3e
-
+
https://github.com/dotnet/node
- e0b2d00649a1627cdeb16f9ea75bbd08b37234df
+ 7ddcc434d9adb6256368f250592c97e065f9af4a
-
+
https://github.com/dotnet/node
- e0b2d00649a1627cdeb16f9ea75bbd08b37234df
+ 7ddcc434d9adb6256368f250592c97e065f9af4a
-
+
https://github.com/dotnet/node
- e0b2d00649a1627cdeb16f9ea75bbd08b37234df
+ 7ddcc434d9adb6256368f250592c97e065f9af4a
-
+
https://github.com/dotnet/node
- e0b2d00649a1627cdeb16f9ea75bbd08b37234df
+ 7ddcc434d9adb6256368f250592c97e065f9af4a
-
+
https://github.com/dotnet/node
- e0b2d00649a1627cdeb16f9ea75bbd08b37234df
+ 7ddcc434d9adb6256368f250592c97e065f9af4a
-
+
https://github.com/dotnet/node
- e0b2d00649a1627cdeb16f9ea75bbd08b37234df
+ 7ddcc434d9adb6256368f250592c97e065f9af4a
-
+
https://github.com/dotnet/node
- e0b2d00649a1627cdeb16f9ea75bbd08b37234df
+ 7ddcc434d9adb6256368f250592c97e065f9af4a
-
+
https://github.com/dotnet/node
- e0b2d00649a1627cdeb16f9ea75bbd08b37234df
+ 7ddcc434d9adb6256368f250592c97e065f9af4a
https://github.com/dotnet/runtime-assets
diff --git a/global.json b/global.json
index b9edfb29cb2652..eb4524bc479b62 100644
--- a/global.json
+++ b/global.json
@@ -1,16 +1,16 @@
{
"sdk": {
- "version": "10.0.108",
+ "version": "10.0.109",
"allowPrerelease": true,
"rollForward": "major"
},
"tools": {
- "dotnet": "10.0.108"
+ "dotnet": "10.0.109"
},
"msbuild-sdks": {
- "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26307.103",
- "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.26307.103",
- "Microsoft.DotNet.SharedFramework.Sdk": "10.0.0-beta.26307.103",
+ "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26319.104",
+ "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.26319.104",
+ "Microsoft.DotNet.SharedFramework.Sdk": "10.0.0-beta.26319.104",
"Microsoft.Build.NoTargets": "3.7.0",
"Microsoft.Build.Traversal": "3.4.0",
"Microsoft.NET.Sdk.IL": "10.0.10"
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs
index 91c53377081701..d14e50c9a33157 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/InteropHelpers.cs
@@ -45,7 +45,9 @@ internal static unsafe void StringToByValAnsiString(string str, byte* pNative, i
fixed (char* pManaged = str)
{
- PInvokeMarshal.StringToAnsiString(pManaged, lenUnicode, pNative, /*terminateWithNull=*/true, bestFit, throwOnUnmappableChar);
+ PInvokeMarshal.StringToAnsiString(pManaged, lenUnicode, pNative,
+ /*terminateWithNull=*/true, bestFit, throwOnUnmappableChar,
+ nativeByteLength: charCount);
}
}
else
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs
index 323544388d81e3..c70563b9f786f7 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/PInvokeMarshal.cs
@@ -499,9 +499,14 @@ public static unsafe char AnsiCharToWideChar(byte nativeValue)
}
// c# string (UTF-16) to UTF-8 encoded byte array
+ // If specified, nativeByteLength represents the length of the output buffer and this function
+ // will not write more than that many bytes. If negative, this function writes as many bytes
+ // as needed to encode the string.
internal static unsafe byte* StringToAnsiString(char* pManaged, int lenUnicode, byte* pNative, bool terminateWithNull,
- bool bestFit, bool throwOnUnmappableChar)
+ bool bestFit, bool throwOnUnmappableChar, int nativeByteLength = -1)
{
+ Debug.Assert(pNative != null || nativeByteLength == -1, "Native buffer should not be null when nativeByteLength is specified.");
+
bool allAscii = Ascii.IsValid(new ReadOnlySpan(pManaged, lenUnicode));
int length;
@@ -514,6 +519,18 @@ public static unsafe char AnsiCharToWideChar(byte nativeValue)
length = GetByteCount(pManaged, lenUnicode);
}
+ // Clamp to nativeByteLength when caller provides a bounded output buffer (ByValTStr).
+ // For non-ASCII, ConvertWideCharToMultiByte will throw (Unix) or truncate (Windows)
+ // when the encoded bytes exceed the clamped length. This matches CoreCLR behavior.
+ if (nativeByteLength >= 0)
+ {
+ int maxBytesToWrite = terminateWithNull ? Math.Max(0, nativeByteLength - 1) : nativeByteLength;
+ if (length > maxBytesToWrite)
+ {
+ length = maxBytesToWrite;
+ }
+ }
+
if (pNative == null)
{
pNative = (byte*)Marshal.AllocCoTaskMem(checked(length + 1));
@@ -533,8 +550,8 @@ public static unsafe char AnsiCharToWideChar(byte nativeValue)
throwOnUnmappableChar);
}
- // Zero terminate
- if (terminateWithNull)
+ // Zero terminate if requested and the buffer is not specified to be size 0.
+ if (terminateWithNull && nativeByteLength != 0)
*(pNative + length) = 0;
return pNative;
diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs
index 8191f0bacd18fe..991e0a07121747 100644
--- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs
+++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs
@@ -351,7 +351,7 @@ internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, b
string? fileDestinationPath = GetFullDestinationPath(
destinationDirectoryPath,
Path.IsPathFullyQualified(name) ? name : Path.Join(destinationDirectoryPath, name));
- if (fileDestinationPath == null)
+ if (fileDestinationPath is null || FilePathEscapesDirectory(destinationDirectoryPath, fileDestinationPath))
{
throw new IOException(SR.Format(SR.TarExtractingResultsFileOutside, name, destinationDirectoryPath));
}
@@ -372,7 +372,7 @@ internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, b
string? linkDestination = GetFullDestinationPath(
destinationDirectoryPath,
Path.IsPathFullyQualified(linkName) ? linkName : Path.Join(Path.GetDirectoryName(fileDestinationPath), linkName));
- if (linkDestination is null)
+ if (linkDestination is null || FilePathEscapesDirectory(destinationDirectoryPath, linkDestination))
{
throw new IOException(SR.Format(SR.TarExtractingResultsLinkOutside, linkName, destinationDirectoryPath));
}
@@ -387,7 +387,7 @@ internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, b
string? linkDestination = GetFullDestinationPath(
destinationDirectoryPath,
Path.Join(destinationDirectoryPath, linkName));
- if (linkDestination is null)
+ if (linkDestination is null || FilePathEscapesDirectory(destinationDirectoryPath, linkDestination))
{
throw new IOException(SR.Format(SR.TarExtractingResultsLinkOutside, linkName, destinationDirectoryPath));
}
@@ -398,6 +398,101 @@ internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, b
return (fileDestinationPath, linkTargetPath);
}
+ // Check if the file destination path or the link target path escapes the destination directory, by walking through the relative path components and resolving symlinks at each step.
+ private static bool FilePathEscapesDirectory(string destinationDirectoryPath, string fileDestinationPath)
+ {
+ // Windows is case insensitive while Linux is case sensitive
+ // This ensures the comparison is consistent with how the OS would resolve the paths
+ StringComparison pathComparison = OperatingSystem.IsWindows()
+ ? StringComparison.OrdinalIgnoreCase
+ : StringComparison.Ordinal;
+
+ string resolvedDest = ResolvePhysicalPath(destinationDirectoryPath);
+ string destPrefix = resolvedDest.EndsWith(Path.DirectorySeparatorChar)
+ ? resolvedDest
+ : resolvedDest + Path.DirectorySeparatorChar;
+
+ // Normalize file path (resolves .. and . but not symlinks)
+ string normalizedFile = Path.GetFullPath(fileDestinationPath);
+
+ // Walk relative components, resolving symlinks at each step
+ string relative = normalizedFile.Substring(resolvedDest.Length)
+ .TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
+
+ string[] components = relative.Split(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar },
+ StringSplitOptions.RemoveEmptyEntries);
+
+ string current = resolvedDest;
+
+ foreach (string component in components)
+ {
+ current = Path.Combine(current, component);
+
+ if (Path.Exists(current))
+ {
+ string? resolved = ResolveSymlink(current);
+ if (resolved is null)
+ {
+ return true;
+ }
+ current = resolved;
+ }
+
+ string normalizedCurrent = Path.GetFullPath(current);
+ if (!normalizedCurrent.StartsWith(destPrefix, pathComparison) &&
+ !normalizedCurrent.Equals(resolvedDest, pathComparison))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static string? ResolveSymlink(string path)
+ {
+ FileSystemInfo? target = new FileInfo(path).ResolveLinkTarget(returnFinalTarget: true);
+
+ if (target is null)
+ {
+ return Path.GetFullPath(path);
+ }
+
+ return target.FullName;
+ }
+
+ // Resolves the full path of the specified path, resolving symlinks at each step.
+ // This is needed to mitigate malicious entries in the archive that could lead to writing files outside of the intended directory.
+ private static string ResolvePhysicalPath(string path)
+ {
+ string fullPath = Path.GetFullPath(path);
+ string? root = Path.GetPathRoot(fullPath);
+
+ if (root is null)
+ {
+ return fullPath;
+ }
+
+ string[] components = fullPath.Substring(root.Length)
+ .Split(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
+ string current = root;
+ foreach (string component in components)
+ {
+ current = Path.Combine(current, component);
+ if (Path.Exists(current))
+ {
+ string? resolved = ResolveSymlink(current);
+ if (resolved is null)
+ {
+ return current;
+ }
+ current = resolved;
+ }
+ }
+
+ return current;
+ }
+
// Returns the full destination path if the path is the destinationDirectory or a subpath. Otherwise, returns null.
private static string? GetFullDestinationPath(string destinationDirectoryFullPath, string qualifiedPath)
{
diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs
index 9ffbcc00792703..2d685256cf0b2b 100644
--- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs
+++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs
@@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
+using System.Text;
using Xunit;
namespace System.Formats.Tar.Tests
@@ -45,6 +46,7 @@ public void NonExistentDirectory_Throws()
}
[Fact]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public void SetsLastModifiedTimeOnExtractedFiles()
{
using TempDirectory root = new TempDirectory();
@@ -72,6 +74,7 @@ public void SetsLastModifiedTimeOnExtractedFiles()
}
[Fact]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public void SetsLastModifiedTimeOnExtractedDirectories()
{
using TempDirectory root = new TempDirectory();
@@ -208,6 +211,7 @@ public void Extract_AllSegmentsOfPath()
}
[Fact]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public void ExtractArchiveWithEntriesThatStartWithSlashDotPrefix()
{
using TempDirectory root = new TempDirectory();
@@ -233,6 +237,7 @@ public void ExtractArchiveWithEntriesThatStartWithSlashDotPrefix()
[Theory]
[InlineData(true)]
[InlineData(false)]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public void UnixFileModes(bool overwrite)
{
using TempDirectory source = new TempDirectory();
@@ -301,6 +306,7 @@ public void UnixFileModes(bool overwrite)
[Theory]
[InlineData(true)]
[InlineData(false)]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public void UnixFileModes_RestrictiveParentDir(bool overwrite)
{
using TempDirectory source = new TempDirectory();
@@ -341,6 +347,7 @@ public void UnixFileModes_RestrictiveParentDir(bool overwrite)
}
[ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public void LinkBeforeTarget()
{
using TempDirectory source = new TempDirectory();
@@ -368,5 +375,96 @@ public void LinkBeforeTarget()
Assert.True(File.Exists(filePath), $"{filePath}' does not exist.");
Assert.True(File.Exists(linkPath), $"{linkPath}' does not exist.");
}
+
+ [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
+ public void ExtractToDirectory_RejectsSymlinkDirectoryTraversal_WithNestedFile()
+ {
+ using TempDirectory root = new TempDirectory();
+ string destDir = Path.Combine(root.Path, "dest");
+ Directory.CreateDirectory(destDir);
+
+ // Absolute path outside destDir
+ string linkTarget = "/tmp/outside";
+
+ string tarPath = Path.Combine(root.Path, "symlink_dir_traversal.tar");
+ using (FileStream stream = new FileStream(tarPath, FileMode.Create, FileAccess.Write))
+ using (TarWriter writer = new TarWriter(stream, leaveOpen: false))
+ {
+ // symlink: "link" -> "/tmp/outside"
+ writer.WriteEntry(new PaxTarEntry(TarEntryType.SymbolicLink, "link")
+ {
+ LinkName = linkTarget
+ });
+
+ // file: "link/test.txt" with "hello"
+ byte[] content = Encoding.UTF8.GetBytes("hello");
+ var fileEntry = new PaxTarEntry(TarEntryType.RegularFile, "link/test.txt")
+ {
+ DataStream = new MemoryStream(content, writable: false)
+ };
+
+ fileEntry.DataStream.Position = 0;
+ writer.WriteEntry(fileEntry);
+ }
+
+ Assert.Throws(() => TarFile.ExtractToDirectory(tarPath, destDir, overwriteFiles: true));
+
+ // Nothing should be created in dest
+ string linkPath = Path.Combine(destDir, "link");
+ string outsideFilePath = Path.Combine(destDir, "link", "test.txt");
+ Assert.False(File.Exists(linkPath) || Directory.Exists(linkPath), "link should not have been created.");
+ Assert.False(File.Exists(outsideFilePath) || Directory.Exists(linkPath), "traversal link should not have been created.");
+ }
+
+
+ [ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
+ public void ExtractToDirectory_RejectsChainedSymlinkDirectoryTraversal_WithNestedFile()
+ {
+ // dir a/
+ // symlink a/b ? .
+ // symlink a/b/c ? .
+ // symlink a/b/c/d ? ../../outside
+ // file a/d/ pwned.txt escapes
+
+ using TempDirectory root = new TempDirectory();
+ string destDir = Path.Combine(root.Path, "dest");
+ Directory.CreateDirectory(destDir);
+
+ string tarPath = Path.Combine(root.Path, "chained_symlink_traversal.tar");
+ using (FileStream stream = new FileStream(tarPath, FileMode.Create, FileAccess.Write))
+ using (TarWriter writer = new TarWriter(stream, leaveOpen: false))
+ {
+ writer.WriteEntry(new PaxTarEntry(TarEntryType.Directory, "a/"));
+
+ writer.WriteEntry(new PaxTarEntry(TarEntryType.SymbolicLink, "a/b") { LinkName = "." });
+
+ writer.WriteEntry(new PaxTarEntry(TarEntryType.SymbolicLink, "a/b/c") { LinkName = "." });
+
+ writer.WriteEntry(new PaxTarEntry(TarEntryType.SymbolicLink, "a/b/c/d") { LinkName = "../../outside" });
+
+ var pwned = new PaxTarEntry(TarEntryType.RegularFile, "a/d/pwned.txt")
+ {
+ DataStream = new MemoryStream(Encoding.UTF8.GetBytes("pwned"))
+ };
+ writer.WriteEntry(pwned);
+ }
+
+ if (OperatingSystem.IsWindows())
+ {
+ // Windows only creates file symlinks and trying to process a directory symlink will throw UnauthorizedAccessException instead of IOException
+ Assert.Throws(() => TarFile.ExtractToDirectory(tarPath, destDir, overwriteFiles: true));
+ }
+ else
+ {
+ Assert.Throws(() => TarFile.ExtractToDirectory(tarPath, destDir, overwriteFiles: true));
+ }
+
+ string outsideDir = Path.Combine(root.Path, "outside");
+ Assert.False(Directory.Exists(outsideDir), "outside/directory should not have been created.");
+ Assert.False(File.Exists(Path.Combine(outsideDir, "pwned.txt")), "pwned.txt should not have been written outside destination.");
+
+ }
}
}
diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs
index d8f6bfb1d2b2c4..bb4594c5640253 100644
--- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs
+++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Stream.Tests.cs
@@ -76,6 +76,7 @@ public void ExtractEntry_ManySubfolderSegments_NoPrecedingDirectoryEntries()
[Theory]
[InlineData(TarEntryType.SymbolicLink)]
[InlineData(TarEntryType.HardLink)]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public void Extract_LinkEntry_TargetOutsideDirectory(TarEntryType entryType)
{
using MemoryStream archive = new MemoryStream();
@@ -98,21 +99,25 @@ public void Extract_LinkEntry_TargetOutsideDirectory(TarEntryType entryType)
[ConditionalTheory(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))]
[InlineData(TarEntryFormat.Pax)]
[InlineData(TarEntryFormat.Gnu)]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public void Extract_SymbolicLinkEntry_TargetInsideDirectory(TarEntryFormat format) => Extract_LinkEntry_TargetInsideDirectory_Internal(TarEntryType.SymbolicLink, format, null);
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.SupportsHardLinkCreation))]
[InlineData(TarEntryFormat.Pax)]
[InlineData(TarEntryFormat.Gnu)]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public void Extract_HardLinkEntry_TargetInsideDirectory(TarEntryFormat format) => Extract_LinkEntry_TargetInsideDirectory_Internal(TarEntryType.HardLink, format, null);
[ConditionalTheory(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))]
[InlineData(TarEntryFormat.Pax)]
[InlineData(TarEntryFormat.Gnu)]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public void Extract_SymbolicLinkEntry_TargetInsideDirectory_LongBaseDir(TarEntryFormat format) => Extract_LinkEntry_TargetInsideDirectory_Internal(TarEntryType.SymbolicLink, format, new string('a', 99));
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.SupportsHardLinkCreation))]
[InlineData(TarEntryFormat.Pax)]
[InlineData(TarEntryFormat.Gnu)]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public void Extract_HardLinkEntry_TargetInsideDirectory_LongBaseDir(TarEntryFormat format) => Extract_LinkEntry_TargetInsideDirectory_Internal(TarEntryType.HardLink, format, new string('a', 99));
// This test would not pass for the V7 and Ustar formats in some OSs like MacCatalyst, tvOSSimulator and OSX, because the TempDirectory gets created in
@@ -152,6 +157,7 @@ private void Extract_LinkEntry_TargetInsideDirectory_Internal(TarEntryType entry
[InlineData(512)]
[InlineData(512 + 1)]
[InlineData(512 + 512 - 1)]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public void Extract_UnseekableStream_BlockAlignmentPadding_DoesNotAffectNextEntries(int contentSize)
{
byte[] fileContents = new byte[contentSize];
@@ -208,6 +214,7 @@ public void PaxNameCollision_DedupInExtendedAttributes()
[Theory]
[MemberData(nameof(GetTestTarFormats))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public void UnseekableStreams_RoundTrip(TestTarFormat testFormat)
{
using TempDirectory root = new();
diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.File.Tests.cs
index 42b0eef65eef87..d692432f058bb7 100644
--- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.File.Tests.cs
+++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.File.Tests.cs
@@ -57,6 +57,7 @@ public async Task NonExistentDirectory_Throws_Async()
}
[Fact]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public async Task SetsLastModifiedTimeOnExtractedFiles()
{
using TempDirectory root = new TempDirectory();
@@ -84,6 +85,7 @@ public async Task SetsLastModifiedTimeOnExtractedFiles()
}
[Fact]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public async Task SetsLastModifiedTimeOnExtractedDirectories()
{
using TempDirectory root = new TempDirectory();
@@ -236,6 +238,7 @@ public async Task Extract_AllSegmentsOfPath_Async()
}
[Fact]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public async Task ExtractArchiveWithEntriesThatStartWithSlashDotPrefix_Async()
{
using (TempDirectory root = new TempDirectory())
@@ -264,6 +267,7 @@ public async Task ExtractArchiveWithEntriesThatStartWithSlashDotPrefix_Async()
[Theory]
[InlineData(true)]
[InlineData(false)]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public async Task UnixFileModes_Async(bool overwrite)
{
using TempDirectory source = new TempDirectory();
@@ -330,6 +334,7 @@ public async Task UnixFileModes_Async(bool overwrite)
}
[Fact]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public async Task UnixFileModes_RestrictiveParentDir_Async()
{
using TempDirectory source = new TempDirectory();
@@ -363,6 +368,7 @@ public async Task UnixFileModes_RestrictiveParentDir_Async()
}
[ConditionalFact(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public async Task LinkBeforeTargetAsync()
{
using TempDirectory source = new TempDirectory();
diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs
index d56f85648914df..d95deb6128d080 100644
--- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs
+++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.Stream.Tests.cs
@@ -136,6 +136,7 @@ public async Task ExtractEntry_PodmanImageTarWithRelativeSymlinksPointingInExtra
[Theory]
[InlineData(TarEntryType.SymbolicLink)]
[InlineData(TarEntryType.HardLink)]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public async Task Extract_LinkEntry_TargetOutsideDirectory_Async(TarEntryType entryType)
{
await using (MemoryStream archive = new MemoryStream())
@@ -160,21 +161,25 @@ public async Task Extract_LinkEntry_TargetOutsideDirectory_Async(TarEntryType en
[ConditionalTheory(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))]
[InlineData(TarEntryFormat.Pax)]
[InlineData(TarEntryFormat.Gnu)]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public Task Extract_SymbolicLinkEntry_TargetInsideDirectory_Async(TarEntryFormat format) => Extract_LinkEntry_TargetInsideDirectory_Internal_Async(TarEntryType.SymbolicLink, format, null);
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.SupportsHardLinkCreation))]
[InlineData(TarEntryFormat.Pax)]
[InlineData(TarEntryFormat.Gnu)]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public Task Extract_HardLinkEntry_TargetInsideDirectory_Async(TarEntryFormat format) => Extract_LinkEntry_TargetInsideDirectory_Internal_Async(TarEntryType.HardLink, format, null);
[ConditionalTheory(typeof(MountHelper), nameof(MountHelper.CanCreateSymbolicLinks))]
[InlineData(TarEntryFormat.Pax)]
[InlineData(TarEntryFormat.Gnu)]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public Task Extract_SymbolicLinkEntry_TargetInsideDirectory_LongBaseDir_Async(TarEntryFormat format) => Extract_LinkEntry_TargetInsideDirectory_Internal_Async(TarEntryType.SymbolicLink, format, new string('a', 99));
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.SupportsHardLinkCreation))]
[InlineData(TarEntryFormat.Pax)]
[InlineData(TarEntryFormat.Gnu)]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public Task Extract_HardLinkEntry_TargetInsideDirectory_LongBaseDir_Async(TarEntryFormat format) => Extract_LinkEntry_TargetInsideDirectory_Internal_Async(TarEntryType.HardLink, format, new string('a', 99));
// This test would not pass for the V7 and Ustar formats in some OSs like MacCatalyst, tvOSSimulator and OSX, because the TempDirectory gets created in
@@ -217,6 +222,7 @@ private async Task Extract_LinkEntry_TargetInsideDirectory_Internal_Async(TarEnt
[InlineData(512)]
[InlineData(512 + 1)]
[InlineData(512 + 512 - 1)]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public async Task Extract_UnseekableStream_BlockAlignmentPadding_DoesNotAffectNextEntries_Async(int contentSize)
{
byte[] fileContents = new byte[contentSize];
@@ -273,6 +279,7 @@ public async Task PaxNameCollision_DedupInExtendedAttributesAsync()
[Theory]
[MemberData(nameof(GetTestTarFormats))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129227")]
public async Task UnseekableStreams_RoundTrip_Async(TestTarFormat testFormat)
{
using TempDirectory root = new();
diff --git a/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/Marshal/StructureToPtrTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/Marshal/StructureToPtrTests.cs
index 59cefc137cef81..f553f2945888a0 100644
--- a/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/Marshal/StructureToPtrTests.cs
+++ b/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/Marshal/StructureToPtrTests.cs
@@ -363,5 +363,59 @@ public struct InnerStruct
public InnerStruct s;
public byte b;
}
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
+ public struct StructWithByValString
+ {
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
+ public string? Name;
+ }
+
+ [Fact]
+ [PlatformSpecific(TestPlatforms.AnyUnix)]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/129228")]
+ public void StructureToPtr_ByValTStr_MultiByte_Overflow()
+ {
+ // ByValTStr uses UTF-8 on Unix. € is 3 bytes, so the string exceeds the specified SizeConst
+ var payload = new StructWithByValString { Name = "€€€" };
+
+ int size = Marshal.SizeOf();
+ IntPtr buffer = Marshal.AllocHGlobal(size + 1);
+ byte sentinelValue = 0xFF;
+ try
+ {
+ Marshal.WriteByte(buffer, size, sentinelValue);
+ Assert.Throws(() => Marshal.StructureToPtr(payload, buffer, false));
+ Assert.Equal(sentinelValue, Marshal.ReadByte(buffer, size));
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(buffer);
+ }
+ }
+
+ [Fact]
+ public void StructureToPtr_ByValTStr_Ascii_TruncatesLongString()
+ {
+ var payload = new StructWithByValString { Name = "abcdef" };
+
+ int size = Marshal.SizeOf();
+ IntPtr buffer = Marshal.AllocHGlobal(size + 1);
+ byte sentinelValue = 0xFF;
+ try
+ {
+ Marshal.WriteByte(buffer, size, sentinelValue);
+ Marshal.StructureToPtr(payload, buffer, false);
+ Assert.Equal((byte)'a', Marshal.ReadByte(buffer, 0));
+ Assert.Equal((byte)'b', Marshal.ReadByte(buffer, 1));
+ Assert.Equal((byte)'c', Marshal.ReadByte(buffer, 2));
+ Assert.Equal((byte)0, Marshal.ReadByte(buffer, 3));
+ Assert.Equal(sentinelValue, Marshal.ReadByte(buffer, size));
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(buffer);
+ }
+ }
}
}