Skip to content

[Android] Add missing JNI exception checks across crypto PAL#130555

Draft
simonrozsival wants to merge 1 commit into
mainfrom
dev/simonrozsival/android-pal-jni-exception-checks-pr2
Draft

[Android] Add missing JNI exception checks across crypto PAL#130555
simonrozsival wants to merge 1 commit into
mainfrom
dev/simonrozsival/android-pal-jni-exception-checks-pr2

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Jul 11, 2026

Copy link
Copy Markdown
Member

Note

This PR description was drafted by an AI assistant (GitHub Copilot CLI) based on the actual code changes in this branch. All code was authored interactively with Simon driving the design decisions.

Follow-up to #128777, which added the initial batch of JNI exception checks to the Android crypto PAL. This PR covers the remaining functions.

Summary

Adds missing JNI exception checks across the remaining functions in the Android cryptography PAL (src/native/libs/System.Security.Cryptography.Native.Android/). The work is based on a static audit of Call*Method / NewObject / Get*ArrayRegion-style JNI calls where a pending Java exception could otherwise leak back into managed code (undefined behavior for the next managed→native transition) or produce partial output.

Beyond adding the checks, the touched functions are tightened to follow three patterns already established elsewhere in the PAL:

  1. ON_EXCEPTION_PRINT_AND_GOTO(label) immediately after each fallible JNI call. The macro both prints and clears the exception, so subsequent JNI calls on the cleanup path are safe.
  2. INIT_LOCALS(loc, …) + a single cleanup:/RELEASE_LOCALS(loc, env) tail for local-ref hygiene, replacing ad-hoc jobject foo = NULL; declarations and scattered DeleteLocalRef calls.
  3. No partial output state on failure — results are computed into locals and only promoted to caller-visible out-parameters / global refs on the success path.

Native changes

All under src/native/libs/System.Security.Cryptography.Native.Android/:

File Functions
pal_x509chain.c X509ChainBuild, X509ChainGetCertificateCount, X509ChainGetCertificates, X509ChainGetErrorCount, PopulateValidationError, X509ChainGetErrors, X509ChainSetCustomTrustStore, CreateCertPathFromAnchor, ValidateWithRevocation, and the error-capture branches
pal_bignum.c BigNumToBinary, GetBigNumBytesIncludingPaddingByteForSign
pal_cipher.c CipherSetKeyAndIV, CipherUpdateAAD, CipherUpdate (also releases the update output array on all paths)
pal_ecdsa.c EcDsaSign (clear exception on the getPrivate() failure path)
pal_eckey.c NewEcKeyFromKeys, EcKeyCreateByOid (converted to INIT_LOCALS + per-call checks), EcKeyGetCurveName
pal_hmac.c DoFinal
pal_misc.c GetRandomBytes
pal_pbkdf2.c Pbkdf2 (clear the pending exception when NewDirectByteBuffer fails)
pal_sslstream.c SSLStreamSetTargetHost, SSLStreamRead, SSLStreamRequestClientAuthentication, SSLStreamSetApplicationProtocols
pal_x509.c X509Decode, X509DecodeCollection, X509GetContentType (check SetByteArrayRegion)

Managed changes

  • Interop.X509Chain.csX509ChainGetErrorCount now returns a negative value when a JNI exception occurs (distinct from a legitimate count of 0); the wrapper throws CryptographicException in that case instead of silently reporting "no errors" (which would leave ChainStatus empty for a failed validation).
  • Interop.Bignum.csAndroidCryptoNative_BigNumToBinary now returns -1 and writes nothing when a JNI exception occurs; ExtractBignum throws CryptographicException rather than returning a zero-filled (corrupt) buffer.

Testing

  • Native build: android-arm64 Release — 0 warnings / 0 errors.
  • Managed build: libs -os android -a arm64 -rf CoreCLR (includes System.Security.Cryptography) — 0 warnings / 0 errors.

CI will provide cross-architecture validation and device/emulator test runs.

Adds missing JNI exception checks across the remaining functions in the
Android cryptography PAL, based on a static audit of `Call*Method` /
`NewObject` / `Get*ArrayRegion`-style JNI calls where a pending Java
exception could otherwise leak back into managed code (undefined behavior
for the next managed->native transition) or produce partial output.

The touched functions follow the patterns already established elsewhere in
the PAL:

1. `ON_EXCEPTION_PRINT_AND_GOTO(label)` immediately after each fallible JNI
   call. The macro both prints and clears the exception, so subsequent JNI
   calls on the cleanup path are safe.
2. `INIT_LOCALS(loc, ...)` + a single `cleanup:`/`RELEASE_LOCALS(loc, env)`
   tail for local-ref hygiene, replacing ad-hoc `jobject foo = NULL;`
   declarations and scattered `DeleteLocalRef` calls.
3. No partial output state on failure -- results are computed into locals
   and only promoted to caller-visible out-parameters / global refs on the
   success path.

Files changed (all under
`src/native/libs/System.Security.Cryptography.Native.Android/` unless noted):

- `pal_x509chain.c` -- harden the functions not covered by the earlier
  `X509ChainCreateContext` fix (build, certificate/error enumeration,
  custom trust store, revocation validation) and clear exceptions on the
  error-capture paths.
- `pal_bignum.c` -- check `toByteArray()` before using the result.
- `pal_cipher.c` -- `CipherSetKeyAndIV`, `CipherUpdateAAD`, `CipherUpdate`
  (also releases the update output array on all paths).
- `pal_ecdsa.c` -- clear the exception on the `getPrivate()` failure path in
  `EcDsaSign`.
