[Android] Add missing JNI exception checks across crypto PAL - #130555
Merged
simonrozsival merged 6 commits intoJul 17, 2026
Conversation
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
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 11, 2026 18:53 — with
GitHub Actions
Inactive
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 11, 2026 18:53 — with
GitHub Actions
Inactive
simonrozsival
had a problem deploying
to
copilot-pat-pool
July 11, 2026 18:53 — with
GitHub Actions
Failure
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 11, 2026 18:54 — with
GitHub Actions
Inactive
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 11, 2026 18:55 — with
GitHub Actions
Inactive
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 11, 2026 18:55 — with
GitHub Actions
Inactive
Contributor
There was a problem hiding this comment.
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
Contributor
|
Tagging subscribers to 'arch-android': @vitek-karas, @simonrozsival, @steveisok, @akoeplinger |
…uched crypto PAL functions Refinement pass over the functions touched earlier in this PR: - Convert manual `jobject foo = ...;` + `DeleteLocalRef` local-ref handling to `INIT_LOCALS(loc, ...)` + a single `cleanup:`/`RELEASE_LOCALS(loc, env)` tail in the touched functions (pal_bignum.c, pal_cipher.c, pal_ecdsa.c, pal_eckey.c, pal_hmac.c, pal_misc.c, pal_pbkdf2.c, pal_x509chain.c). - In `AndroidCryptoNative_X509ChainGetCertificates`, the trust-anchor local ref now lives in the `INIT_LOCALS` array and the caller-owned global ref is produced with `AddGRef` (instead of `ToGRef`); `RELEASE_LOCALS` frees the local ref on all paths, which also fixes a local-ref leak on the branch where the trust anchor equalled the last path certificate. - Ensure caller-visible out-parameters are zero-initialized at function entry and only assigned their meaningful value on the success path (`CipherUpdate`, `DoFinal`, `EcKeyGetCurveName`). No functional change on the success paths. Native build (android-arm64 Release) is clean with 0 warnings / 0 errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bc96caa6-f528-4662-8cfa-df04e8a47475
…-or-nothing `AndroidCryptoNative_X509ChainGetCertificates` filled the caller's `certs` array incrementally and, if a JNI call threw partway through, left the already-created global references in the array and returned FAIL, relying on the managed wrapper's `finally` to release those partial entries. Track the number of entries stored and, on any failure, release the global references we created and clear the slots, so the native function is self-contained: the caller only ever observes a fully populated array on success and never has to clean up partial native state on failure. Update the managed wrapper comment accordingly (its `finally` now releases the native-returned refs on success and is a no-op on failure). Native build (android-arm64 Release) is clean with 0 warnings / 0 errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bc96caa6-f528-4662-8cfa-df04e8a47475
bartonjs
reviewed
Jul 14, 2026
- pal_misc: check for a pending JNI exception immediately after SetByteArrayRegion in GetRandomBytes - pal_x509chain: move exception checks ahead of DeleteLocalRef, and store loop/temporary local refs (anchor, endOnly, cert) in the loc[] array so RELEASE_LOCALS frees them on every path; null out message after free in the X509ChainGetErrors failure rollback - pal_sslstream: use INIT_LOCALS/RELEASE_LOCALS in SSLStreamRead instead of a raw local + manual ReleaseLRef - pal_eckey: convert NewEcKeyFromKeys to the INIT_LOCALS/RELEASE_LOCALS cleanup pattern to avoid leaking the curveParameters local ref - Interop.Bignum: reword the negative-result comment more conservatively Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e1f935e1-2a62-4d1b-94ed-88a01eafaa4b
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 16, 2026 10:06 — with
GitHub Actions
Inactive
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 16, 2026 10:06 — with
GitHub Actions
Inactive
simonrozsival
had a problem deploying
to
copilot-pat-pool
July 16, 2026 10:07 — with
GitHub Actions
Failure
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 16, 2026 10:08 — with
GitHub Actions
Inactive
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 16, 2026 10:09 — with
GitHub Actions
Inactive
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 16, 2026 10:09 — with
GitHub Actions
Inactive
…ArrayLength Address review feedback: - pal_x509chain (ValidateWithRevocation): check for a pending JNI exception immediately after GetStaticObjectField for the ONLY_END_ENTITY option, before calling CallBooleanMethod - pal_hmac (DoFinal): check after GetArrayLength so we don't call GetByteArrayRegion with a pending exception Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e1f935e1-2a62-4d1b-94ed-88a01eafaa4b
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 16, 2026 10:18 — with
GitHub Actions
Inactive
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 16, 2026 10:19 — with
GitHub Actions
Inactive
simonrozsival
had a problem deploying
to
copilot-pat-pool
July 16, 2026 10:19 — with
GitHub Actions
Failure
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 16, 2026 10:20 — with
GitHub Actions
Inactive
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 16, 2026 10:21 — with
GitHub Actions
Inactive
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 16, 2026 10:21 — with
GitHub Actions
Inactive
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 16, 2026 19:56 — with
GitHub Actions
Inactive
simonrozsival
marked this pull request as ready for review
July 16, 2026 19:56
Member
Author
|
/azp run runtime-android |
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 16, 2026 19:56 — with
GitHub Actions
Inactive
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
Azure Pipelines: Successfully started running 3 pipeline(s). 12 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
simonrozsival
had a problem deploying
to
copilot-pat-pool
July 16, 2026 19:57 — with
GitHub Actions
Failure
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 16, 2026 19:59 — with
GitHub Actions
Inactive
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 16, 2026 19:59 — with
GitHub Actions
Inactive
simonrozsival
temporarily deployed
to
copilot-pat-pool
July 16, 2026 20:00 — with
GitHub Actions
Inactive
bartonjs
approved these changes
Jul 16, 2026
simonrozsival
deleted the
dev/simonrozsival/android-pal-jni-exception-checks-pr2
branch
July 17, 2026 08:27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 ofCall*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:
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 thecleanuppath are safe.INIT_LOCALS(loc, …)+ a singlecleanup:/RELEASE_LOCALS(loc, env)tail for local-ref hygiene, replacing ad-hocjobject foo = NULL;declarations and scatteredDeleteLocalRefcalls.Native changes
All under
src/native/libs/System.Security.Cryptography.Native.Android/:pal_x509chain.cX509ChainBuild,X509ChainGetCertificateCount,X509ChainGetCertificates,X509ChainGetErrorCount,PopulateValidationError,X509ChainGetErrors,X509ChainSetCustomTrustStore,CreateCertPathFromAnchor,ValidateWithRevocation, and the error-capture branchespal_bignum.cBigNumToBinary,GetBigNumBytesIncludingPaddingByteForSignpal_cipher.cCipherSetKeyAndIV,CipherUpdateAAD,CipherUpdate(also releases the update output array on all paths)pal_ecdsa.cEcDsaSign(clear exception on thegetPrivate()failure path)pal_eckey.cNewEcKeyFromKeys,EcKeyCreateByOid(converted toINIT_LOCALS+ per-call checks),EcKeyGetCurveNamepal_hmac.cDoFinalpal_misc.cGetRandomBytespal_pbkdf2.cPbkdf2(clear the pending exception whenNewDirectByteBufferfails)pal_sslstream.cSSLStreamSetTargetHost,SSLStreamRead,SSLStreamRequestClientAuthentication,SSLStreamSetApplicationProtocolspal_x509.cX509Decode,X509DecodeCollection,X509GetContentType(checkSetByteArrayRegion)Managed changes
Interop.X509Chain.cs—X509ChainGetErrorCountnow returns a negative value when a JNI exception occurs (distinct from a legitimate count of0); the wrapper throwsCryptographicExceptionin that case instead of silently reporting "no errors" (which would leaveChainStatusempty for a failed validation).Interop.Bignum.cs—AndroidCryptoNative_BigNumToBinarynow returns-1when a JNI exception occurs (the output must be treated as invalid — it may be zero-filled or only partially written);ExtractBignumthrowsCryptographicExceptionrather than returning a corrupt buffer.Testing
libs -os android -a arm64 -rf CoreCLR(includesSystem.Security.Cryptography) — 0 warnings / 0 errors.CI will provide cross-architecture validation and device/emulator test runs.