Skip to content

Identity TFLite Model Creation Crash Fix#12083

Merged
bcapps merged 7 commits intomasterfrom
fix-identity-tflite-crash
Dec 4, 2025
Merged

Identity TFLite Model Creation Crash Fix#12083
bcapps merged 7 commits intomasterfrom
fix-identity-tflite-crash

Conversation

@bcapps
Copy link
Collaborator

@bcapps bcapps commented Dec 2, 2025

Summary

This fixes a crash that could happen when attempting to open a tflite model file that doesn't exist or has been removed during Identity verification. This was seen in production on a large client app. We're not quite sure how it happens, but it is theoretically possible that a model could be cleared from the cache in between validation and loading. Builds on #9812

Motivation

Here's a backtrace:

Fatal Exception: java.lang.IllegalArgumentException: Contents of /data/user/0/com.example.app/cache/a8bcf0129dcd29084f6797ede7e0be86f9e11ed5.tflite does not encode a valid TensorFlow Lite model: Could not open '/data/user/0/com.example.app/cache/a8bcf0129dcd29084f6797ede7e0be86f9e11ed5.tflite'.
The model allocation is null/empty
       at org.tensorflow.lite.NativeInterpreterWrapper.createModel(NativeInterpreterWrapper.java)
       at org.tensorflow.lite.NativeInterpreterWrapper.<init>(NativeInterpreterWrapper.java:57)
       at org.tensorflow.lite.NativeInterpreterWrapperExperimental.<init>(NativeInterpreterWrapperExperimental.java:32)
       at org.tensorflow.lite.Interpreter.<init>(Interpreter.java:200)
       at com.stripe.android.mlcore.impl.InterpreterWrapperImpl.<init>(InterpreterWrapperImpl.java:11)
       at com.stripe.android.identity.ml.FaceDetectorAnalyzer.<init>(FaceDetectorAnalyzer.kt:29)
       at com.stripe.android.identity.ml.FaceDetectorAnalyzer$Factory.newInstance(FaceDetectorAnalyzer.kt:98)
       at com.stripe.android.camera.framework.AnalyzerPool$Companion.of(AnalyzerPool.java:54)
       at com.stripe.android.camera.framework.AnalyzerPool$Companion.of$default(AnalyzerPool.java:44)
       at com.stripe.android.identity.camera.IdentityScanFlow$startFlow$1.invokeSuspend(IdentityScanFlow.java:102)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
       at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:375)
       at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
       at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.java:358)
       at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:134)
       at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(BuildersKt__Builders_common.kt:53)
       at kotlinx.coroutines.BuildersKt.launch(Builders.kt:1)
       at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(BuildersKt__Builders_common.kt:44)
       at kotlinx.coroutines.BuildersKt.launch$default(Builders.kt:1)
       at com.stripe.android.identity.camera.IdentityScanFlow.startFlow(IdentityScanFlow.java:88)
       at com.stripe.android.identity.viewmodel.IdentityScanViewModel.startScan(IdentityScanViewModel.java:133)
       at com.stripe.android.identity.utils.CameraUtilsKt.startScanning(CameraUtils.kt:16)
       at com.stripe.android.identity.ui.SelfieScreenKt$SelfieCaptureScreen$1$1.invokeSuspend(SelfieScreen.kt:176)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
       at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
       at androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch(AndroidUiDispatcher.android.kt:79)

The crashing TFLite Interpreter is created upon FaceDetectorAnalyzer creation, which is made from IdentityScanFlow.startFlow when ScanType is SELFIE. Since we don't own or control what exceptions TensorFlow Lite (now LiteRT) throws, I changed the existing try-catch statements surrounding the Interpreter and FaceDetectorAnalyzer initialization to catch all exceptions and call the appropriate error handler. I considered catching IllegalArgumentException in addition to the existing IllegalStateException, but thought the catch-all made sense here given that the exceptions are coming from an outside dependency, so that we fail gracefully for library users instead of crashing, even for issues we can't anticipate. We now log those exceptions as well.

Testing

  • Added tests
  • Modified tests
  • Manually verified

Manually completed verification via the identity example app, selecting selfie to make sure I went through that flow and received a success, verifying everything still worked as expected.

@bcapps bcapps requested review from a team as code owners December 2, 2025 19:30
@github-actions
Copy link
Contributor

github-actions bot commented Dec 2, 2025

Diffuse output:

OLD: identity-example-release-base.apk (signature: V1, V2)
NEW: identity-example-release-pr.apk (signature: V1, V2)

          │           compressed           │          uncompressed          
          ├───────────┬───────────┬────────┼───────────┬───────────┬────────
 APK      │ old       │ new       │ diff   │ old       │ new       │ diff   
