diff --git a/eng/Versions.props b/eng/Versions.props
index 7704ae5bb3b055..b7d96c21a10f90 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -140,7 +140,7 @@
1.0.0-prerelease.22415.6
1.0.0-prerelease.22415.6
- 16.11.23-beta1.23063.1
+ 16.11.27-beta1.23180.1
2.0.0-beta4.22355.1
3.0.3
2.1.0
diff --git a/src/coreclr/dlls/mscoree/CMakeLists.txt b/src/coreclr/dlls/mscoree/CMakeLists.txt
index dc22c27a0957e9..785655763d5d91 100644
--- a/src/coreclr/dlls/mscoree/CMakeLists.txt
+++ b/src/coreclr/dlls/mscoree/CMakeLists.txt
@@ -11,7 +11,7 @@ set(CLR_SOURCES
if(CLR_CMAKE_TARGET_WIN32)
list(APPEND CLR_SOURCES
- delayloadhook.cpp
+ ${CLR_SRC_NATIVE_DIR}/libs/Common/delayloadhook_windows.cpp
Native.rc
)
diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp
index e6a1f86114c4e8..76b57204df42cf 100644
--- a/src/coreclr/vm/ceeload.cpp
+++ b/src/coreclr/vm/ceeload.cpp
@@ -2311,26 +2311,17 @@ ISymUnmanagedReader *Module::GetISymUnmanagedReader(void)
"reachable or needs to be reimplemented for CoreCLR!");
}
- // We're going to be working with Windows PDB format symbols. Attempt to CoCreate the symbol binder.
- // CoreCLR supports not having a symbol reader installed, so CoCreate searches the PATH env var
- // and then tries coreclr dll location.
- // On desktop, the framework installer is supposed to install diasymreader.dll as well
- // and so this shouldn't happen.
- hr = FakeCoCreateInstanceEx(CLSID_CorSymBinder_SxS, NATIVE_SYMBOL_READER_DLL, IID_ISymUnmanagedBinder, (void**)&pBinder, NULL);
+ PathString symbolReaderPath;
+ hr = GetClrModuleDirectory(symbolReaderPath);
if (FAILED(hr))
{
- PathString symbolReaderPath;
- hr = GetClrModuleDirectory(symbolReaderPath);
- if (FAILED(hr))
- {
- RETURN (NULL);
- }
- symbolReaderPath.Append(NATIVE_SYMBOL_READER_DLL);
- hr = FakeCoCreateInstanceEx(CLSID_CorSymBinder_SxS, symbolReaderPath.GetUnicode(), IID_ISymUnmanagedBinder, (void**)&pBinder, NULL);
- if (FAILED(hr))
- {
- RETURN (NULL);
- }
+ RETURN (NULL);
+ }
+ symbolReaderPath.Append(NATIVE_SYMBOL_READER_DLL);
+ hr = FakeCoCreateInstanceEx(CLSID_CorSymBinder_SxS, symbolReaderPath.GetUnicode(), IID_ISymUnmanagedBinder, (void**)&pBinder, NULL);
+ if (FAILED(hr))
+ {
+ RETURN (NULL);
}
LOG((LF_CORDB, LL_INFO10, "M::GISUR: Created binder\n"));
diff --git a/src/libraries/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.manual.cs b/src/libraries/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.manual.cs
index 85c0697114db3c..b20fa4e016073c 100644
--- a/src/libraries/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.manual.cs
+++ b/src/libraries/Common/src/System/Security/Cryptography/Asn1/Pkcs12/PfxAsn.manual.cs
@@ -2,12 +2,23 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
+using System.Formats.Asn1;
+using System.Security.Cryptography.Asn1.Pkcs7;
using System.Security.Cryptography.Pkcs;
+using Internal.Cryptography;
+
+#if BUILDING_PKCS
+using Helpers = Internal.Cryptography.PkcsHelpers;
+#endif
namespace System.Security.Cryptography.Asn1.Pkcs12
{
internal partial struct PfxAsn
{
+ private const int MaxIterationWork = 300_000;
+ private static ReadOnlySpan EmptyPassword => ""; // don't use ReadOnlySpan.Empty because it will get confused with default.
+ private static ReadOnlySpan NullPassword => default;
+
internal bool VerifyMac(
ReadOnlySpan macPassword,
ReadOnlySpan authSafeContents)
@@ -84,5 +95,245 @@ internal bool VerifyMac(
MacData.Value.Mac.Digest.Span);
}
}
+
+ internal ulong CountTotalIterations()
+ {
+ checked
+ {
+ ulong count = 0;
+
+ // RFC 7292 section 4.1:
+ // the contentType field of authSafe shall be of type data
+ // or signedData. The content field of the authSafe shall, either
+ // directly (data case) or indirectly (signedData case), contain a BER-
+ // encoded value of type AuthenticatedSafe.
+ // We don't support authSafe that is signedData, so enforce that it's just data.
+ if (AuthSafe.ContentType != Oids.Pkcs7Data)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ ReadOnlyMemory authSafeContents = Helpers.DecodeOctetStringAsMemory(AuthSafe.Content);
+ AsnValueReader outerAuthSafe = new AsnValueReader(authSafeContents.Span, AsnEncodingRules.BER); // RFC 7292 PDU says BER
+ AsnValueReader authSafeReader = outerAuthSafe.ReadSequence();
+ outerAuthSafe.ThrowIfNotEmpty();
+
+ bool hasSeenEncryptedInfo = false;
+
+ while (authSafeReader.HasData)
+ {
+ ContentInfoAsn.Decode(ref authSafeReader, authSafeContents, out ContentInfoAsn contentInfo);
+
+ ReadOnlyMemory contentData;
+ ArraySegment? rentedData = null;
+
+ try
+ {
+ if (contentInfo.ContentType != Oids.Pkcs7Data)
+ {
+ if (contentInfo.ContentType == Oids.Pkcs7Encrypted)
+ {
+ if (hasSeenEncryptedInfo)
+ {
+ // We will process at most one encryptedData ContentInfo. This is the most typical scenario where
+ // certificates are stored in an encryptedData ContentInfo, and keys are shrouded in a data ContentInfo.
+ throw new CryptographicException(SR.Cryptography_X509_PfxWithoutPassword);
+ }
+
+ ArraySegment content = DecryptContentInfo(contentInfo, out uint iterations);
+ contentData = content;
+ rentedData = content;
+ hasSeenEncryptedInfo = true;
+ count += iterations;
+ }
+ else
+ {
+ // Not a common scenario. It's not data or encryptedData, so they need to go through the
+ // regular PKCS12 loader.
+ throw new CryptographicException(SR.Cryptography_X509_PfxWithoutPassword);
+ }
+ }
+ else
+ {
+ contentData = Helpers.DecodeOctetStringAsMemory(contentInfo.Content);
+ }
+
+ AsnValueReader outerSafeBag = new AsnValueReader(contentData.Span, AsnEncodingRules.BER);
+ AsnValueReader safeBagReader = outerSafeBag.ReadSequence();
+ outerSafeBag.ThrowIfNotEmpty();
+
+ while (safeBagReader.HasData)
+ {
+ SafeBagAsn.Decode(ref safeBagReader, contentData, out SafeBagAsn bag);
+
+ // We only need to count iterations on PKCS8ShroudedKeyBag.
+ // * KeyBag is PKCS#8 PrivateKeyInfo and doesn't do iterations.
+ // * CertBag, either for x509Certificate or sdsiCertificate don't do iterations.
+ // * CRLBag doesn't do iterations.
+ // * SecretBag doesn't do iteations.
+ // * Nested SafeContents _can_ do iterations, but Windows ignores it. So we will ignore it too.
+ if (bag.BagId == Oids.Pkcs12ShroudedKeyBag)
+ {
+ AsnValueReader pkcs8ShroudedKeyReader = new AsnValueReader(bag.BagValue.Span, AsnEncodingRules.BER);
+ EncryptedPrivateKeyInfoAsn.Decode(
+ ref pkcs8ShroudedKeyReader,
+ bag.BagValue,
+ out EncryptedPrivateKeyInfoAsn epki);
+
+ count += IterationsFromParameters(epki.EncryptionAlgorithm);
+ }
+ }
+ }
+ finally
+ {
+ if (rentedData.HasValue)
+ {
+ CryptoPool.Return(rentedData.Value);
+ }
+ }
+ }
+
+ if (MacData.HasValue)
+ {
+ if (MacData.Value.IterationCount < 0)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ count += (uint)MacData.Value.IterationCount;
+ }
+
+ return count;
+ }
+ }
+
+ private static ArraySegment DecryptContentInfo(ContentInfoAsn contentInfo, out uint iterations)
+ {
+ EncryptedDataAsn encryptedData = EncryptedDataAsn.Decode(contentInfo.Content, AsnEncodingRules.BER);
+
+ if (encryptedData.Version != 0 && encryptedData.Version != 2)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ // The encrypted contentInfo can only wrap a PKCS7 data.
+ if (encryptedData.EncryptedContentInfo.ContentType != Oids.Pkcs7Data)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ if (!encryptedData.EncryptedContentInfo.EncryptedContent.HasValue)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ iterations = IterationsFromParameters(encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm);
+
+ // This encryptData is encrypted with more rounds than we are willing to process. Bail out of the whole thing.
+ if (iterations > MaxIterationWork)
+ {
+ throw new CryptographicException(SR.Cryptography_X509_PfxWithoutPassword);
+ }
+
+ int encryptedValueLength = encryptedData.EncryptedContentInfo.EncryptedContent.Value.Length;
+ byte[] destination = CryptoPool.Rent(encryptedValueLength);
+ int written = 0;
+
+ try
+ {
+ try
+ {
+ written = PasswordBasedEncryption.Decrypt(
+ in encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm,
+ EmptyPassword,
+ default,
+ encryptedData.EncryptedContentInfo.EncryptedContent.Value.Span,
+ destination);
+ }
+ catch
+ {
+ // If empty password didn't work, try null password.
+ written = PasswordBasedEncryption.Decrypt(
+ in encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm,
+ NullPassword,
+ default,
+ encryptedData.EncryptedContentInfo.EncryptedContent.Value.Span,
+ destination);
+ }
+ }
+ finally
+ {
+ if (written == 0)
+ {
+ // This means the decryption operation failed and destination could contain
+ // partial data. Clear it to be hygienic.
+ CryptographicOperations.ZeroMemory(destination);
+ }
+ }
+
+ return new ArraySegment(destination, 0, written);
+ }
+
+ private static uint IterationsFromParameters(in AlgorithmIdentifierAsn algorithmIdentifier)
+ {
+ switch (algorithmIdentifier.Algorithm)
+ {
+ case Oids.PasswordBasedEncryptionScheme2:
+ if (!algorithmIdentifier.Parameters.HasValue)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ PBES2Params pbes2Params = PBES2Params.Decode(algorithmIdentifier.Parameters.Value, AsnEncodingRules.BER);
+
+ // PBES2 only defines PKBDF2 for now. See RFC 8018 A.4
+ if (pbes2Params.KeyDerivationFunc.Algorithm != Oids.Pbkdf2)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ if (!pbes2Params.KeyDerivationFunc.Parameters.HasValue)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ Pbkdf2Params pbkdf2Params = Pbkdf2Params.Decode(pbes2Params.KeyDerivationFunc.Parameters.Value, AsnEncodingRules.BER);
+
+ if (pbkdf2Params.IterationCount < 0)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ return (uint)pbkdf2Params.IterationCount;
+
+ // PBES1
+ case Oids.PbeWithMD5AndDESCBC:
+ case Oids.PbeWithMD5AndRC2CBC:
+ case Oids.PbeWithSha1AndDESCBC:
+ case Oids.PbeWithSha1AndRC2CBC:
+ case Oids.Pkcs12PbeWithShaAnd3Key3Des:
+ case Oids.Pkcs12PbeWithShaAnd2Key3Des:
+ case Oids.Pkcs12PbeWithShaAnd128BitRC2:
+ case Oids.Pkcs12PbeWithShaAnd40BitRC2:
+ if (!algorithmIdentifier.Parameters.HasValue)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ PBEParameter pbeParameters = PBEParameter.Decode(
+ algorithmIdentifier.Parameters.Value,
+ AsnEncodingRules.BER);
+
+ if (pbeParameters.IterationCount < 0)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ return (uint)pbeParameters.IterationCount;
+
+ default:
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ }
}
}
diff --git a/src/libraries/Common/src/System/Security/Cryptography/KdfWorkLimiter.cs b/src/libraries/Common/src/System/Security/Cryptography/KdfWorkLimiter.cs
new file mode 100644
index 00000000000000..7500212fe27d9c
--- /dev/null
+++ b/src/libraries/Common/src/System/Security/Cryptography/KdfWorkLimiter.cs
@@ -0,0 +1,86 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+
+namespace System.Security.Cryptography
+{
+ // Places KDF work limits on the current thread.
+ internal static class KdfWorkLimiter
+ {
+ [ThreadStatic]
+ private static State? t_state;
+
+ // Entry point: sets the iteration limit to a new value.
+ internal static void SetIterationLimit(ulong workLimit)
+ {
+ Debug.Assert(t_state == null, "This method is not intended to be called recursively.");
+ State state = new State();
+ state.RemainingAllowedWork = workLimit;
+ t_state = state;
+ }
+
+ internal static bool WasWorkLimitExceeded()
+ {
+ Debug.Assert(t_state != null, "This method should only be called within a protected block.");
+ return t_state.WorkLimitWasExceeded;
+ }
+
+ // Removes any iteration limit on the current thread.
+ internal static void ResetIterationLimit()
+ {
+ t_state = null;
+ }
+
+ // Records that we're about to perform some amount of work.
+ // Overflows if the work count is exceeded.
+ internal static void RecordIterations(int workCount)
+ {
+ RecordIterations((long)workCount);
+ }
+
+ // Records that we're about to perform some amount of work.
+ // Overflows if the work count is exceeded.
+ internal static void RecordIterations(long workCount)
+ {
+ State? state = t_state;
+ if (state == null)
+ {
+ return;
+ }
+
+ bool success = false;
+
+ if (workCount < 0)
+ {
+ throw new CryptographicException();
+ }
+
+ try
+ {
+ if (!state.WorkLimitWasExceeded)
+ {
+ state.RemainingAllowedWork = checked(state.RemainingAllowedWork - (ulong)workCount);
+ success = true;
+ }
+ }
+ finally
+ {
+ // If for any reason we failed, mark the thread as "no further work allowed" and
+ // normalize to CryptographicException.
+ if (!success)
+ {
+ state.RemainingAllowedWork = 0;
+ state.WorkLimitWasExceeded = true;
+ throw new CryptographicException();
+ }
+ }
+ }
+
+ private sealed class State
+ {
+ internal ulong RemainingAllowedWork;
+ internal bool WorkLimitWasExceeded;
+ }
+ }
+}
diff --git a/src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs b/src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs
index 800b4f335e892c..4bf056ffd5f4f9 100644
--- a/src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs
+++ b/src/libraries/Common/src/System/Security/Cryptography/PasswordBasedEncryption.cs
@@ -393,6 +393,7 @@ internal static unsafe int Encrypt(
Debug.Assert(pwdTmpBytes!.Length == 0);
}
+ KdfWorkLimiter.RecordIterations(iterationCount);
using (var pbkdf2 = new Rfc2898DeriveBytes(pwdTmpBytes, salt.ToArray(), iterationCount, prf))
{
derivedKey = pbkdf2.GetBytes(keySizeBytes);
diff --git a/src/libraries/Common/src/System/Security/Cryptography/Pkcs12Kdf.cs b/src/libraries/Common/src/System/Security/Cryptography/Pkcs12Kdf.cs
index 1bd7c53bb2b44a..8e482b931c7646 100644
--- a/src/libraries/Common/src/System/Security/Cryptography/Pkcs12Kdf.cs
+++ b/src/libraries/Common/src/System/Security/Cryptography/Pkcs12Kdf.cs
@@ -147,6 +147,7 @@ private static void Derive(
I = IRented.AsSpan(0, ILen);
}
+ KdfWorkLimiter.RecordIterations(iterationCount);
IncrementalHash hash = IncrementalHash.CreateHash(hashAlgorithm);
try
diff --git a/src/libraries/Common/tests/System/Net/Http/TestHelper.cs b/src/libraries/Common/tests/System/Net/Http/TestHelper.cs
index 8525ed8c1b2974..e9feee002f6ec8 100644
--- a/src/libraries/Common/tests/System/Net/Http/TestHelper.cs
+++ b/src/libraries/Common/tests/System/Net/Http/TestHelper.cs
@@ -158,7 +158,7 @@ public static X509Certificate2 CreateServerSelfSignedCertificate(string name = "
X509Certificate2 cert = req.CreateSelfSigned(start, end);
if (PlatformDetection.IsWindows)
{
- cert = new X509Certificate2(cert.Export(X509ContentType.Pfx));
+ cert = new X509Certificate2(cert.Export(X509ContentType.Pfx), (string?)null);
}
return cert;
diff --git a/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/RevocationResponder.cs b/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/RevocationResponder.cs
index 4e9e5e20612a94..b08655e6f1d10b 100644
--- a/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/RevocationResponder.cs
+++ b/src/libraries/Common/tests/System/Security/Cryptography/X509Certificates/RevocationResponder.cs
@@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using System.Web;
+using Xunit;
namespace System.Security.Cryptography.X509Certificates.Tests.Common
{
@@ -29,6 +30,7 @@ private readonly Dictionary _crlPaths
public string UriPrefix { get; }
public bool RespondEmpty { get; set; }
+ public AiaResponseKind AiaResponseKind { get; set; }
public TimeSpan ResponseDelay { get; set; }
public DelayedActionsFlag DelayedActions { get; set; }
@@ -181,13 +183,13 @@ private void HandleRequest(HttpListenerContext context, ref bool responded)
Thread.Sleep(ResponseDelay);
}
- byte[] certData = RespondEmpty ? Array.Empty() : authority.GetCertData();
+ byte[] certData = RespondEmpty ? Array.Empty() : GetCertDataForAiaResponseKind(AiaResponseKind, authority);
responded = true;
context.Response.StatusCode = 200;
- context.Response.ContentType = "application/pkix-cert";
+ context.Response.ContentType = AiaResponseKindToContentType(AiaResponseKind);
context.Response.Close(certData, willBlock: true);
- Trace($"Responded with {certData.Length}-byte certificate from {authority.SubjectName}.");
+ Trace($"Responded with {certData.Length}-byte {AiaResponseKind} from {authority.SubjectName}.");
return;
}
@@ -295,6 +297,41 @@ private static HttpListener OpenListener(out string uriPrefix)
}
}
+ private static string AiaResponseKindToContentType(AiaResponseKind kind)
+ {
+ if (kind == AiaResponseKind.Cert)
+ {
+ return "application/pkix-cert";
+ }
+ else if (kind == AiaResponseKind.Pkcs12)
+ {
+ return "application/x-pkcs12";
+ }
+ else
+ {
+ Assert.True(false, $"Unknown value AiaResponseKind.`{kind}`.");
+ return null;
+ }
+ }
+
+ private static byte[] GetCertDataForAiaResponseKind(AiaResponseKind kind, CertificateAuthority authority)
+ {
+ if (kind == AiaResponseKind.Cert)
+ {
+ return authority.GetCertData();
+ }
+ else if (kind == AiaResponseKind.Pkcs12)
+ {
+ using X509Certificate2 cert = new X509Certificate2(authority.GetCertData());
+ return cert.Export(X509ContentType.Pkcs12);
+ }
+ else
+ {
+ Assert.True(false, $"Unknown value AiaResponseKind.`{kind}`.");
+ return null;
+ }
+ }
+
private static bool TryGetOcspRequestBytes(HttpListenerRequest request, string prefix, out byte[] requestBytes)
{
requestBytes = null;
@@ -425,4 +462,10 @@ public enum DelayedActionsFlag : byte
Aia = 0b100,
All = 0b11111111
}
+
+ public enum AiaResponseKind
+ {
+ Cert = 0,
+ Pkcs12 = 1,
+ }
}
diff --git a/src/libraries/System.Data.Common/src/System/Data/Common/SqlUDTStorage.cs b/src/libraries/System.Data.Common/src/System/Data/Common/SqlUDTStorage.cs
index 8ed9938ac835fb..97ac551b1cc87d 100644
--- a/src/libraries/System.Data.Common/src/System/Data/Common/SqlUDTStorage.cs
+++ b/src/libraries/System.Data.Common/src/System/Data/Common/SqlUDTStorage.cs
@@ -182,6 +182,9 @@ public override object ConvertXmlToObject(XmlReader xmlReader, XmlRootAttribute?
}
}
Type type = (typeName == null) ? _dataType : Type.GetType(typeName)!;
+
+ TypeLimiter.EnsureTypeIsAllowed(type);
+
object Obj = System.Activator.CreateInstance(type, true)!;
Debug.Assert(xmlReader is DataTextReader, "Invalid DataTextReader is being passed to customer");
((IXmlSerializable)Obj).ReadXml(xmlReader);
diff --git a/src/libraries/System.Data.Common/tests/System/Data/RestrictedTypeHandlingTests.cs b/src/libraries/System.Data.Common/tests/System/Data/RestrictedTypeHandlingTests.cs
index d42609fed6b5cf..4b425eaea464e5 100644
--- a/src/libraries/System.Data.Common/tests/System/Data/RestrictedTypeHandlingTests.cs
+++ b/src/libraries/System.Data.Common/tests/System/Data/RestrictedTypeHandlingTests.cs
@@ -242,6 +242,59 @@ public void DataTable_HonorsGloballyDefinedAllowList()
}
}
+ [Fact]
+ public void DataTable_HonorsGloballyDefinedAllowListForSqlTypes()
+ {
+ // Arrange
+
+ DataTable table = new DataTable("MyTable");
+ table.Columns.Add("MyNullableColumn", typeof(MyCustomNullable1));
+ table.Rows.Add(new MyCustomNullable1());
+ table.AcceptChanges();
+
+ var asXml = @$"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+";
+
+ // Act & assert
+ // Deserialization should fail since MyCustomNullable2 is not on the allow list,
+ // even though MyCustomNullable1 is on the allow list.
+
+ try
+ {
+ AppDomain.CurrentDomain.SetData(AppDomainDataSetDefaultAllowedTypesKey, new Type[]
+ {
+ typeof(MyCustomNullable1)
+ });
+
+ table = new DataTable();
+ Assert.Throws(() => table.ReadXml(new StringReader(asXml)));
+ }
+ finally
+ {
+ AppDomain.CurrentDomain.SetData(AppDomainDataSetDefaultAllowedTypesKey, null);
+ }
+ }
+
[Fact]
public void DataColumn_ConvertExpression_SubjectToAllowList_Success()
{
@@ -401,6 +454,20 @@ private sealed class MyCustomClass
{
}
+ public sealed class MyCustomNullable1 : INullable
+ {
+ public static MyCustomNullable1 Null { get; } = new MyCustomNullable1();
+
+ public bool IsNull => false;
+ }
+
+ public sealed class MyCustomNullable2 : INullable
+ {
+ public static MyCustomNullable2 Null { get; } = new MyCustomNullable2();
+
+ public bool IsNull => false;
+ }
+
public sealed class MyXmlSerializableClass : IXmlSerializable
{
public XmlSchema GetSchema()
diff --git a/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj b/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj
index 33a9aa8c1dc0cb..9c219ec9ef564c 100644
--- a/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj
+++ b/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj
@@ -33,7 +33,9 @@
+
+
@@ -47,7 +49,6 @@
-
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 65315b006d918f..36cbdab8a7cc78 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
@@ -368,9 +368,17 @@ internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, b
// If the path can be extracted in the specified destination directory, returns the full path with sanitized file name. Otherwise, returns null.
private static string? GetSanitizedFullPath(string destinationDirectoryFullPath, string path)
{
+ destinationDirectoryFullPath = PathInternal.EnsureTrailingSeparator(destinationDirectoryFullPath);
+
string fullyQualifiedPath = Path.IsPathFullyQualified(path) ? path : Path.Combine(destinationDirectoryFullPath, path);
string normalizedPath = Path.GetFullPath(fullyQualifiedPath); // Removes relative segments
- string sanitizedPath = Path.Join(Path.GetDirectoryName(normalizedPath), ArchivingUtils.SanitizeEntryFilePath(Path.GetFileName(normalizedPath)));
+ string? fileName = Path.GetFileName(normalizedPath);
+ if (string.IsNullOrEmpty(fileName)) // It's a directory
+ {
+ fileName = PathInternal.DirectorySeparatorCharAsString;
+ }
+
+ string sanitizedPath = Path.Join(Path.GetDirectoryName(normalizedPath), ArchivingUtils.SanitizeEntryFilePath(fileName));
return sanitizedPath.StartsWith(destinationDirectoryFullPath, PathInternal.StringComparison) ? sanitizedPath : null;
}
diff --git a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj
index ad1e7316f99462..063d064a0cb8db 100644
--- a/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj
+++ b/src/libraries/System.Formats.Tar/tests/System.Formats.Tar.Tests.csproj
@@ -13,6 +13,7 @@
+
diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Base.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Base.cs
new file mode 100644
index 00000000000000..b5a1f111aa6a46
--- /dev/null
+++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.Base.cs
@@ -0,0 +1,80 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace System.Formats.Tar.Tests;
+
+public abstract class TarFile_ExtractToDirectory_Tests : TarTestsBase
+{
+ // TarEntryFormat, TarEntryType, string fileName
+ public static IEnumerable