Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Proper fix for compilation issue caused by deprecated API in Mojave #30815

Merged
merged 4 commits into from
Jul 10, 2018

Conversation

maryamariyan
Copy link
Member

Proper fix for compilation issue caused by deprecated API in macOS Mojave by

using dlsym to call available API rather than suppressing deprecation warnings.

Fixes: #30599

cc: @janvorli, @bartonjs, @danmosemsft

…jave by

using dlsym to call available API rather than suppressing deprecation warnings.

Fixes: #30599
@maryamariyan maryamariyan added the os-mac-os-x OS-X aka Mac OS label Jul 3, 2018
@maryamariyan maryamariyan self-assigned this Jul 3, 2018
@maryamariyan maryamariyan requested review from bartonjs and janvorli July 3, 2018 16:50
if (!checked)
{
secCertificateCopyKey = (SecKeyRef (*)(SecCertificateRef))dlsym(RTLD_DEFAULT, "SecCertificateCopyKey");
secCertificateCopyPublicKey = (OSStatus (*)(SecCertificateRef, SecKeyRef*))dlsym(RTLD_DEFAULT, "SecCertificateCopyPublicKey");
Copy link
Member Author

@maryamariyan maryamariyan Jul 3, 2018

Choose a reason for hiding this comment

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

@janvorli is there a way we could call dlsym only once in this code snippet to improve the diff?

Copy link
Member Author

Choose a reason for hiding this comment

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

From @bartonjs on Jul 3, 2018, 8:35 AM PDT

In C# I'd do this as Volatile.Write(ref checked, 1), or declare checked as volatile. Is declaring checked volatile here the right way to prevent a reordering race condition, @janvorli?

maryamariyan#25 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

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

From @bartonjs on Jul 3, 2018, 8:37 AM PDT

(Or is my static local-fu weak, and we don't need to do a lazy initializer manually, because static locals guarantee that they only run their initializer once? (and therefore the two dlsym calls should move up to variable initializers))

maryamariyan#25 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

@jkotas will also know for sure

Copy link
Member

Choose a reason for hiding this comment

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

Right, this code is wrong. It can be broken even on x64, depending on the compiler optimizations.

The other places in the corefx shims use volatile, double-checked locking and pthread_mutex_lock (look for pthread_mutex_lock under src/native for examples). You can use the same pattern.

Copy link
Member

Choose a reason for hiding this comment

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

Actually, I would just use pthread_once here. It does exactly what you need here.

Copy link
Member Author

Choose a reason for hiding this comment

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

@jkotas, in my case here, I would need to call either of SecCertificateCopyKey or SecCertificateCopyPublicKey once, and both have arguments and neither returns void.

While looking at the doc here, it seems that init_routine() which is the second argument passed to pthread_once, apparently should have no arguments and returns void.

Please let me know if I misunderstood, but I think because of this observation I don't think I can use pthread_once for my case here.

I just pushed up a commit using the alternative pthread_mutex_lock pattern you recommended as seen in other places in corefx.

@jkotas
Copy link
Member

jkotas commented Jul 5, 2018

I think because of this observation I don't think I can use pthread_once for my case here.

I think it would work just fine. I do not think you need any arguments for the init routine. The init routine would be just:

static void InitCertificateCopy()
{
    secCertificateCopyKey = (SecKeyRef (*)(SecCertificateRef))dlsym(RTLD_DEFAULT, "SecCertificateCopyKey");
    secCertificateCopyPublicKey = (OSStatus (*)(SecCertificateRef, SecKeyRef*))dlsym(RTLD_DEFAULT, "SecCertificateCopyPublicKey");
}

And you would not need the checked variable at all.

@jkotas
Copy link
Member

jkotas commented Jul 5, 2018

The implementation with mutex is functionally correct, but it is slower than it needs to be. Implementation using pthread_once is going to be faster.

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

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

LGTM otherwise

}
else
{
assert(secCertificateCopyPublicKey != NULL);
Copy link
Member

Choose a reason for hiding this comment

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

This can be null when things go wrong. It may be better to return error instead of asserting.

@maryamariyan
Copy link
Member Author

@dotnet-bot test Windows x64 Debug Build please

@maryamariyan
Copy link
Member Author

@dotnet-bot test Packaging All Configurations x64 Debug Build please

@maryamariyan
Copy link
Member Author

@dotnet-bot test OSX x64 Debug Build please

@danmoseley
Copy link
Member

Any other feedback @jkotas?

}
else
{
return kErrorBadInput;
Copy link
Member

Choose a reason for hiding this comment

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

This is not really capturing the error (there is nothing wrong with the input), but I see we are ignoring the error code on the managed side in release builds anyway, so it does not really matter which error code we are going to return here. It may be worth a comment.

Copy link
Member

Choose a reason for hiding this comment

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

It's not completely ignored, but anything that isn't 1 or 0 is all a generic CryptographicException.

Copy link
Member Author

@maryamariyan maryamariyan Jul 9, 2018

Choose a reason for hiding this comment

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

Here are the other options:

// Unless another interpretation is "obvious", pal_seckey functions return 1 on success.
// functions which represent a boolean return 0 on "successful false"
// otherwise functions will return one of the following return values:
static const int32_t kErrorBadInput = -1;
static const int32_t kErrorSeeError = -2;
static const int32_t kErrorUnknownAlgorithm = -3;
static const int32_t kErrorUnknownState = -4;

Copy link
Member

Choose a reason for hiding this comment

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

"Unknown state" certainly sounds like what we'd be in if neither method exists.

@karelz karelz added this to the 3.0 milestone Jul 8, 2018
@maryamariyan
Copy link
Member Author

@dotnet-bot test Packaging All Configurations x64 Debug Build please

@maryamariyan
Copy link
Member Author

@dotnet-bot test OSX x64 Debug Build please

@filipnavara
Copy link
Member

This broke a plenty of unit tests on my macOS Mojave DP2 and DP3 machines. I reverted commit 92b4826 and the unit test failures disappeared. I guess we need to revisit this.

@maryamariyan
Copy link
Member Author

what version macOS Mojave are you using? which tests are failing for you?

@filipnavara
Copy link
Member

About 8+ tests in System.Security.Cryptography.X509Certificates. It's actually hard to tell because there're some failure that cause assert to be hit before the tests finish:

  Discovering: System.Security.Cryptography.X509Certificates.Tests
  Discovered:  System.Security.Cryptography.X509Certificates.Tests
  Starting:    System.Security.Cryptography.X509Certificates.Tests
  Warning: unable to build chain to self-signed root for signer "(null)"   System.Security.Cryptography.X509Certificates.Tests.PublicKeyTests.TestECDsaPublicKey_BrainpoolP160r1_ValidatesSignature(curveData: [45, 45, 45, 45, 45, ...], existingSignature: [145, 69, 199, 157, 212, ...]) [FAIL]
        System.ArgumentException : Cannot open an invalid handle.
        Parameter name: publicKey
        Stack Trace:
  Assertion Failed
  Unknown keysize (0)
  
     at System.Security.Cryptography.EccSecurityTransforms.GetKeySize(SecKeyPair newKeys) in /Users/filipnavara/Documents/corefx/src/Common/src/System/Security/Cryptography/EccSecurityTransforms.cs:line 175
     at System.Security.Cryptography.EccSecurityTransforms.SetKeyAndGetSize(SecKeyPair keyPair) in /Users/filipnavara/Documents/corefx/src/Common/src/System/Security/Cryptography/EccSecurityTransforms.cs:line 81
     at System.Security.Cryptography.ECDsaImplementation.ECDsaSecurityTransforms..ctor(SafeSecKeyRefHandle publicKey) in /Users/filipnavara/Documents/corefx/src/Common/src/System/Security/Cryptography/ECDsaSecurityTransforms.cs:line 61
     at Internal.Cryptography.Pal.X509Pal.AppleX509Pal.DecodePublicKey(Oid oid, Byte[] encodedKeyValue, Byte[] encodedParameters, ICertificatePal certificatePal) in /Users/filipnavara/Documents/corefx/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.OSX/X509Pal.cs:line 43
     at Internal.Cryptography.Pal.CertificateExtensionsCommon.GetPublicKey[T](X509Certificate2 certificate, Predicate`1 matchesConstraints) in /Users/filipnavara/Documents/corefx/src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/CertificateExtensionsCommon.cs:line 34
     at System.Security.Cryptography.X509Certificates.ECDsaCertificateExtensions.GetECDsaPublicKey(X509Certificate2 certificate) in /Users/filipnavara/Documents/corefx/src/System.Security.Cryptography.X509Certificates/src/System/Security/Cryptography/X509Certificates/ECDsaCertificateExtensions.cs:line 22
     at System.Security.Cryptography.X509Certificates.Tests.PublicKeyTests.TestECDsa224PublicKey() in /Users/filipnavara/Documents/corefx/src/System.Security.Cryptography.X509Certificates/tests/PublicKeyTests.cs:line 456
     at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
     at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) in /Users/buildagent/agent/_work/482/s/src/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs:line 485

Basically all the tests that fail either report this assert or that there's invalid handle related to public key.

@filipnavara
Copy link
Member

macOS Mojave DP2 or DP3, basically any up-to-date developer preview hits it. Reproduced on two separate machines.

@bartonjs
Copy link
Member

Sounds like Apple is adding support for ECC other than secp256r1, secp384r1, and secp521r1 now. If forward fixing is sensible that's the option I'd prefer (over going back to "ignore deprecation warnings"), but that might turn out to be a somewhat significant change.

@filipnavara
Copy link
Member

@bartonjs That's probably not the case. Much likely there are some key copy operations that fail and the error is not checked for. Then hell breaks loose later... I am in process of pinning down the issue, but wanted to give a heads up.

@bartonjs
Copy link
Member

The callstack says this is in a test for ECDSA-224 (secp224r1).

https://github.com/dotnet/corefx/blob/master/src/Common/src/System/Security/Cryptography/ECDsaSecurityTransforms.cs#L59-L62 called SecKeyPair.PublicOnly(publicKey). That would throw if publicKey was null / representing the null pointer (https://github.com/dotnet/corefx/blob/master/src/Common/src/System/Security/Cryptography/SecKeyPair.cs#L38-L44).

Then, we made it into

uint64_t AppleCryptoNative_EccGetKeySizeInBits(SecKeyRef publicKey)
{
if (publicKey == NULL)
{
return 0;
}
size_t blockSize = SecKeyGetBlockSize(publicKey);
// This seems to be the expected size of an ECDSA signature for this key.
// But since Apple uses the DER SEQUENCE(r, s) format the signature size isn't
// fixed. It might be trying to encode the biggest the DER value could be:
//
// 256: r is 32 bytes, but maybe one padding byte, so 33.
// s is 32 bytes, but maybe one padding byte, so 33.
// each of those values gets one tag and one length byte
// 35 * 2 is 70 payload bytes for the sequence, so one length byte
// and one tag byte, makes 72.
//
// 384: r,s are 48 bytes, plus padding, length, and tag: 51
// 2 * 51 = 102, requires one length byte and one tag byte, 104.
//
// 521: neither r nor s can have the high bit set, no padding. 66 content bytes
// plus tag and length is 68.
// 2 * 68 is 136, since it's greater than 127 it takes 2 length bytes
// so 136 + 2 + 1 = 139. Looks like they accounted for padding bytes anyways.
//
// This completely needs to be revisited if Apple adds support for "generic" ECC.
//
// Word of caution: While seeking meaning in these numbers I ran across a snippet of code
// which suggests that on iOS (vs macOS) they use a different set of reasoning and produce
// different numbers (they used (8 + 2*thisValue) on iOS for "signature length").
switch (blockSize)
{
case 72:
return 256;
case 104:
return 384;
case 141:
return 521;
}
return 0;
}
.

We know that the pointer isn't NULL, so we skip the first return 0. Then we make it past the switch statement, none of those hit, and we hit the trailing return 0. Why? Apple has never supported secp224r1 before.

If that shim method is made to allow secp224r1 to flow through then now the caller will still assert (that it got back an answer that wasn't 256, 384, or 521), and then maybe many more places will have similar "we don't know what to do here, because there's no obvious path forward from Apple's current design" guards.

If the test passes with this change rolled back, it means that asking for the public key failed, which the test considers acceptable. And that means that Apple has added support for generic ECC (or, at least, "more curves"), but only when using the new function.

@filipnavara
Copy link
Member

Ok, that sounds like valid reasoning, although it doesn't necessarily explain all the failures I am seeing. Btw, the new values I see in blockSize are 32 (for the stack trace above) and 66.

@danmoseley
Copy link
Member

@filipnavara I suggest opening a new issue if a follow up change is needed.

@filipnavara
Copy link
Member

@danmosemsft I will. Just gathering enough details to describe what the issue is... :-)

@bartonjs
Copy link
Member

32?!. Using their previous formula it would be (((ceil(224 / 8) + 3) * 2) + 2) => 64. Gotta love consistency.

@filipnavara
Copy link
Member

filipnavara commented Jul 16, 2018

@bartonjs Eh, or maybe 28... on this machine. And 48 for 384 (TestECDsaPublicKey). And 66 for 521. The values don't really make any sense whatsoever. The values seem to be actual key size in bytes now.

picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
…otnet/corefx#30815)

* Proper fix for compilation issue caused by deprecated API in macOS Mojave by
using dlsym to call available API rather than suppressing deprecation warnings.

Fixes: dotnet/corefx#30599


Commit migrated from dotnet/corefx@92b4826
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Build on macOS Mojave DP fails
6 participants