diff --git a/.github/workflows/inter-branch-merge-flow.yml b/.github/workflows/inter-branch-merge-flow.yml new file mode 100644 index 0000000000000..20246c14fc585 --- /dev/null +++ b/.github/workflows/inter-branch-merge-flow.yml @@ -0,0 +1,13 @@ +name: Inter-branch merge workflow +on: + push: + branches: + - release/** + +permissions: + contents: write + pull-requests: write + +jobs: + Merge: + uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@main \ No newline at end of file diff --git a/src/coreclr/debug/createdump/createdump.h b/src/coreclr/debug/createdump/createdump.h index 930e48b916654..68012ffef7a3d 100644 --- a/src/coreclr/debug/createdump/createdump.h +++ b/src/coreclr/debug/createdump/createdump.h @@ -132,5 +132,10 @@ typedef struct extern bool CreateDump(const CreateDumpOptions& options); extern bool FormatDumpName(std::string& name, const char* pattern, const char* exename, int pid); +#ifdef HOST_WINDOWS +extern DWORD GetTempPathWrapper(IN DWORD nBufferLength, OUT LPSTR lpBuffer); +#else +#define GetTempPathWrapper GetTempPathA +#endif extern void printf_status(const char* format, ...); extern void printf_error(const char* format, ...); diff --git a/src/coreclr/debug/createdump/createdumpwindows.cpp b/src/coreclr/debug/createdump/createdumpwindows.cpp index b8384e4c8580f..d598ade93e81f 100644 --- a/src/coreclr/debug/createdump/createdumpwindows.cpp +++ b/src/coreclr/debug/createdump/createdumpwindows.cpp @@ -73,3 +73,38 @@ CreateDump(const CreateDumpOptions& options) return result; } + +typedef DWORD(WINAPI *pfnGetTempPathA)(DWORD nBufferLength, LPSTR lpBuffer); + +static volatile pfnGetTempPathA +g_pfnGetTempPathA = nullptr; + + +DWORD +GetTempPathWrapper( + IN DWORD nBufferLength, + OUT LPSTR lpBuffer) +{ + if (g_pfnGetTempPathA == nullptr) + { + HMODULE hKernel32 = LoadLibraryExW(L"kernel32.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + + pfnGetTempPathA pLocalGetTempPathA = NULL; + if (hKernel32 != NULL) + { + // store to thread local variable to prevent data race + pLocalGetTempPathA = (pfnGetTempPathA)::GetProcAddress(hKernel32, "GetTempPath2A"); + } + + if (pLocalGetTempPathA == NULL) // method is only available with Windows 10 Creators Update or later + { + g_pfnGetTempPathA = &GetTempPathA; + } + else + { + g_pfnGetTempPathA = pLocalGetTempPathA; + } + } + + return g_pfnGetTempPathA(nBufferLength, lpBuffer); +} \ No newline at end of file diff --git a/src/coreclr/debug/createdump/main.cpp b/src/coreclr/debug/createdump/main.cpp index 6dd116a7a3c33..89c8c7f404350 100644 --- a/src/coreclr/debug/createdump/main.cpp +++ b/src/coreclr/debug/createdump/main.cpp @@ -175,7 +175,7 @@ int __cdecl main(const int argc, const char* argv[]) if (options.DumpPathTemplate == nullptr) { - if (::GetTempPathA(MAX_LONGPATH, tmpPath) == 0) + if (::GetTempPathWrapper(MAX_LONGPATH, tmpPath) == 0) { printf_error("GetTempPath failed (0x%08x)\n", ::GetLastError()); return ::GetLastError(); diff --git a/src/coreclr/inc/longfilepathwrappers.h b/src/coreclr/inc/longfilepathwrappers.h index 149cdcf4443ac..e62f1ae6be3c7 100644 --- a/src/coreclr/inc/longfilepathwrappers.h +++ b/src/coreclr/inc/longfilepathwrappers.h @@ -87,10 +87,6 @@ UINT WINAPI GetTempFileNameWrapper( SString& lpTempFileName ); -DWORD WINAPI GetTempPathWrapper( - SString& lpBuffer - ); - DWORD WINAPI GetCurrentDirectoryWrapper( SString& lpBuffer ); diff --git a/src/coreclr/inc/winwrap.h b/src/coreclr/inc/winwrap.h index 4d97618984fd2..96fdb678ddbd7 100644 --- a/src/coreclr/inc/winwrap.h +++ b/src/coreclr/inc/winwrap.h @@ -220,7 +220,6 @@ //Long Files will not work on these till redstone #define WszGetCurrentDirectory GetCurrentDirectoryWrapper #define WszGetTempFileName GetTempFileNameWrapper -#define WszGetTempPath GetTempPathWrapper //APIS which have a buffer as an out parameter #define WszGetEnvironmentVariable GetEnvironmentVariableWrapper diff --git a/src/coreclr/utilcode/longfilepathwrappers.cpp b/src/coreclr/utilcode/longfilepathwrappers.cpp index 3afc611c3b218..a383090692595 100644 --- a/src/coreclr/utilcode/longfilepathwrappers.cpp +++ b/src/coreclr/utilcode/longfilepathwrappers.cpp @@ -514,46 +514,6 @@ UINT WINAPI GetTempFileNameWrapper( return ret; } -DWORD WINAPI GetTempPathWrapper( - SString& lpBuffer - ) -{ - CONTRACTL - { - NOTHROW; - } - CONTRACTL_END; - - HRESULT hr = S_OK; - DWORD ret = 0; - DWORD lastError; - - EX_TRY - { - //Change the behaviour in Redstone to retry - COUNT_T size = MAX_LONGPATH; - - ret = GetTempPathW( - size, - lpBuffer.OpenUnicodeBuffer(size - 1) - ); - - lastError = GetLastError(); - lpBuffer.CloseBuffer(ret); - } - EX_CATCH_HRESULT(hr); - - if (hr != S_OK) - { - SetLastError(hr); - } - else if (ret == 0) - { - SetLastError(lastError); - } - - return ret; -} DWORD WINAPI GetCurrentDirectoryWrapper( SString& lpBuffer diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetProcAddress.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetProcAddress.cs index 214050545ed15..08a2f9ffd33f0 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetProcAddress.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetProcAddress.cs @@ -10,8 +10,10 @@ internal static partial class Interop { internal static partial class Kernel32 { +#if !MS_IO_REDIST // SafeLibraryHandle is inaccessible due to its protection level [DllImport(Libraries.Kernel32, CharSet = CharSet.Ansi, BestFitMapping = false)] public static extern IntPtr GetProcAddress(SafeLibraryHandle hModule, string lpProcName); +#endif [DllImport(Libraries.Kernel32, CharSet = CharSet.Ansi, BestFitMapping = false)] public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetTempPathW.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetTempPathW.cs index 5d555df58cd23..80c71479c8e51 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetTempPathW.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.GetTempPathW.cs @@ -9,5 +9,8 @@ internal static partial class Kernel32 { [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)] internal static extern uint GetTempPathW(int bufferLen, ref char buffer); + + [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)] + internal static extern uint GetTempPath2W(int bufferLen, ref char buffer); } } diff --git a/src/libraries/Common/src/System/Security/Cryptography/DSAKeyFormatHelper.cs b/src/libraries/Common/src/System/Security/Cryptography/DSAKeyFormatHelper.cs index 63c7b451e142b..e63792f3ba1ac 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/DSAKeyFormatHelper.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/DSAKeyFormatHelper.cs @@ -25,16 +25,6 @@ internal static void ReadDsaPrivateKey( throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } - DssParms parms = DssParms.Decode(algId.Parameters.Value, AsnEncodingRules.BER); - - ret = new DSAParameters - { - P = parms.P.ToByteArray(isUnsigned: true, isBigEndian: true), - Q = parms.Q.ToByteArray(isUnsigned: true, isBigEndian: true), - }; - - ret.G = parms.G.ExportKeyParameter(ret.P.Length); - BigInteger x; try @@ -57,6 +47,34 @@ internal static void ReadDsaPrivateKey( throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); } + DssParms parms = DssParms.Decode(algId.Parameters.Value, AsnEncodingRules.BER); + + // Sanity checks from FIPS 186-4 4.1/4.2. Since FIPS 186-5 withdrew DSA/DSS + // these will never change again. + // + // This technically allows a non-standard combination of 1024-bit P and 256-bit Q, + // but that will get filtered out by the underlying provider. + // These checks just prevent obviously bad data from wasting work on reinterpretation. + + if (parms.P.Sign < 0 || + parms.Q.Sign < 0 || + !IsValidPLength(parms.P.GetBitLength()) || + !IsValidQLength(parms.Q.GetBitLength()) || + parms.G <= 1 || + parms.G >= parms.P || + x <= 1 || + x >= parms.Q) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + + ret = new DSAParameters + { + P = parms.P.ToByteArray(isUnsigned: true, isBigEndian: true), + Q = parms.Q.ToByteArray(isUnsigned: true, isBigEndian: true), + }; + + ret.G = parms.G.ExportKeyParameter(ret.P.Length); ret.X = x.ExportKeyParameter(ret.Q.Length); // The public key is not contained within the format, calculate it. @@ -69,6 +87,11 @@ internal static void ReadDsaPublicKey( in AlgorithmIdentifierAsn algId, out DSAParameters ret) { + if (!algId.Parameters.HasValue) + { + throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); + } + BigInteger y; try @@ -88,13 +111,27 @@ internal static void ReadDsaPublicKey( throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); } - if (!algId.Parameters.HasValue) + DssParms parms = DssParms.Decode(algId.Parameters.Value, AsnEncodingRules.BER); + + // Sanity checks from FIPS 186-4 4.1/4.2. Since FIPS 186-5 withdrew DSA/DSS + // these will never change again. + // + // This technically allows a non-standard combination of 1024-bit P and 256-bit Q, + // but that will get filtered out by the underlying provider. + // These checks just prevent obviously bad data from wasting work on reinterpretation. + + if (parms.P.Sign < 0 || + parms.Q.Sign < 0 || + !IsValidPLength(parms.P.GetBitLength()) || + !IsValidQLength(parms.Q.GetBitLength()) || + parms.G <= 1 || + parms.G >= parms.P || + y <= 1 || + y >= parms.P) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } - DssParms parms = DssParms.Decode(algId.Parameters.Value, AsnEncodingRules.BER); - ret = new DSAParameters { P = parms.P.ToByteArray(isUnsigned: true, isBigEndian: true), @@ -105,6 +142,25 @@ internal static void ReadDsaPublicKey( ret.Y = y.ExportKeyParameter(ret.P.Length); } + private static bool IsValidPLength(long pBitLength) + { + return pBitLength switch + { + // FIPS 186-3/186-4 + 1024 or 2048 or 3072 => true, + // FIPS 186-1/186-2 + >= 512 and < 1024 => pBitLength % 64 == 0, + _ => false, + }; + } + + private static bool IsValidQLength(long qBitLength) + { + // FIPS 196-1/186-2 only allows 160 + // FIPS 186-3/186-4 allow 160/224/256 + return qBitLength is 160 or 224 or 256; + } + internal static void ReadSubjectPublicKeyInfo( ReadOnlySpan source, out int bytesRead, diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAKeyFileTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAKeyFileTests.cs index 5f3a0f5b82a6d..27a94ab1907d1 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAKeyFileTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DSA/DSAKeyFileTests.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Asn1; +using System.Numerics; using System.Security.Cryptography.Encryption.RC2.Tests; using System.Text; using Test.Cryptography; @@ -302,6 +304,150 @@ public static void ReadWriteDsa2048SubjectPublicKeyInfo() DSATestData.GetDSA2048Params()); } + [Fact] + [SkipOnPlatform(TestPlatforms.OSX, "DSASecurityTransforms goes straight to OS, has different failure mode")] + public static void ImportNonsensePublicParameters() + { + AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); + + DSAParameters validParameters = DSATestData.GetDSA2048Params(); + BigInteger p = new BigInteger(validParameters.P, true, true); + BigInteger q = new BigInteger(validParameters.Q, true, true); + BigInteger g = new BigInteger(validParameters.G, true, true); + BigInteger y = new BigInteger(validParameters.Y, true, true); + + using (DSA dsa = DSAFactory.Create()) + { + // 1 < y < p, 1 < g < p, q is 160/224/256 bits + // p is 512..1024 % 64, or 1024/2048/3072 bits + ImportSPKI(dsa, p, q, g, p, writer); + ImportSPKI(dsa, p, q, g, BigInteger.One, writer); + ImportSPKI(dsa, p, q, g, BigInteger.MinusOne, writer); + ImportSPKI(dsa, p, q, p, y, writer); + ImportSPKI(dsa, p, q, -g, y, writer); + ImportSPKI(dsa, p, q, BigInteger.One, y, writer); + ImportSPKI(dsa, p, q, BigInteger.MinusOne, y, writer); + ImportSPKI(dsa, p, q << 1, g, y, writer); + ImportSPKI(dsa, p, q >> 1, g, y, writer); + ImportSPKI(dsa, p, -q, g, y, writer); + ImportSPKI(dsa, p >> 1, q, g, y, writer); + ImportSPKI(dsa, p << 1, q, g, y, writer); + ImportSPKI(dsa, BigInteger.One << 4095, q, 2, 97, writer); + } + + static void ImportSPKI( + DSA key, + BigInteger p, + BigInteger q, + BigInteger g, + BigInteger y, + AsnWriter writer) + { + writer.Reset(); + writer.WriteInteger(y); + byte[] encodedPublicKey = writer.Encode(); + writer.Reset(); + + using (writer.PushSequence()) + { + using (writer.PushSequence()) + { + writer.WriteObjectIdentifier("1.2.840.10040.4.1"); + + using (writer.PushSequence()) + { + writer.WriteInteger(p); + writer.WriteInteger(q); + writer.WriteInteger(g); + } + } + + writer.WriteBitString(encodedPublicKey); + } + + byte[] spki = writer.Encode(); + writer.Reset(); + + AssertExtensions.ThrowsContains( + () => key.ImportSubjectPublicKeyInfo(spki, out _), + "corrupted"); + } + } + + [Fact] + public static void ImportNonsensePrivateParameters() + { + AsnWriter writer = new AsnWriter(AsnEncodingRules.DER); + + DSAParameters validParameters = DSATestData.GetDSA2048Params(); + BigInteger p = new BigInteger(validParameters.P, true, true); + BigInteger q = new BigInteger(validParameters.Q, true, true); + BigInteger g = new BigInteger(validParameters.G, true, true); + BigInteger x = new BigInteger(validParameters.X, true, true); + + using (DSA dsa = DSAFactory.Create()) + { + // 1 < x < q, 1 < g < p, q is 160/224/256 bits + // p is 512..1024 % 64, or 1024/2048/3072 bits + ImportPkcs8(dsa, p, q, g, q, writer); + ImportPkcs8(dsa, p, q, g, BigInteger.One, writer); + // x = -1 gets re-interpreted as x = 255 because of a CAPI compat issue. + //ImportPkcs8(dsa, p, q, g, BigInteger.MinusOne, writer); + ImportPkcs8(dsa, p, q, g, -x, writer); + ImportPkcs8(dsa, p, q, p, x, writer); + ImportPkcs8(dsa, p, q, -g, x, writer); + ImportPkcs8(dsa, p, q, BigInteger.One, x, writer); + ImportPkcs8(dsa, p, q, BigInteger.MinusOne, x, writer); + ImportPkcs8(dsa, p, q << 1, g, x, writer); + ImportPkcs8(dsa, p, q >> 1, g, x, writer); + ImportPkcs8(dsa, p >> 1, q, g, x, writer); + ImportPkcs8(dsa, p << 1, q, g, x, writer); + ImportPkcs8(dsa, -q, q, g, x, writer); + ImportPkcs8(dsa, BigInteger.One << 4095, q, 2, 97, writer); + ImportPkcs8(dsa, -p, q, g, x, writer); + } + + static void ImportPkcs8( + DSA key, + BigInteger p, + BigInteger q, + BigInteger g, + BigInteger x, + AsnWriter writer) + { + writer.Reset(); + + using (writer.PushSequence()) + { + writer.WriteInteger(0); + + using (writer.PushSequence()) + { + writer.WriteObjectIdentifier("1.2.840.10040.4.1"); + + using (writer.PushSequence()) + { + writer.WriteInteger(p); + writer.WriteInteger(q); + writer.WriteInteger(g); + } + } + + using (writer.PushOctetString()) + { + writer.WriteInteger(x); + } + } + + byte[] pkcs8 = writer.Encode(); + writer.Reset(); + + AssertExtensions.ThrowsContains( + () => key.ImportPkcs8PrivateKey(pkcs8, out _), + "corrupted"); + } + } + [Fact] public static void NoFuzzySubjectPublicKeyInfo() { diff --git a/src/libraries/Microsoft.IO.Redist/src/Microsoft.IO.Redist.csproj b/src/libraries/Microsoft.IO.Redist/src/Microsoft.IO.Redist.csproj index 674d785cb7e8b..f86b4eda02cc0 100644 --- a/src/libraries/Microsoft.IO.Redist/src/Microsoft.IO.Redist.csproj +++ b/src/libraries/Microsoft.IO.Redist/src/Microsoft.IO.Redist.csproj @@ -1,6 +1,8 @@  true + true + 1 Downlevel support package for System.IO classes. $(DefineConstants);MS_IO_REDIST true @@ -108,6 +110,10 @@ Link="Interop\Windows\Interop.GetTempFileNameW.cs" /> + + The encoded named bit list value is larger than the value size of the '{0}' enum. + + The encoded object identifier (OID) exceeds the limits supported by this library. Supported OIDs are limited to 64 arcs and each subidentifier is limited to a 128-bit value. + The encoded value uses a constructed encoding, which is invalid for '{0}' values. diff --git a/src/libraries/System.Formats.Asn1/src/System.Formats.Asn1.csproj b/src/libraries/System.Formats.Asn1/src/System.Formats.Asn1.csproj index 6c8a6d9ed36c5..255d375562204 100644 --- a/src/libraries/System.Formats.Asn1/src/System.Formats.Asn1.csproj +++ b/src/libraries/System.Formats.Asn1/src/System.Formats.Asn1.csproj @@ -4,6 +4,8 @@ enable $(NetCoreAppCurrent);netstandard2.0;net461 true + true + 1 Provides classes that can read and write the ASN.1 BER, CER, and DER data formats. Commonly Used Types: @@ -11,6 +13,9 @@ System.Formats.Asn1.AsnReader System.Formats.Asn1.AsnWriter + + Common\System\LocalAppContextSwitches.Common.cs + Common\System\Security\Cryptography\CryptoPool.cs @@ -48,6 +53,7 @@ System.Formats.Asn1.AsnWriter + diff --git a/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.Oid.cs b/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.Oid.cs index 44106332bf015..67c04b7f1351e 100644 --- a/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.Oid.cs +++ b/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.Oid.cs @@ -71,15 +71,55 @@ private static void ReadSubIdentifier( throw new AsnContentException(); } + // Set semanticBits to a value such that on the first + // iteration of the loop it becomes the correct value. + // So each entry here is [real semantic bits for this value] - 7. + int semanticBits = source[0] switch + { + >= 0b1100_0000 => 0, + >= 0b1010_0000 => -1, + >= 0b1001_0000 => -2, + >= 0b1000_1000 => -3, + >= 0b1000_0100 => -4, + >= 0b1000_0010 => -5, + >= 0b1000_0001 => -6, + _ => 0, + }; + // First, see how long the segment is int end = -1; int idx; + // None of T-REC-X.660-201107, T-REC-X.680-201508, or T-REC-X.690-201508 + // have any recommendations for a minimum (or maximum) size of a + // sub-identifier. + // + // T-REC-X.667-201210 (and earlier versions) discuss the no-registration- + // required UUID space at 2.25.{UUID}, where UUIDs are defined as 128-bit + // values. This gives us a minimum lower bound of 128-bit. + // + // Windows Crypt32 has historically only supported 64-bit values, and + // the "size limitations" FAQ on oid-info.com says that the largest arc + // value is a 39-digit value that corresponds to a 2.25.UUID value. + // + // So, until something argues for a bigger number, our bit-limit is 128. + const int MaxAllowedBits = 128; + for (idx = 0; idx < source.Length; idx++) { // If the high bit isn't set this marks the end of the sub-identifier. bool endOfIdentifier = (source[idx] & 0x80) == 0; + if (!LocalAppContextSwitches.AllowAnySizeOid) + { + semanticBits += 7; + + if (semanticBits > MaxAllowedBits) + { + throw new AsnContentException(SR.ContentException_OidTooBig); + } + } + if (endOfIdentifier) { end = idx; @@ -258,8 +298,21 @@ private static string ReadObjectIdentifier( contents = contents.Slice(bytesRead); + const int MaxArcs = 64; + int remainingArcs = MaxArcs - 2; + while (!contents.IsEmpty) { + if (!LocalAppContextSwitches.AllowAnySizeOid) + { + if (remainingArcs <= 0) + { + throw new AsnContentException(SR.ContentException_OidTooBig); + } + + remainingArcs--; + } + ReadSubIdentifier(contents, out bytesRead, out smallValue, out largeValue); // Exactly one should be non-null. Debug.Assert((smallValue == null) != (largeValue == null)); diff --git a/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/LocalAppContextSwitches.cs b/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/LocalAppContextSwitches.cs new file mode 100644 index 0000000000000..62722d7f4d000 --- /dev/null +++ b/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/LocalAppContextSwitches.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System +{ + internal static partial class LocalAppContextSwitches + { + private static int s_allowAnySizeOid; + public static bool AllowAnySizeOid + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => GetCachedSwitchValue("System.Formats.Asn1.AllowAnySizeOid", ref s_allowAnySizeOid); + } + } +} diff --git a/src/libraries/System.Formats.Asn1/tests/Reader/ReadObjectIdentifier.cs b/src/libraries/System.Formats.Asn1/tests/Reader/ReadObjectIdentifier.cs index c1add074220a0..6584fd47b8f6b 100644 --- a/src/libraries/System.Formats.Asn1/tests/Reader/ReadObjectIdentifier.cs +++ b/src/libraries/System.Formats.Asn1/tests/Reader/ReadObjectIdentifier.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Text; +using Microsoft.DotNet.RemoteExecutor; using Test.Cryptography; using Xunit; @@ -198,70 +199,188 @@ public static void ExpectedTag_IgnoresConstructed( [InlineData(AsnEncodingRules.BER)] [InlineData(AsnEncodingRules.CER)] [InlineData(AsnEncodingRules.DER)] - public static void ReadVeryLongOid(AsnEncodingRules ruleSet) + public static void ReadMaximumArcOid(AsnEncodingRules ruleSet) { - byte[] inputData = new byte[100000]; - // 06 83 02 00 00 (OBJECT IDENTIFIER, 65536 bytes). - inputData[0] = 0x06; - inputData[1] = 0x83; - inputData[2] = 0x01; - inputData[3] = 0x00; - inputData[4] = 0x00; - // and the rest are all zero. - - // The first byte produces "0.0". Each of the remaining 65535 bytes produce - // another ".0". - const int ExpectedLength = 65536 * 2 + 1; - StringBuilder builder = new StringBuilder(ExpectedLength); - builder.Append('0'); - - for (int i = 0; i <= ushort.MaxValue; i++) + const int MaxArcs = 64; + // MaxArcs content bytes (all 0x7F) (which includes one for failure), plus one for the tag + // plus one for the encoded length. + byte[] input = new byte[MaxArcs + 2]; + input.AsSpan().Fill(0x7F); + input[0] = 0x06; + // The first two arcs are encoded in the first sub-identifier, so MaxArcs - 1. + input[1] = MaxArcs - 1; + + string decoded = AsnDecoder.ReadObjectIdentifier(input, ruleSet, out int consumed); + Assert.Equal(input.Length - 1, consumed); + + StringBuilder expected = new StringBuilder(4 * MaxArcs); + expected.Append("2.47"); + + for (int i = 2; i < MaxArcs; i++) { - builder.Append('.'); - builder.Append(0); + expected.Append(".127"); } - AsnReader reader = new AsnReader(inputData, ruleSet); - string oidString = reader.ReadObjectIdentifier(); + Assert.Equal(expected.ToString(), decoded); - Assert.Equal(ExpectedLength, oidString.Length); - Assert.Equal(builder.ToString(), oidString); + input[1] = MaxArcs; + AsnContentException ex = Assert.Throws( + () => AsnDecoder.ReadObjectIdentifier(input, ruleSet, out _)); + Assert.Contains("OID", ex.Message); } [Theory] [InlineData(AsnEncodingRules.BER)] [InlineData(AsnEncodingRules.CER)] [InlineData(AsnEncodingRules.DER)] - public static void ReadVeryLongOidArc(AsnEncodingRules ruleSet) + public static void ReadMaximumInitialSubIdentifier(AsnEncodingRules ruleSet) { - byte[] inputData = new byte[255]; - // 06 81 93 (OBJECT IDENTIFIER, 147 bytes). - inputData[0] = 0x06; - inputData[1] = 0x81; - inputData[2] = 0x93; - - // With 147 bytes we get 147*7 = 1029 value bits. - // The smallest legal number to encode would have a top byte of 0x81, - // leaving 1022 bits remaining. If they're all zero then we have 2^1022. - // - // Since it's our first sub-identifier it's really encoding "2.(2^1022 - 80)". - inputData[3] = 0x81; - // Leave the last byte as 0. - new Span(inputData, 4, 145).Fill(0x80); - - const string ExpectedOid = - "2." + - "449423283715578976932326297697256183404494244735576643183575" + - "202894331689513752407831771193306018840052800284699678483394" + - "146974422036041556232118576598685310944419733562163713190755" + - "549003115235298632707380212514422095376705856157203684782776" + - "352068092908376276711465745599868114846199290762088390824060" + - "56034224"; + // First sub-identifier is 2^128 - 1, second is 1 + byte[] valid = + { + 0x06, 0x14, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x01, + }; - AsnReader reader = new AsnReader(inputData, ruleSet); + // First sub-identifier is 2^128, second is 1 + byte[] invalid = + { + 0x06, 0x14, 0x84, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x00, 0x01, + }; + + string oid = AsnDecoder.ReadObjectIdentifier(valid, ruleSet, out int consumed); + Assert.Equal(valid.Length, consumed); + Assert.Equal("2.340282366920938463463374607431768211375.1", oid); + + AsnContentException ex = Assert.Throws( + () => AsnDecoder.ReadObjectIdentifier(invalid, ruleSet, out _)); + Assert.Contains("OID", ex.Message); + } - string oidString = reader.ReadObjectIdentifier(); - Assert.Equal(ExpectedOid, oidString); + [Theory] + [InlineData(AsnEncodingRules.BER)] + [InlineData(AsnEncodingRules.CER)] + [InlineData(AsnEncodingRules.DER)] + public static void ReadMaximumNonInitialSubIdentifier(AsnEncodingRules ruleSet) + { + // First sub-identifier is 1, second is 2^128 - 1 + byte[] valid = + { + 0x06, 0x14, 0x01, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, + }; + + // First sub-identifier is 1, second is 2^128 + byte[] invalid = new byte[] + { + 0x06, 0x14, 0x01, 0x84, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, + }; + + string oid = AsnDecoder.ReadObjectIdentifier(valid, ruleSet, out int consumed); + Assert.Equal(valid.Length, consumed); + Assert.Equal("0.1.340282366920938463463374607431768211455", oid); + + AsnContentException ex = Assert.Throws( + () => AsnDecoder.ReadObjectIdentifier(invalid, ruleSet, out _)); + Assert.Contains("OID", ex.Message); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public static void ReadVeryLongOid_WithAppContext() + { + RemoteExecutor.Invoke( + static () => + { + AppContext.SetSwitch("System.Formats.Asn1.AllowAnySizeOid", true); + + byte[] inputData = new byte[100000]; + // 06 83 02 00 00 (OBJECT IDENTIFIER, 65536 bytes). + inputData[0] = 0x06; + inputData[1] = 0x83; + inputData[2] = 0x01; + inputData[3] = 0x00; + inputData[4] = 0x00; + // and the rest are all zero. + + // The first byte produces "0.0". Each of the remaining 65535 bytes produce + // another ".0". + const int ExpectedLength = 65536 * 2 + 1; + StringBuilder builder = new StringBuilder(ExpectedLength); + builder.Append('0'); + + for (int i = 0; i <= ushort.MaxValue; i++) + { + builder.Append('.'); + builder.Append(0); + } + + AsnEncodingRules[] ruleSets = + { + AsnEncodingRules.BER, + AsnEncodingRules.CER, + AsnEncodingRules.DER, + }; + + foreach (AsnEncodingRules ruleSet in ruleSets) + { + AsnReader reader = new AsnReader(inputData, ruleSet); + string oidString = reader.ReadObjectIdentifier(); + + Assert.Equal(ExpectedLength, oidString.Length); + Assert.Equal(builder.ToString(), oidString); + } + }).Dispose(); + } + + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public static void ReadVeryLongOidArc_WithAppContext() + { + RemoteExecutor.Invoke( + static () => + { + AppContext.SetSwitch("System.Formats.Asn1.AllowAnySizeOid", true); + + byte[] inputData = new byte[255]; + // 06 81 93 (OBJECT IDENTIFIER, 147 bytes). + inputData[0] = 0x06; + inputData[1] = 0x81; + inputData[2] = 0x93; + + // With 147 bytes we get 147*7 = 1029 value bits. + // The smallest legal number to encode would have a top byte of 0x81, + // leaving 1022 bits remaining. If they're all zero then we have 2^1022. + // + // Since it's our first sub-identifier it's really encoding "2.(2^1022 - 80)". + inputData[3] = 0x81; + // Leave the last byte as 0. + new Span(inputData, 4, 145).Fill(0x80); + + const string ExpectedOid = + "2." + + "449423283715578976932326297697256183404494244735576643183575" + + "202894331689513752407831771193306018840052800284699678483394" + + "146974422036041556232118576598685310944419733562163713190755" + + "549003115235298632707380212514422095376705856157203684782776" + + "352068092908376276711465745599868114846199290762088390824060" + + "56034224"; + + AsnEncodingRules[] ruleSets = + { + AsnEncodingRules.BER, + AsnEncodingRules.CER, + AsnEncodingRules.DER, + }; + + foreach (AsnEncodingRules ruleSet in ruleSets) + { + AsnReader reader = new AsnReader(inputData, ruleSet); + + string oidString = reader.ReadObjectIdentifier(); + Assert.Equal(ExpectedOid, oidString); + } + }).Dispose(); } } } diff --git a/src/libraries/System.Formats.Asn1/tests/System.Formats.Asn1.Tests.csproj b/src/libraries/System.Formats.Asn1/tests/System.Formats.Asn1.Tests.csproj index 1c4965e48777e..c4bfbf0a5ebc0 100644 --- a/src/libraries/System.Formats.Asn1/tests/System.Formats.Asn1.Tests.csproj +++ b/src/libraries/System.Formats.Asn1/tests/System.Formats.Asn1.Tests.csproj @@ -2,6 +2,7 @@ true $(NetCoreAppCurrent);net461 + true diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index ec495cc7817c9..21af116b5c8a0 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1567,9 +1567,6 @@ Common\Interop\Windows\Kernel32\Interop.GetTempFileNameW.cs - - Common\Interop\Windows\Kernel32\Interop.GetTempPathW.cs - Common\Interop\Windows\Kernel32\Interop.GetVolumeInformation.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs index e0ad5c0c26af2..7c66f82316fe6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; using System.Text; #if MS_IO_REDIST @@ -14,8 +15,14 @@ namespace Microsoft.IO namespace System.IO #endif { - public static partial class Path + public static unsafe partial class Path { +#if MS_IO_REDIST + private static volatile int s_GetTempPathVersion; +#else + private static volatile delegate* unmanaged s_GetTempPathWFunc; +#endif + public static char[] GetInvalidFileNameChars() => new char[] { '\"', '<', '>', '|', '\0', @@ -152,10 +159,37 @@ public static string GetTempPath() return path; } - private static void GetTempPath(ref ValueStringBuilder builder) +#if MS_IO_REDIST + private static int GetGetTempPathVersion() + { + IntPtr kernel32 = Interop.Kernel32.GetModuleHandle(Interop.Libraries.Kernel32); + if (kernel32 != IntPtr.Zero) + { + if (Interop.Kernel32.GetProcAddress(kernel32, "GetTempPath2W") != IntPtr.Zero) + { + return 2; + } + } + return 1; + } +#else + private static unsafe delegate* unmanaged GetGetTempPathWFunc() + { + IntPtr kernel32 = Interop.Kernel32.LoadLibraryEx(Interop.Libraries.Kernel32, IntPtr.Zero, Interop.Kernel32.LOAD_LIBRARY_SEARCH_SYSTEM32); + + if (!NativeLibrary.TryGetExport(kernel32, "GetTempPath2W", out IntPtr func)) + { + func = NativeLibrary.GetExport(kernel32, "GetTempPathW"); + } + + return (delegate* unmanaged)func; + } +#endif + + internal static void GetTempPath(ref ValueStringBuilder builder) { uint result; - while ((result = Interop.Kernel32.GetTempPathW(builder.Capacity, ref builder.GetPinnableReference())) > builder.Capacity) + while ((result = GetTempPathW(builder.Capacity, ref builder.GetPinnableReference())) > builder.Capacity) { // Reported size is greater than the buffer size. Increase the capacity. builder.EnsureCapacity(checked((int)result)); @@ -165,6 +199,38 @@ private static void GetTempPath(ref ValueStringBuilder builder) throw Win32Marshal.GetExceptionForLastWin32Error(); builder.Length = (int)result; + + static uint GetTempPathW(int bufferLen, ref char buffer) + { +#if MS_IO_REDIST + int getTempPathVersion = s_GetTempPathVersion; + if (getTempPathVersion == 0) + { + s_GetTempPathVersion = getTempPathVersion = GetGetTempPathVersion(); + } + return getTempPathVersion == 2 + ? Interop.Kernel32.GetTempPath2W(bufferLen, ref buffer) + : Interop.Kernel32.GetTempPathW(bufferLen, ref buffer); +#else + delegate* unmanaged func = s_GetTempPathWFunc; + if (func == null) + { + func = s_GetTempPathWFunc = GetGetTempPathWFunc(); + } + + int lastError; + uint retVal; + fixed (char* ptr = &buffer) + { + Marshal.SetLastSystemError(0); + retVal = func(bufferLen, ptr); + lastError = Marshal.GetLastSystemError(); + } + + Marshal.SetLastPInvokeError(lastError); + return retVal; +#endif + } } // Returns a unique temporary file name, and creates a 0-byte file by that diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs index cf57a01929bb1..deb7025593506 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs @@ -375,7 +375,7 @@ private void WriteMembers(NameInfo memberNameInfo, return; } - if (!WriteKnownValueClass(memberNameInfo, memberTypeNameInfo, memberData!)) + if (!WriteKnownValueClass(memberNameInfo, memberTypeNameInfo, memberData!, ref assignUniqueIdToValueType)) { if (outType == null) { @@ -624,10 +624,10 @@ private void WriteArrayMember(WriteObjectInfo objectInfo, NameInfo arrayElemType actualTypeInfo._isArrayItem = true; } - if (!WriteKnownValueClass(arrayElemTypeNameInfo, actualTypeInfo, data!)) + bool assignUniqueIdForValueTypes = false; + if (!WriteKnownValueClass(arrayElemTypeNameInfo, actualTypeInfo, data!, ref assignUniqueIdForValueTypes)) { object obj = data!; - bool assignUniqueIdForValueTypes = false; if (ReferenceEquals(arrayElemTypeNameInfo._type, Converter.s_typeofObject)) { assignUniqueIdForValueTypes = true; @@ -807,11 +807,12 @@ private long Schedule(object obj, bool assignUniqueIdToValueType, Type? type, Wr } // Determines if a type is a primitive type, if it is it is written - private bool WriteKnownValueClass(NameInfo memberNameInfo, NameInfo typeNameInfo, object data) + private bool WriteKnownValueClass(NameInfo memberNameInfo, NameInfo typeNameInfo, object data, ref bool assignUniqueIdToValueType) { if (ReferenceEquals(typeNameInfo._type, Converter.s_typeofString)) { WriteString(memberNameInfo, typeNameInfo, data); + return true; } else { @@ -825,18 +826,22 @@ private bool WriteKnownValueClass(NameInfo memberNameInfo, NameInfo typeNameInfo if (typeNameInfo._isArray) // null if an array { _serWriter.WriteItem(memberNameInfo, typeNameInfo, data); + return true; } - else + else if (memberNameInfo._type == typeNameInfo._type + || memberNameInfo._type == typeof(object) + || (memberNameInfo._type != null && Nullable.GetUnderlyingType(memberNameInfo._type) != null)) { _serWriter.WriteMember(memberNameInfo, typeNameInfo, data); + return true; } } } - return true; + assignUniqueIdToValueType = true; + return false; } - // Writes an object reference to the stream. private void WriteObjectRef(NameInfo nameInfo, long objectId) => _serWriter!.WriteMemberObjectRef(nameInfo, (int)objectId); diff --git a/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs b/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs index 7bd5ede42c51b..5baa570e8621e 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs @@ -535,6 +535,25 @@ public void Roundtrip_ArrayContainingArrayAtNonZeroLowerBound() BinaryFormatterHelpers.Clone(Array.CreateInstance(typeof(uint[]), new[] { 5 }, new[] { 1 })); } + [Fact] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, ".NET Framework has not been patched yet.")] + public void BypassingSerializationBinders() + { + Tuple tuple = new Tuple(42, new byte[] { 1, 2, 3, 4 }); + BinaryFormatter formatter = new BinaryFormatter(); + + using (MemoryStream stream = new MemoryStream()) + { + formatter.Serialize(stream, tuple); + + stream.Position = 0; + + Tuple deserialized = (Tuple)formatter.Deserialize(stream); + Assert.Equal(tuple.Item1, deserialized.Item1); + Assert.Equal(tuple.Item2, deserialized.Item2); + } + } + private static void ValidateEqualityComparer(object obj) { Type objType = obj.GetType(); diff --git a/src/native/corehost/hostmisc/pal.windows.cpp b/src/native/corehost/hostmisc/pal.windows.cpp index cd6701879389d..94c47541d2404 100644 --- a/src/native/corehost/hostmisc/pal.windows.cpp +++ b/src/native/corehost/hostmisc/pal.windows.cpp @@ -11,6 +11,38 @@ #include #include +namespace +{ + typedef DWORD(WINAPI *get_temp_path_func_ptr)(DWORD buffer_len, LPWSTR buffer); + static volatile get_temp_path_func_ptr s_get_temp_path_func = nullptr; + + DWORD get_temp_path(DWORD buffer_len, LPWSTR buffer) + { + if (s_get_temp_path_func == nullptr) + { + HMODULE kernel32 = ::LoadLibraryExW(L"kernel32.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + + get_temp_path_func_ptr get_temp_path_func_local = NULL; + if (kernel32 != NULL) + { + // store to thread local variable to prevent data race + get_temp_path_func_local = (get_temp_path_func_ptr)::GetProcAddress(kernel32, "GetTempPath2W"); + } + + if (get_temp_path_func_local == NULL) // method is only available with Windows 10 Creators Update or later + { + s_get_temp_path_func = &GetTempPathW; + } + else + { + s_get_temp_path_func = get_temp_path_func_local; + } + } + + return s_get_temp_path_func(buffer_len, buffer); + } +} + bool GetModuleFileNameWrapper(HMODULE hModule, pal::string_t* recv) { pal::string_t path; @@ -569,7 +601,7 @@ bool get_extraction_base_parent_directory(pal::string_t& directory) const size_t max_len = MAX_PATH + 1; pal::char_t temp_path[max_len]; - size_t len = GetTempPathW(max_len, temp_path); + size_t len = get_temp_path(max_len, temp_path); if (len == 0) { return false;