[Android] Clear pending JNI exceptions in pal_rsa.c to avoid CheckJNI aborts#128747
[Android] Clear pending JNI exceptions in pal_rsa.c to avoid CheckJNI aborts#128747simonrozsival wants to merge 1 commit into
Conversation
|
Tagging subscribers to this area: @bartonjs, @vcsjones, @dotnet/area-system-security |
There was a problem hiding this comment.
Pull request overview
This PR hardens the Android RSA native interop layer (pal_rsa.c) by adding consistent “check-and-clear pending JNI exception” points between JNI calls, so Java-side failures (e.g., provider rejecting a key) don’t cascade into CheckJNI process aborts and instead flow back to managed code via existing FAIL/RSA_FAIL return conventions.
Changes:
- Added
ON_EXCEPTION_PRINT_AND_GOTO(...)checks after JNI calls in the RSA encrypt/decrypt/sign/verify, import/decode, keygen, and parameter get/set paths. - Refactored several functions to a single
cleanup:/error:label and centralized local/global ref release viaReleaseLRef/ReleaseGRef. - Ensured RSA parameter “out” handles are initialized and reliably cleared on error to keep managed cleanup safe.
This comment has been minimized.
This comment has been minimized.
99ffc21 to
544c17b
Compare
|
Note This comment was generated with GitHub Copilot. Addressed the two
Verified on emulator-5554 (API 36, arm64-v8a): 12373/10738/0 failed/977 skipped — unchanged from the previous run. |
🤖 Copilot Code Review — PR #128747Holistic AssessmentMotivation: Justified. The root cause is clear from the stack trace: chained JNI calls without intermediate exception checks violate the JNI spec and cause unrecoverable Approach: Correct. Adding Summary: ✅ LGTM. The change is defensive, consistent with sibling files, and correct on both the success and failure paths. One minor observation noted below but nothing blocking. Detailed Findings✅ Exception checking coverageAll nine hardened functions now check for pending JNI exceptions after every JNI call that can throw. The ✅ Partial-failure safety in
|
|
This implies we need to fix RSA. LegalKeySizes on Android. I think we can do this separately from this PR but we may want to make a tracking issue for it. |
|
/azp run runtime-android |
|
Azure Pipelines successfully started running 1 pipeline(s). |
Note
This PR description was drafted with help from GitHub Copilot.
Summary
Closes #128660.
Fixes a CheckJNI
SIGABRTin the Android System.Security.Cryptography native library when importing or working with RSA keys that the Conscrypt + BoringSSL provider on the target device refuses to construct — most notably 16384-bit RSA on Pixel 6a / Android 16.Root cause
AndroidCryptoNative_SetRsaParametersissued a chain of JNI calls without checking for pending Java exceptions in between. The crash from issue #128660 follows this exact pattern:KeyFactory.generatePrivate(rsaPrivateKeySpec)is called for a 16384-bit private key.Conscrypt + BoringSSL rejects the key and throws
InvalidKeySpecException(BoringSSL caps RSA at 8192 bits on this device). The exception is left pending on the JNI thread.The next JNI call —
NewObject(g_RSAPublicCrtKeySpecClass, …)— runs with that pending exception, which is a JNI spec violation.ART CheckJNI detects the violation and aborts the process:
The crash bypasses any managed-side
catch (CryptographicException), so callers cannot recover, and any test relying on a graceful failure for unsupported key sizes (e.g.RSAKeyFileTests.Supports16384) blocks clean CI.Because we only have a stack trace and no repro device, the fix is defensive: every JNI-using function in
pal_rsa.cnow clears any pending exception between JNI calls and propagates the failure to the managed caller asFAIL, which the existing C# code already converts to a recoverableCryptographicException.Changes
Single file:
src/native/libs/System.Security.Cryptography.Native.Android/pal_rsa.c.Nine functions hardened to:
ON_EXCEPTION_PRINT_AND_GOTO(cleanup)(which callsExceptionDescribe+ExceptionClear);cleanup:(orerror:) label;ReleaseLRef/ReleaseGRefhelpers instead of hand-rolledif (x) DeleteLocalRef(…)chains;Hardened functions:
AndroidCryptoNative_SetRsaParametersKeyFactory.generatePrivate(...)/generatePublic(...)reject keys the provider can't handle (e.g. 16384-bit RSA).AndroidCryptoNative_GetRsaParametersRSAPrivateCrtKey/RSAPublicKeyfield accessors are JNI calls; theerror:path now also releases each output GRef and resets the slot toNULLso the managed-sideSafeBignumHandle.Dispose()becomes a safe no-op (no double-free, no leak —IsInvalidreturns true forIntPtr.Zero).AndroidCryptoNative_RsaPublicEncryptCipher.getInstance(...)/Cipher.init(...)/Cipher.doFinal(...)can throwNoSuchAlgorithmException,InvalidKeyException,IllegalBlockSizeException,BadPaddingException.AndroidCryptoNative_RsaPrivateDecryptAndroidCryptoNative_RsaSignPrimitiveAndroidCryptoNative_RsaVerificationPrimitiveAndroidCryptoNative_DecodeRsaSubjectPublicKeyInfoKeyFactory.getInstance(...)/new X509EncodedKeySpec(...)/KeyFactory.generatePublic(...)can throwNoSuchAlgorithmException,InvalidKeySpecException.AndroidCryptoNative_RsaGenerateKeyExKeyPairGenerator.getInstance(...)/initialize(bits)/genKeyPair()can throwNoSuchAlgorithmException,InvalidParameterException.AndroidCryptoNative_NewRsaFromKeysRSAKey.getModulus()is a JNI call that should not be assumed exception-free.No managed-side or API changes; behavior is identical on the success path. On failure, the functions now return the existing
FAIL/RSA_FAILsentinel without aborting the process, and the managed callers (RSAAndroid,Interop.AndroidCrypto.ExportRsaParameters) already convert those intoCryptographicException.Test plan
System.Security.Cryptography.Tests(the suite that contains the RSA importer tests in #128660) was run twice on Android arm64 with the change applied:Both runs match the pre-change baseline (no regressions). The 16384-bit
KeyFactory.generatePrivatefailure path could not be exercised directly because the Conscrypt build on these particular devices accepts 16384-bit RSA — neither hardware reproduces the abort from #128660. The change is justified by the stack trace alone: adding the exception check at every JNI call site inpal_rsa.cis the standard hardening pattern used elsewhere in this directory (seepal_dsa.c,pal_x509.c).Once merged,
RSAKeyFileTests.Supports16384will fall back toImported = falsecleanly on devices where Conscrypt rejects the key, which unblocks CI on the affected hardware without changing behavior on devices that accept the key.Risk
Low. Single file; no managed-side or API changes; success paths are unchanged; failure paths only switch from a process abort to a recoverable
CryptographicException.Notes for reviewers
pal_dsa.c,pal_ecc_import_export.c, and other sibling files in the same directory have a few similar patterns (chained JNI calls without intermediate exception checks). I deliberately scoped this PR topal_rsa.cto keep the fix small and easy to backport; a follow-up sweep would be a sensible next step but is out of scope here.pal_rsa.c(e.g. the redundantoaepParameterSpec != NULL && oaepParameterSpec != FAILcheck inRsaPublicEncrypt, sinceFAIL == NULLforjobject) were left untouched to keep the diff focused on the bug.