- `pal_eckey.c` -- `NewEcKeyFromKeys`, `EcKeyCreateByOid`
  (converted to `INIT_LOCALS` + per-call checks), `EcKeyGetCurveName`.
- `pal_hmac.c` -- check `Mac.doFinal()` before reading the result array.
- `pal_misc.c` -- `GetRandomBytes`.
- `pal_pbkdf2.c` -- clear the pending exception when `NewDirectByteBuffer`
  fails.
- `pal_sslstream.c` -- `SetTargetHost`, `SSLStreamRead`,
  `RequestClientAuthentication`, `SetApplicationProtocols`.
- `pal_x509.c` -- check `SetByteArrayRegion` in the decode paths.

Managed changes:

- `Interop.X509Chain.cs` -- `X509ChainGetErrorCount` now returns a negative
  value when a JNI exception occurs (distinct from a legitimate count of 0);
  the wrapper throws `CryptographicException` in that case instead of
  silently reporting "no errors".
- `Interop.Bignum.cs` -- `AndroidCryptoNative_BigNumToBinary` now returns -1
  when a JNI exception occurs and writes nothing; `ExtractBignum` throws
  `CryptographicException` rather than returning a zero-filled buffer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: bc96caa6-f528-4662-8cfa-df04e8a47475

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the Android cryptography native PAL by adding missing JNI exception checks and tightening failure-path behavior so pending Java exceptions don’t leak into subsequent JNI transitions; it also updates managed interop to surface certain JNI failures as CryptographicException.

Changes:

  • Added ON_EXCEPTION_PRINT_AND_GOTO(...) / CheckJNIExceptions(...) checks after additional fallible JNI calls across the Android crypto PAL.
  • Adjusted native return conventions for some functions to better distinguish JNI-exception failures from legitimate “0 results”.
  • Updated managed Android interop wrappers to throw when native indicates a JNI exception occurred (e.g., negative error counts / negative bignum conversions).
Show a summary per file
File Description
src/native/libs/System.Security.Cryptography.Native.Android/pal_x509chain.c Adds JNI exception checks and tightens error materialization/cleanup when building/validating chains and collecting errors.
src/native/libs/System.Security.Cryptography.Native.Android/pal_x509.c Adds missing exception checks after byte-array population JNI calls.
src/native/libs/System.Security.Cryptography.Native.Android/pal_sslstream.c Adds exception checks around additional SSLEngine/ByteBuffer JNI operations to avoid leaking pending exceptions.
src/native/libs/System.Security.Cryptography.Native.Android/pal_pbkdf2.c Adds exception check after NewDirectByteBuffer and other JNI byte-buffer operations.
src/native/libs/System.Security.Cryptography.Native.Android/pal_misc.c Converts to a ret/cleanup pattern and adds some exception checks in GetRandomBytes.
src/native/libs/System.Security.Cryptography.Native.Android/pal_hmac.c Adds exception checks in the HMAC finalization path to prevent pending exceptions from escaping.
src/native/libs/System.Security.Cryptography.Native.Android/pal_eckey.c Improves JNI exception handling for EC key creation and curve name extraction.
src/native/libs/System.Security.Cryptography.Native.Android/pal_ecdsa.c Ensures exception state is cleared/observed on getPrivate() failure path.
src/native/libs/System.Security.Cryptography.Native.Android/pal_cipher.c Adds exception checks in cipher update/AAD paths and ensures local refs are released on all paths.
src/native/libs/System.Security.Cryptography.Native.Android/pal_bignum.c Adds exception checks and returns a negative result on JNI failure for bignum-to-binary conversion.
src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.X509Chain.cs Throws when native indicates a JNI exception during chain error-count retrieval.
src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Bignum.cs Propagates native “JNI exception occurred” via CryptographicException instead of returning corrupt/zero-filled data.

Copilot's findings

  • Files reviewed: 12/12 changed files
  • Comments generated: 4

Comment on lines 23 to +26
jbyteArray buffArray = make_java_byte_array(env, len);
(*env)->SetByteArrayRegion(env, buffArray, 0, len, (jbyte*)buff);
(*env)->CallVoidMethod(env, randObj, g_randNextBytesMethod, buffArray);
ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
Comment on lines 271 to +280
JNIEnv* env = GetJNIEnv();
int32_t ret = FAIL;
jbyteArray outDataBytes = NULL;
jbyteArray inDataBytes = make_java_byte_array(env, inl);
(*env)->SetByteArrayRegion(env, inDataBytes, 0, inl, (jbyte*)in);
ON_EXCEPTION_PRINT_AND_GOTO(cleanup);

*outl = 0;
jbyteArray outDataBytes = (jbyteArray)(*env)->CallObjectMethod(env, ctx->cipher, g_cipherUpdateMethod, inDataBytes);
outDataBytes = (jbyteArray)(*env)->CallObjectMethod(env, ctx->cipher, g_cipherUpdateMethod, inDataBytes);
ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
Comment on lines 493 to +495
(*env)->CallBooleanMethod(env, anchors, g_HashSetAdd, anchor);
(*env)->DeleteLocalRef(env, anchor);
ON_EXCEPTION_PRINT_AND_GOTO(cleanup);
Comment on lines +45 to +47
// A negative result means a pending JNI exception was cleared and no data was written.
// Fail loudly rather than silently returning a zero-filled (corrupt) buffer.
if (written < 0)
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to 'arch-android': @vitek-karas, @simonrozsival, @steveisok, @akoeplinger
See info in area-owners.md if you want to be subscribed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants