Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean the OpenSSL error queue more consistently #65148

Merged
merged 5 commits into from
Feb 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
Expand All @@ -13,8 +14,8 @@ internal static partial class Crypto
[GeneratedDllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_ErrClearError")]
internal static partial ulong ErrClearError();

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

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

private static unsafe string ErrErrorStringN(ulong error)
{
var buffer = new byte[1024];
byte[] buffer = ArrayPool<byte>.Shared.Rent(1024);
string ret;

fixed (byte* buf = &buffer[0])
{
ErrErrorStringN(error, buf, buffer.Length);
return Marshal.PtrToStringAnsi((IntPtr)buf)!;
ret = Marshal.PtrToStringAnsi((IntPtr)buf)!;
}

ArrayPool<byte>.Shared.Return(buffer);
return ret;
}

internal static Exception CreateOpenSslCryptographicException()
{
// The Windows cryptography library reports error codes through
// Marshal.GetLastWin32Error, which has a single value when the
// function exits, last writer wins.
// The Windows cryptography libraries reports error codes through
// return values, or Marshal.GetLastWin32Error, either of which
// has a single value when the function exits.
//
// OpenSSL maintains an error queue. Calls to ERR_get_error read
// values out of the queue in the order that ERR_set_error wrote
// them. Nothing enforces that a single call into an OpenSSL
// function will guarantee at-most one error being set.
// function will guarantee at-most one error being set, and there
// are well-known cases where multiple errors are emitted.
//
// In order to maintain parity in how error flows look between the
// Windows code and the OpenSSL-calling code, drain the queue
// whenever an Exception is desired, and report the exception
// related to the last value in the queue.
bool isAllocFailure;
ulong error = ErrGetErrorAlloc(out isAllocFailure);
ulong lastRead = error;
bool lastIsAllocFailure = isAllocFailure;

// 0 (there's no named constant) is only returned when the calls
// to ERR_get_error exceed the calls to ERR_set_error.
while (lastRead != 0)
{
error = lastRead;
isAllocFailure = lastIsAllocFailure;

lastRead = ErrGetErrorAlloc(out lastIsAllocFailure);
}
// In older versions of .NET, we collected the last error in the
// queue, by repeatedly calling into ERR_get_error from managed code
// and using the last error as the basis of the exception.
// Now, we call into the shim once, which is responsible for
// maintaining the error state and informing us of the one value to report.
// (and when fetching that error we go ahead and clear out the rest).
bartonjs marked this conversation as resolved.
Show resolved Hide resolved
ulong error = ErrGetExceptionError(out bool isAllocFailure);

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

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

// If there was an error code, and it wasn't something handled specially,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ static const Entry s_cryptoNative[] =
DllImportEntry(CryptoNative_EncodeX509SubjectPublicKeyInfo)
DllImportEntry(CryptoNative_ErrClearError)
DllImportEntry(CryptoNative_ErrErrorStringN)
DllImportEntry(CryptoNative_ErrGetErrorAlloc)
DllImportEntry(CryptoNative_ErrGetExceptionError)
DllImportEntry(CryptoNative_ErrPeekError)
DllImportEntry(CryptoNative_ErrPeekLastError)
DllImportEntry(CryptoNative_ErrReasonErrorString)
Expand Down
51 changes: 51 additions & 0 deletions src/native/libs/System.Security.Cryptography.Native/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ int32_t CryptoNative_GetX509Thumbprint(X509* x509, uint8_t* pBuf, int32_t cBuf)
return -SHA_DIGEST_LENGTH;
}

ERR_clear_error();

if (!X509_digest(x509, EVP_sha1(), pBuf, NULL))
{
return 0;
Expand All @@ -102,6 +104,8 @@ otherwise.
*/
const ASN1_TIME* CryptoNative_GetX509NotBefore(X509* x509)
{
// No error queue impact.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I almost preemptively removed all of these comments, since they aren't as relevant with "keep using the last error in the queue". Clearly, I didn't.

  • Why remove: They were added to justify a lack of ERR_clear_error, when ERR_clear_error was necessary producing correct exceptions.
  • Why keep: They represent the output of white-box investigation of the code behind the code.
  • Why remove: They could be invalidated.
  • Why keep: Uh... it made opening the PR easier.

I have to do something to deal with merge conflicts no matter what... so thought I'd open the floor for a keep or cut discussion.


if (x509)
{
return X509_get0_notBefore(x509);
Expand All @@ -123,6 +127,8 @@ otherwise.
*/
const ASN1_TIME* CryptoNative_GetX509NotAfter(X509* x509)
{
// No error queue impact.

if (x509)
{
return X509_get0_notAfter(x509);
Expand All @@ -144,6 +150,8 @@ otherwise.
*/
const ASN1_TIME* CryptoNative_GetX509CrlNextUpdate(X509_CRL* crl)
{
// No error queue impact.

if (crl)
{
return X509_CRL_get0_nextUpdate(crl);
Expand All @@ -168,6 +176,9 @@ The encoded value of the version, otherwise:
*/
int32_t CryptoNative_GetX509Version(X509* x509)
{
// No errors are expected to be written to the queue on this call,
// and the managed caller doesn't check for one.

if (x509)
{
return (int32_t)X509_get_version(x509);
Expand All @@ -189,6 +200,8 @@ describing the object type.
*/
ASN1_OBJECT* CryptoNative_GetX509PublicKeyAlgorithm(X509* x509)
{
// No error queue impact, all of the called routines are just field accessors.

if (x509)
{
X509_PUBKEY* pubkey = X509_get_X509_PUBKEY(x509);
Expand Down Expand Up @@ -216,6 +229,8 @@ describing the object type.
*/
ASN1_OBJECT* CryptoNative_GetX509SignatureAlgorithm(X509* x509)
{
// No error queue impact.

if (x509)
{
const X509_ALGOR* sigAlg = X509_get0_tbs_sigalg(x509);
Expand Down Expand Up @@ -243,6 +258,8 @@ Any negative value: The input buffer size was reported as insufficient. A buffer
*/
int32_t CryptoNative_GetX509PublicKeyParameterBytes(X509* x509, uint8_t* pBuf, int32_t cBuf)
{
ERR_clear_error();

if (!x509)
{
return 0;
Expand Down Expand Up @@ -302,6 +319,8 @@ the public key.
*/
ASN1_BIT_STRING* CryptoNative_GetX509PublicKeyBytes(X509* x509)
{
// No error queue impact.

if (x509)
{
return X509_get0_pubkey_bitstr(x509);
Expand Down Expand Up @@ -345,6 +364,8 @@ Any negative value: The input buffer size was reported as insufficient. A buffer
*/
int32_t CryptoNative_GetAsn1StringBytes(ASN1_STRING* asn1, uint8_t* pBuf, int32_t cBuf)
{
// No error queue impact.

if (!asn1 || cBuf < 0)
{
return 0;
Expand Down Expand Up @@ -380,6 +401,8 @@ Any negative value: The input buffer size was reported as insufficient. A buffer
*/
int32_t CryptoNative_GetX509NameRawBytes(X509_NAME* x509Name, uint8_t* pBuf, int32_t cBuf)
{
ERR_clear_error();

const uint8_t* nameBuf;
size_t nameBufLen;

Expand Down Expand Up @@ -433,6 +456,7 @@ Note that 0 does not always indicate an error, merely that GetX509EkuField shoul
*/
int32_t CryptoNative_GetX509EkuFieldCount(EXTENDED_KEY_USAGE* eku)
{
// No error queue impact.
return sk_ASN1_OBJECT_num(eku);
}

Expand All @@ -449,6 +473,7 @@ that particular OID.
*/
ASN1_OBJECT* CryptoNative_GetX509EkuField(EXTENDED_KEY_USAGE* eku, int32_t loc)
{
// No error queue impact.
return sk_ASN1_OBJECT_value(eku, loc);
}

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

ERR_clear_error();

if (!x509 || nameType < NAME_TYPE_SIMPLE || nameType > NAME_TYPE_URL)
{
return NULL;
Expand Down Expand Up @@ -729,6 +756,8 @@ int32_t CryptoNative_CheckX509Hostname(X509* x509, const char* hostname, int32_t
if (cchHostname < 0)
return -5;

ERR_clear_error();

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

ERR_clear_error();

int subjectNid = NID_commonName;
int sanGenType = GEN_IPADD;
GENERAL_NAMES* san = (GENERAL_NAMES*)(X509_get_ext_d2i(x509, NID_subject_alt_name, NULL, NULL));
Expand Down Expand Up @@ -848,6 +879,7 @@ Note that 0 does not always indicate an error, merely that GetX509StackField sho
*/
int32_t CryptoNative_GetX509StackFieldCount(STACK_OF(X509) * stack)
{
// No error queue impact.
return sk_X509_num(stack);
}

Expand All @@ -864,6 +896,7 @@ that particular element.
*/
X509* CryptoNative_GetX509StackField(STACK_OF(X509) * stack, int loc)
{
// No error queue impact.
return sk_X509_value(stack, loc);
}

Expand All @@ -876,6 +909,7 @@ when done with it.
*/
void CryptoNative_RecursiveFreeX509Stack(STACK_OF(X509) * stack)
{
// No error queue impact.
sk_X509_pop_free(stack, X509_free);
}

Expand All @@ -899,6 +933,8 @@ int32_t CryptoNative_X509StoreSetVerifyTime(X509_STORE* ctx,
int32_t second,
int32_t isDst)
{
ERR_clear_error();

if (!ctx)
{
return 0;
Expand Down Expand Up @@ -935,6 +971,7 @@ otherwise NULL.
*/
X509* CryptoNative_ReadX509AsDerFromBio(BIO* bio)
{
ERR_clear_error();
return d2i_X509_bio(bio, NULL);
}

Expand All @@ -955,6 +992,8 @@ OpenSSL's BIO_tell
*/
int32_t CryptoNative_BioTell(BIO* bio)
{
// No error queue impact.

if (!bio)
{
return -1;
Expand Down Expand Up @@ -982,6 +1021,8 @@ OpenSSL's BIO_seek
*/
int32_t CryptoNative_BioSeek(BIO* bio, int32_t ofs)
{
// No error queue impact.

if (!bio)
{
return -1;
Expand All @@ -1002,6 +1043,7 @@ A STACK_OF(X509*) with no comparator.
*/
STACK_OF(X509) * CryptoNative_NewX509Stack()
{
ERR_clear_error();
return sk_X509_new_null();
}

Expand All @@ -1018,6 +1060,8 @@ Return values:
*/
int32_t CryptoNative_PushX509StackField(STACK_OF(X509) * stack, X509* x509)
{
ERR_clear_error();

if (!stack)
{
return 0;
Expand All @@ -1039,6 +1083,7 @@ Returns a bool to managed code.
*/
int32_t CryptoNative_GetRandomBytes(uint8_t* buf, int32_t num)
{
ERR_clear_error();
int ret = RAND_bytes(buf, num);

return ret == 1;
Expand All @@ -1063,6 +1108,8 @@ int32_t CryptoNative_LookupFriendlyNameByOid(const char* oidValue, const char**
int nid;
const char* ln;

ERR_clear_error();

if (!oidValue || !friendlyName)
{
return -2;
Expand Down Expand Up @@ -1121,6 +1168,7 @@ Version number as MNNFFRBB (major minor fix final beta/patch)
*/
int64_t CryptoNative_OpenSslVersionNumber()
{
// No error queue impact.
return (int64_t)OpenSSL_version_num();
}

Expand All @@ -1130,6 +1178,9 @@ void CryptoNative_RegisterLegacyAlgorithms()
if (API_EXISTS(OSSL_PROVIDER_try_load))
{
OSSL_PROVIDER_try_load(NULL, "legacy", 1);

// Doesn't matter if it succeeded or failed.
ERR_clear_error();
}
#endif
}
Expand Down
Loading