Skip to content

Commit 45a9548

Browse files
bartonjsvcsjones
andauthored
Clean the OpenSSL error queue more consistently
This change started with the intent of changing the error code / message from an OpenSSL-based exception from the most recent error in the queue to the oldest error that was produced after the operation started. This was motivated mostly by EVP_PKEY2PKCS8(pkey) incorrectly indicating a malloc failure after producing the original/correct error that pkey did not have a private key portion. Having fully developed the experiment, data showed that while for EVP_PKEY2PKCS8 the first (of two) errors was the better one, for everything else with more than one error reported, the last error was at least as good as, and often better, than the first error. With that data in hand, this change now represents more consistently cleaning the error queue, and reducing the overhead in producing the exception objects. Co-authored-by: Kevin Jones <[email protected]>
1 parent 8e4bef2 commit 45a9548

File tree

27 files changed

+456
-48
lines changed

27 files changed

+456
-48
lines changed

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.ERR.cs

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.Buffers;
56
using System.Diagnostics;
67
using System.Runtime.InteropServices;
78
using System.Security.Cryptography;
@@ -13,8 +14,8 @@ internal static partial class Crypto
1314
[GeneratedDllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_ErrClearError")]
1415
internal static partial ulong ErrClearError();
1516

16-
[GeneratedDllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_ErrGetErrorAlloc")]
17-
private static partial ulong ErrGetErrorAlloc([MarshalAs(UnmanagedType.Bool)] out bool isAllocFailure);
17+
[GeneratedDllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_ErrGetExceptionError")]
18+
private static partial ulong ErrGetExceptionError([MarshalAs(UnmanagedType.Bool)] out bool isAllocFailure);
1819

1920
[GeneratedDllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_ErrPeekError")]
2021
internal static partial ulong ErrPeekError();
@@ -30,43 +31,38 @@ internal static partial class Crypto
3031

3132
private static unsafe string ErrErrorStringN(ulong error)
3233
{
33-
var buffer = new byte[1024];
34+
byte[] buffer = ArrayPool<byte>.Shared.Rent(1024);
35+
string ret;
36+
3437
fixed (byte* buf = &buffer[0])
3538
{
3639
ErrErrorStringN(error, buf, buffer.Length);
37-
return Marshal.PtrToStringAnsi((IntPtr)buf)!;
40+
ret = Marshal.PtrToStringAnsi((IntPtr)buf)!;
3841
}
42+
43+
ArrayPool<byte>.Shared.Return(buffer);
44+
return ret;
3945
}
4046

4147
internal static Exception CreateOpenSslCryptographicException()
4248
{
43-
// The Windows cryptography library reports error codes through
44-
// Marshal.GetLastWin32Error, which has a single value when the
45-
// function exits, last writer wins.
49+
// The Windows cryptography libraries reports error codes through
50+
// return values, or Marshal.GetLastWin32Error, either of which
51+
// has a single value when the function exits.
4652
//
4753
// OpenSSL maintains an error queue. Calls to ERR_get_error read
4854
// values out of the queue in the order that ERR_set_error wrote
4955
// them. Nothing enforces that a single call into an OpenSSL
50-
// function will guarantee at-most one error being set.
56+
// function will guarantee at-most one error being set, and there
57+
// are well-known cases where multiple errors are emitted.
5158
//
52-
// In order to maintain parity in how error flows look between the
53-
// Windows code and the OpenSSL-calling code, drain the queue
54-
// whenever an Exception is desired, and report the exception
55-
// related to the last value in the queue.
56-
bool isAllocFailure;
57-
ulong error = ErrGetErrorAlloc(out isAllocFailure);
58-
ulong lastRead = error;
59-
bool lastIsAllocFailure = isAllocFailure;
60-
61-
// 0 (there's no named constant) is only returned when the calls
62-
// to ERR_get_error exceed the calls to ERR_set_error.
63-
while (lastRead != 0)
64-
{
65-
error = lastRead;
66-
isAllocFailure = lastIsAllocFailure;
67-
68-
lastRead = ErrGetErrorAlloc(out lastIsAllocFailure);
69-
}
59+
// In older versions of .NET, we collected the last error in the
60+
// queue, by repeatedly calling into ERR_get_error from managed code
61+
// and using the last error as the basis of the exception.
62+
// Now, we call into the shim once, which is responsible for
63+
// maintaining the error state and informing us of the one value to report.
64+
// (and when fetching that error we go ahead and clear out the rest).
65+
ulong error = ErrGetExceptionError(out bool isAllocFailure);
7066

7167
// If we're in an error flow which results in an Exception, but
7268
// no calls to ERR_set_error were made, throw the unadorned
@@ -82,7 +78,8 @@ internal static Exception CreateOpenSslCryptographicException()
8278
}
8379

8480
// Even though ErrGetError returns ulong (C++ unsigned long), we
85-
// really only expect error codes in the UInt32 range
81+
// really only expect error codes in the UInt32 range, since that
82+
// type is only 32 bits on x86 Linux.
8683
Debug.Assert(error <= uint.MaxValue, "ErrGetError should only return error codes in the UInt32 range.");
8784

8885
// If there was an error code, and it wasn't something handled specially,

src/native/libs/System.Security.Cryptography.Native/entrypoints.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ static const Entry s_cryptoNative[] =
8989
DllImportEntry(CryptoNative_EncodeX509SubjectPublicKeyInfo)
9090
DllImportEntry(CryptoNative_ErrClearError)
9191
DllImportEntry(CryptoNative_ErrErrorStringN)
92-
DllImportEntry(CryptoNative_ErrGetErrorAlloc)
92+
DllImportEntry(CryptoNative_ErrGetExceptionError)
9393
DllImportEntry(CryptoNative_ErrPeekError)
9494
DllImportEntry(CryptoNative_ErrPeekLastError)
9595
DllImportEntry(CryptoNative_ErrReasonErrorString)

src/native/libs/System.Security.Cryptography.Native/openssl.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ int32_t CryptoNative_GetX509Thumbprint(X509* x509, uint8_t* pBuf, int32_t cBuf)
8181
return -SHA_DIGEST_LENGTH;
8282
}
8383

84+
ERR_clear_error();
85+
8486
if (!X509_digest(x509, EVP_sha1(), pBuf, NULL))
8587
{
8688
return 0;
@@ -102,6 +104,8 @@ otherwise.
102104
*/
103105
const ASN1_TIME* CryptoNative_GetX509NotBefore(X509* x509)
104106
{
107+
// No error queue impact.
108+
105109
if (x509)
106110
{
107111
return X509_get0_notBefore(x509);
@@ -123,6 +127,8 @@ otherwise.
123127
*/
124128
const ASN1_TIME* CryptoNative_GetX509NotAfter(X509* x509)
125129
{
130+
// No error queue impact.
131+
126132
if (x509)
127133
{
128134
return X509_get0_notAfter(x509);
@@ -144,6 +150,8 @@ otherwise.
144150
*/
145151
const ASN1_TIME* CryptoNative_GetX509CrlNextUpdate(X509_CRL* crl)
146152
{
153+
// No error queue impact.
154+
147155
if (crl)
148156
{
149157
return X509_CRL_get0_nextUpdate(crl);
@@ -168,6 +176,9 @@ The encoded value of the version, otherwise:
168176
*/
169177
int32_t CryptoNative_GetX509Version(X509* x509)
170178
{
179+
// No errors are expected to be written to the queue on this call,
180+
// and the managed caller doesn't check for one.
181+
171182
if (x509)
172183
{
173184
return (int32_t)X509_get_version(x509);
@@ -189,6 +200,8 @@ describing the object type.
189200
*/
190201
ASN1_OBJECT* CryptoNative_GetX509PublicKeyAlgorithm(X509* x509)
191202
{
203+
// No error queue impact, all of the called routines are just field accessors.
204+
192205
if (x509)
193206
{
194207
X509_PUBKEY* pubkey = X509_get_X509_PUBKEY(x509);
@@ -216,6 +229,8 @@ describing the object type.
216229
*/
217230
ASN1_OBJECT* CryptoNative_GetX509SignatureAlgorithm(X509* x509)
218231
{
232+
// No error queue impact.
233+
219234
if (x509)
220235
{
221236
const X509_ALGOR* sigAlg = X509_get0_tbs_sigalg(x509);
@@ -243,6 +258,8 @@ Any negative value: The input buffer size was reported as insufficient. A buffer
243258
*/
244259
int32_t CryptoNative_GetX509PublicKeyParameterBytes(X509* x509, uint8_t* pBuf, int32_t cBuf)
245260
{
261+
ERR_clear_error();
262+
246263
if (!x509)
247264
{
248265
return 0;
@@ -302,6 +319,8 @@ the public key.
302319
*/
303320
ASN1_BIT_STRING* CryptoNative_GetX509PublicKeyBytes(X509* x509)
304321
{
322+
// No error queue impact.
323+
305324
if (x509)
306325
{
307326
return X509_get0_pubkey_bitstr(x509);
@@ -345,6 +364,8 @@ Any negative value: The input buffer size was reported as insufficient. A buffer
345364
*/
346365
int32_t CryptoNative_GetAsn1StringBytes(ASN1_STRING* asn1, uint8_t* pBuf, int32_t cBuf)
347366
{
367+
// No error queue impact.
368+
348369
if (!asn1 || cBuf < 0)
349370
{
350371
return 0;
@@ -380,6 +401,8 @@ Any negative value: The input buffer size was reported as insufficient. A buffer
380401
*/
381402
int32_t CryptoNative_GetX509NameRawBytes(X509_NAME* x509Name, uint8_t* pBuf, int32_t cBuf)
382403
{
404+
ERR_clear_error();
405+
383406
const uint8_t* nameBuf;
384407
size_t nameBufLen;
385408

@@ -433,6 +456,7 @@ Note that 0 does not always indicate an error, merely that GetX509EkuField shoul
433456
*/
434457
int32_t CryptoNative_GetX509EkuFieldCount(EXTENDED_KEY_USAGE* eku)
435458
{
459+
// No error queue impact.
436460
return sk_ASN1_OBJECT_num(eku);
437461
}
438462

@@ -449,6 +473,7 @@ that particular OID.
449473
*/
450474
ASN1_OBJECT* CryptoNative_GetX509EkuField(EXTENDED_KEY_USAGE* eku, int32_t loc)
451475
{
476+
// No error queue impact.
452477
return sk_ASN1_OBJECT_value(eku, loc);
453478
}
454479

@@ -467,6 +492,8 @@ BIO* CryptoNative_GetX509NameInfo(X509* x509, int32_t nameType, int32_t forIssue
467492
{
468493
static const char szOidUpn[] = "1.3.6.1.4.1.311.20.2.3";
469494

495+
ERR_clear_error();
496+
470497
if (!x509 || nameType < NAME_TYPE_SIMPLE || nameType > NAME_TYPE_URL)
471498
{
472499
return NULL;
@@ -729,6 +756,8 @@ int32_t CryptoNative_CheckX509Hostname(X509* x509, const char* hostname, int32_t
729756
if (cchHostname < 0)
730757
return -5;
731758

759+
ERR_clear_error();
760+
732761
// OpenSSL will treat a target hostname starting with '.' as special.
733762
// We don't expect target hostnames to start with '.', but if one gets in here, the fallback
734763
// and the mainline won't be the same... so just make it report false.
@@ -771,6 +800,8 @@ int32_t CryptoNative_CheckX509IpAddress(
771800
if (!addressBytes)
772801
return -6;
773802

803+
ERR_clear_error();
804+
774805
int subjectNid = NID_commonName;
775806
int sanGenType = GEN_IPADD;
776807
GENERAL_NAMES* san = (GENERAL_NAMES*)(X509_get_ext_d2i(x509, NID_subject_alt_name, NULL, NULL));
@@ -848,6 +879,7 @@ Note that 0 does not always indicate an error, merely that GetX509StackField sho
848879
*/
849880
int32_t CryptoNative_GetX509StackFieldCount(STACK_OF(X509) * stack)
850881
{
882+
// No error queue impact.
851883
return sk_X509_num(stack);
852884
}
853885

@@ -864,6 +896,7 @@ that particular element.
864896
*/
865897
X509* CryptoNative_GetX509StackField(STACK_OF(X509) * stack, int loc)
866898
{
899+
// No error queue impact.
867900
return sk_X509_value(stack, loc);
868901
}
869902

@@ -876,6 +909,7 @@ when done with it.
876909
*/
877910
void CryptoNative_RecursiveFreeX509Stack(STACK_OF(X509) * stack)
878911
{
912+
// No error queue impact.
879913
sk_X509_pop_free(stack, X509_free);
880914
}
881915

@@ -899,6 +933,8 @@ int32_t CryptoNative_X509StoreSetVerifyTime(X509_STORE* ctx,
899933
int32_t second,
900934
int32_t isDst)
901935
{
936+
ERR_clear_error();
937+
902938
if (!ctx)
903939
{
904940
return 0;
@@ -935,6 +971,7 @@ otherwise NULL.
935971
*/
936972
X509* CryptoNative_ReadX509AsDerFromBio(BIO* bio)
937973
{
974+
ERR_clear_error();
938975
return d2i_X509_bio(bio, NULL);
939976
}
940977

@@ -955,6 +992,8 @@ OpenSSL's BIO_tell
955992
*/
956993
int32_t CryptoNative_BioTell(BIO* bio)
957994
{
995+
// No error queue impact.
996+
958997
if (!bio)
959998
{
960999
return -1;
@@ -982,6 +1021,8 @@ OpenSSL's BIO_seek
9821021
*/
9831022
int32_t CryptoNative_BioSeek(BIO* bio, int32_t ofs)
9841023
{
1024+
// No error queue impact.
1025+
9851026
if (!bio)
9861027
{
9871028
return -1;
@@ -1002,6 +1043,7 @@ A STACK_OF(X509*) with no comparator.
10021043
*/
10031044
STACK_OF(X509) * CryptoNative_NewX509Stack()
10041045
{
1046+
ERR_clear_error();
10051047
return sk_X509_new_null();
10061048
}
10071049

@@ -1018,6 +1060,8 @@ Return values:
10181060
*/
10191061
int32_t CryptoNative_PushX509StackField(STACK_OF(X509) * stack, X509* x509)
10201062
{
1063+
ERR_clear_error();
1064+
10211065
if (!stack)
10221066
{
10231067
return 0;
@@ -1039,6 +1083,7 @@ Returns a bool to managed code.
10391083
*/
10401084
int32_t CryptoNative_GetRandomBytes(uint8_t* buf, int32_t num)
10411085
{
1086+
ERR_clear_error();
10421087
int ret = RAND_bytes(buf, num);
10431088

10441089
return ret == 1;
@@ -1063,6 +1108,8 @@ int32_t CryptoNative_LookupFriendlyNameByOid(const char* oidValue, const char**
10631108
int nid;
10641109
const char* ln;
10651110

1111+
ERR_clear_error();
1112+
10661113
if (!oidValue || !friendlyName)
10671114
{
10681115
return -2;
@@ -1121,6 +1168,7 @@ Version number as MNNFFRBB (major minor fix final beta/patch)
11211168
*/
11221169
int64_t CryptoNative_OpenSslVersionNumber()
11231170
{
1171+
// No error queue impact.
11241172
return (int64_t)OpenSSL_version_num();
11251173
}
11261174

@@ -1130,6 +1178,9 @@ void CryptoNative_RegisterLegacyAlgorithms()
11301178
if (API_EXISTS(OSSL_PROVIDER_try_load))
11311179
{
11321180
OSSL_PROVIDER_try_load(NULL, "legacy", 1);
1181+
1182+
// Doesn't matter if it succeeded or failed.
1183+
ERR_clear_error();
11331184
}
11341185
#endif
11351186
}

0 commit comments

Comments
 (0)