Skip to content

Commit d5802ff

Browse files
Merge pull request juliansteenbakker#1209 from sanjay-aaritya/fix/android-catch-throwable-worker-thread
fix(android): catch Throwable on worker thread so keystore Errors don't crash the app
2 parents a6e00ce + d413d3f commit d5802ff

3 files changed

Lines changed: 29 additions & 6 deletions

File tree

flutter_secure_storage/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## Unreleased
2+
3+
### Android
4+
5+
- Fixed a fatal app crash when Android Keystore framework code throws `java.lang.Error` subclasses (e.g. `NoSuchFieldError` on some OEM builds with mismatched framework/KeyMint classes): the plugin's worker thread now catches `Throwable` and reports a `PlatformException` to Dart instead of killing the process.
6+
17
## 11.0.0-beta.1
28

39
**Breaking changes**

flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStorage.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,13 @@ public void onSuccess(BiometricPrompt.AuthenticationResult result) {
188188
try {
189189
storageCipher = storageCipherFactory.getCurrentStorageCipher(context, result.getCryptoObject().getCipher());
190190
Log.d(TAG, "Biometric authentication succeeded");
191-
} catch (Exception e) {
191+
} catch (Throwable e) {
192+
if (e instanceof VirtualMachineError) {
193+
throw (VirtualMachineError) e;
194+
}
192195
Log.e(TAG, "Failed to initialize storage cipher after authentication", e);
193-
callback.onError(e);
196+
callback.onError(new Exception(e));
197+
return;
194198
}
195199
callback.onSuccess(null);
196200
}

flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStoragePlugin.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,12 +230,15 @@ public void onSuccess(Void unused) {
230230
result.notImplemented();
231231
break;
232232
}
233-
} catch (Exception e) {
233+
} catch (Throwable e) {
234+
if (e instanceof VirtualMachineError) {
235+
throw (VirtualMachineError) e;
236+
}
234237
if (config.shouldDeleteOnFailure()) {
235238
try {
236239
secureStorage.deleteAll();
237240
result.success("Data has been reset");
238-
} catch (Exception ex) {
241+
} catch (Throwable ex) {
239242
handleException(ex);
240243
}
241244
} else {
@@ -249,13 +252,23 @@ public void onError(Exception e) {
249252
handleException(e);
250253
}
251254
});
252-
} catch (Exception e) {
255+
} catch (Throwable e) {
256+
// Catch Throwable, not just Exception: some OEM builds throw
257+
// java.lang.Error subclasses (e.g. NoSuchFieldError) from Android
258+
// Keystore framework code, and an uncaught Error on this
259+
// HandlerThread would crash the entire app process. Genuine VM
260+
// errors (OOM, StackOverflow) are rethrown instead of being
261+
// funneled through handleException/deleteAll, which would
262+
// allocate memory the JVM may no longer have.
263+
if (e instanceof VirtualMachineError) {
264+
throw (VirtualMachineError) e;
265+
}
253266
handleException(e);
254267
}
255268
}
256269

257270

258-
private void handleException(Exception e) {
271+
private void handleException(Throwable e) {
259272
StringWriter stringWriter = new StringWriter();
260273
e.printStackTrace(new PrintWriter(stringWriter));
261274
// Send exception message as the message field so Flutter can parse it

0 commit comments

Comments
 (0)