[Android] Handle X509ChainContext creation failures#128651
[Android] Handle X509ChainContext creation failures#128651simonrozsival wants to merge 4 commits into
Conversation
Ensure Android X509 chain context creation either returns a valid native context or cleans up partial state before failing. Check the returned SafeHandle in managed code so initialization failures surface as CryptographicException instead of reaching native Build with a null context. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Tagging subscribers to this area: @bartonjs, @vcsjones, @dotnet/area-system-security |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Pull request overview
This PR hardens the Android X.509 chain-building pipeline so that failures during native X509ChainContext initialization don’t later surface as a process-terminating native abort, and instead fail fast with a managed CryptographicException.
Changes:
- Add more consistent JNI exception handling during native
X509ChainContextcreation and pre-create the JavaerrorList. - Validate the returned Android chain context immediately after creation on the managed side and throw a
CryptographicExceptionwhen initialization fails. - Add a new localized resource string for the managed exception message.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/native/libs/System.Security.Cryptography.Native.Android/pal_x509chain.c | Adds additional JNI exception checks and changes how errorList is created/held during chain context initialization. |
| src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/ChainPal.Android.cs | Throws early if the returned chain context handle is invalid, preventing later native calls with a bad context. |
| src/libraries/System.Security.Cryptography/src/Resources/Strings.resx | Adds the new error message resource used by the managed exception. |
| ret = xcalloc(1, sizeof(X509ChainContext)); | ||
| ret->params = AddGRef(env, loc[params]); | ||
| ret->errorList = ToGRef(env, (*env)->NewObject(env, g_ArrayListClass, g_ArrayListCtor)); | ||
| ret->errorList = AddGRef(env, loc[errorList]); | ||
|
|
There was a problem hiding this comment.
Note
This reply was generated by GitHub Copilot.
Addressed in 1b689fb. AndroidCryptoNative_X509ChainCreateContext now checks both global-reference creations after AddGRef. If either params or errorList cannot be promoted to a global ref, it prints/clears any pending JNI exception, releases any partial native state, frees the context, and returns NULL so managed code fails gracefully instead of receiving a partially initialized context.
| <data name="Cryptography_AndroidX509ChainContextInitializationFailed" xml:space="preserve"> | ||
| <value>The Android X.509 certificate chain could not be initialized. See logcat for details.</value> | ||
| </data> | ||
| <data name="Cryptography_AlgKdfRequiresChars" xml:space="preserve"> |
There was a problem hiding this comment.
Note
This reply was generated by GitHub Copilot.
Addressed in 7de43db. I moved Cryptography_AndroidX509ChainContextInitializationFailed into the sorted Cryptography_* position, after Cryptography_AlgorithmTypesMustBeVisible and before Cryptography_ArgDSARequiresDSAKey.
|
|
||
| private static void ThrowIfInvalidChainContext(SafeX509ChainContextHandle chainContext) | ||
| { | ||
| if (chainContext.IsInvalid) |
There was a problem hiding this comment.
We typically try to put these kinds of checks as close to the interop boundary as possible now. For example:
So maybe the right thing to do is to make the actual LibraryImport call private and have the internal one check it. (Also make sure you dispose the handle)
There was a problem hiding this comment.
Note
This reply was generated by GitHub Copilot.
Addressed in 7de43db. The raw LibraryImport is now private (X509ChainCreateContext_private), and the internal X509ChainCreateContext wrapper performs the invalid-handle check at the interop boundary. If the handle is invalid, the wrapper disposes it before throwing, and the higher-level ChainPal.Android check was removed.
| <value>Accessing a hash algorithm by manipulating the HashName property is not supported on this platform. Instead, you must instantiate one of the supplied subtypes (such as HMACSHA1.)</value> | ||
| </data> | ||
| <data name="Cryptography_AndroidX509ChainContextInitializationFailed" xml:space="preserve"> | ||
| <value>The Android X.509 certificate chain could not be initialized. See logcat for details.</value> |
There was a problem hiding this comment.
Does android not have an exception throw helper that includes exception messages from the JNI / Java side? It feels weird to say "See logcat for details" in the exception message.
There was a problem hiding this comment.
Note
This reply was generated by GitHub Copilot.
Addressed in 7de43db. I did not find an existing managed Android crypto helper that propagates the Java/JNI exception message here; the native helper prints and clears JNI exceptions. To avoid the odd guidance, I simplified the resource string to: The Android X.509 certificate chain could not be initialized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Caution Security scanning requires review for Code Review DetailsThe threat detection results could not be parsed. The workflow output should be reviewed before merging. Review the workflow run logs for details. 🤖 Copilot Code Review — PR #128651Note This review was generated by GitHub Copilot. Holistic AssessmentMotivation: Justified reliability fix for a real production native abort on Android when Approach: Correct two-layer defense: harden the native JNI code with consistent exception checks and partial-context cleanup, then validate at the managed interop boundary with a clear Summary: ✅ LGTM. The change is small, focused, and addresses all prior review feedback. The native code now consistently checks JNI exceptions, properly cleans up partial state on Detailed Findings✅ Native JNI exception handling — Correct and completeAll JNI calls that previously lacked exception checks ( ✅
|
ebdb42c to
fc5594d
Compare
| <value>Algorithms added to CryptoConfig must be accessible from outside their assembly.</value> | ||
| </data> | ||
| <data name="Cryptography_AndroidX509ChainContextInitializationFailed" xml:space="preserve"> | ||
| <value>The Android X.509 certificate chain could not be initialized.</value> | ||
| </data> |
| private static partial SafeX509ChainContextHandle X509ChainCreateContext( | ||
| SafeX509Handle cert, | ||
| IntPtr[] extraStore, | ||
| int extraStoreLen); | ||
|
|
||
| internal static SafeX509ChainContextHandle X509ChainCreateContext(SafeX509Handle cert, IntPtr[] extraStore) | ||
| { | ||
| SafeX509ChainContextHandle chainContext = X509ChainCreateContext(cert, extraStore, extraStore.Length); | ||
|
|
||
| if (chainContext.IsInvalid) | ||
| { | ||
| chainContext.Dispose(); |
Note
This PR description was drafted with GitHub Copilot.
An Android production app reported a native abort while building an X.509 chain on arm64. The available tombstone snippet showed the process aborting in
AndroidCryptoNative_X509ChainBuildfrompal_x509chain.c, with the native guard reporting that parameterctxwas not a valid pointer. The report did not include a repro or full tombstone, but the observed failure mode means managed code reached the native build entry point with a nullX509ChainContext*.X509ChainContextis created byAndroidCryptoNative_X509ChainCreateContext. That initialization can fail if Android certificate store setup or PKIX parameter construction throws, or if required JNI global references cannot be created. Previously, the managed Android chain path stored the returnedSafeHandlewithout checking whether context creation failed, so a later build could pass a null native context toAndroidCryptoNative_X509ChainBuildand terminate the app process.This change makes context creation fail gracefully:
CryptographicExceptionif initialization failed.No regression test is included because the reliable failure modes depend on Android platform/provider state or artificial fault injection, and a test hook would be fragile and not representative.