──────────┼───────────┼───────────┼────────┼───────────┼───────────┼────────
      dex │   2.1 MiB │   2.1 MiB │ +200 B │   4.3 MiB │   4.3 MiB │ +108 B 
     arsc │   1.1 MiB │   1.1 MiB │    0 B │   1.1 MiB │   1.1 MiB │    0 B 
 manifest │   2.3 KiB │   2.3 KiB │    0 B │     8 KiB │     8 KiB │    0 B 
      res │ 303.5 KiB │ 303.5 KiB │    0 B │ 457.7 KiB │ 457.7 KiB │    0 B 
   native │   7.9 MiB │   7.9 MiB │    0 B │  19.3 MiB │  19.3 MiB │    0 B 
    asset │   7.5 KiB │   7.5 KiB │   -1 B │   7.3 KiB │   7.2 KiB │   -1 B 
    other │  95.2 KiB │  95.2 KiB │   -6 B │ 183.2 KiB │ 183.2 KiB │    0 B 
──────────┼───────────┼───────────┼────────┼───────────┼───────────┼────────
    total │  11.4 MiB │  11.4 MiB │ +193 B │  25.3 MiB │  25.3 MiB │ +107 B 

 DEX     │ old   │ new   │ diff       
─────────┼───────┼───────┼────────────
   files │     1 │     1 │  0         
 strings │ 20457 │ 20458 │ +1 (+3 -2) 
   types │  6290 │  6290 │  0 (+0 -0) 
 classes │  5051 │  5051 │  0 (+0 -0) 
 methods │ 30674 │ 30674 │  0 (+0 -0) 
  fields │ 17580 │ 17580 │  0 (+0 -0) 

 ARSC    │ old  │ new  │ diff 
─────────┼──────┼──────┼──────
 configs │  163 │  163 │  0   
 entries │ 3667 │ 3667 │  0
APK
    compressed     │   uncompressed    │                               
──────────┬────────┼──────────┬────────┤                               
 size     │ diff   │ size     │ diff   │ path                          
──────────┼────────┼──────────┼────────┼───────────────────────────────
  2.1 MiB │ +200 B │  4.3 MiB │ +108 B │ ∆ classes.dex                 
 29.1 KiB │  -10 B │ 64.5 KiB │    0 B │ ∆ META-INF/CERT.SF            
 25.9 KiB │   +5 B │ 64.5 KiB │    0 B │ ∆ META-INF/MANIFEST.MF        
  6.7 KiB │   -1 B │  6.6 KiB │   -1 B │ ∆ assets/dexopt/baseline.prof 
  1.2 KiB │   -1 B │  1.2 KiB │    0 B │ ∆ META-INF/CERT.RSA           
──────────┼────────┼──────────┼────────┼───────────────────────────────
  2.1 MiB │ +193 B │  4.4 MiB │ +107 B │ (total)
DEX
STRINGS:

   old   │ new   │ diff       
  ───────┼───────┼────────────
   20457 │ 20458 │ +1 (+3 -2) 
  
  + Analyzer creation failed, likely due to model file issue: 
  + r8-map-id-838eb86dc6fed9add5911097e0562ec81d1e6f4d44872432ce0f3375a668a69e
  + ~~R8{"backend":"dex","compilation-mode":"release","has-checksums":false,"min-api":21,"pg-map-id":"838eb86dc6fed9add5911097e0562ec81d1e6f4d44872432ce0f3375a668a69e","r8-mode":"full","version":"8.13.17"}
  
  - r8-map-id-44108d15d0b3aa3d601e5efa58d90738fcce8d28c63081cf0d3cee61f35cd907
  - ~~R8{"backend":"dex","compilation-mode":"release","has-checksums":false,"min-api":21,"pg-map-id":"44108d15d0b3aa3d601e5efa58d90738fcce8d28c63081cf0d3cee61f35cd907","r8-mode":"full","version":"8.13.17"}

)
} catch (e: IllegalStateException) {
} catch (e: Exception) {
Log.e(TAG, "Analyzer creation failed", e)

Choose a reason for hiding this comment

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

What happens after the crash now? Do we show an error message?

Copy link
Collaborator Author

@bcapps bcapps Dec 2, 2025

Choose a reason for hiding this comment

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

Currently the behavior is the same was it was previously – if it fails at the validation level, it simply tries to redownload the model, but if it succeeds there and fails at the creation stage (the rare case like we have for this crash) then identity verification fails and dismisses the sheet, and the exception is passed to the client app in the failure status. The client app can then choose to show the error to the user if they want, or give them another opportunity to retry.

The only difference from before is that we catch more types of exceptions now so that they no longer crash.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use genericError instead to make sure we can see the error in our logs?

Copy link
Collaborator Author

@bcapps bcapps Dec 3, 2025

Choose a reason for hiding this comment

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

sure thing, done – I also included the exception message in the genericError message, but let me know if that message should remain static / if I should remove that as part of the message

Copy link

@cjmisenas-stripe cjmisenas-stripe left a comment

Choose a reason for hiding this comment

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

Do we also need to handle this for document capture?

@bcapps
Copy link
Collaborator Author

bcapps commented Dec 2, 2025

Do we also need to handle this for document capture?

The existing try-catches in validation and analyzer creation handle both selfie and document scan cases, so we should be covered!

Copy link
Contributor

@luisv-stripe luisv-stripe left a comment

Choose a reason for hiding this comment

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

LGTM

@bcapps bcapps merged commit b0801e9 into master Dec 4, 2025
19 checks passed
@bcapps bcapps deleted the fix-identity-tflite-crash branch December 4, 2025 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants