ankidroid / Anki-Android

AnkiDroid: Anki flashcards on Android. Your secret trick to achieve superhuman information retention.
GNU General Public License v3.0
8.67k stars 2.24k forks source link

[Bug] SecurityException when sharing image to AnkiDroid [Image Occlusion] if app is not open #15653

Open david-allison opened 8 months ago

david-allison commented 8 months ago
Reproduction Steps
  1. API 33 emulator
  2. Close AnkiDroid
  3. Share an image from the 'Files' App
  4. Select Image Occlusion
ACRA caught a SecurityException for com.ichi2.anki.debug
  java.lang.SecurityException: Permission Denial: opening provider com.android.providers.downloads.DownloadStorageProvider from ProcessRecord{33af84f 10815:com.ichi2.anki.debug/u0a181} (pid=10815, uid=10181) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
    at android.os.Parcel.createExceptionOrNull(Parcel.java:3011)
    at android.os.Parcel.createException(Parcel.java:2995)
    at android.os.Parcel.readException(Parcel.java:2978)
    at android.os.Parcel.readException(Parcel.java:2920)
    at android.app.IActivityManager$Stub$Proxy.getContentProvider(IActivityManager.java:5224)
    at android.app.ActivityThread.acquireProvider(ActivityThread.java:7007)
    at android.app.ContextImpl$ApplicationContentResolver.acquireUnstableProvider(ContextImpl.java:3420)
    at android.content.ContentResolver.acquireUnstableProvider(ContentResolver.java:2526)
    at android.content.ContentResolver.query(ContentResolver.java:1203)
    at android.content.ContentResolver.query(ContentResolver.java:1151)
    at android.content.ContentResolver.query(ContentResolver.java:1107)
    at com.ichi2.utils.ImportUtils$FileImporter.getFileNameFromContentProvider(ImportUtils.kt:251)
    at com.ichi2.utils.ImportUtils$FileImporter.getFileCachedCopy(ImportUtils.kt:131)
    at com.ichi2.utils.ImportUtils.getFileCachedCopy(ImportUtils.kt:68)
    at com.ichi2.anki.NoteEditor.onCollectionLoaded(NoteEditor.kt:658)
    at com.ichi2.anki.AnkiActivity.startLoadingCollection$lambda$0(AnkiActivity.kt:287)
    at com.ichi2.anki.AnkiActivity.$r8$lambda$kj9hf0g17vxBfzngPYSgjzadw2g(Unknown Source:0)
    at com.ichi2.anki.AnkiActivity$$ExternalSyntheticLambda1.execute(Unknown Source:2)
    at com.ichi2.async.CollectionLoader$load$1.invokeSuspend(CollectionLoader.kt:52)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at android.os.Handler.handleCallback(Handler.java:942)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loopOnce(Looper.java:201)
    at android.os.Looper.loop(Looper.java:288)
    at android.app.ActivityThread.main(ActivityThread.java:7872)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
    Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@b94260d, Dispatchers.Main.immediate]
  Caused by: android.os.RemoteException: Remote stack trace:
    at com.android.server.am.ContentProviderHelper.checkAssociationAndPermissionLocked(ContentProviderHelper.java:651)
    at com.android.server.am.ContentProviderHelper.getContentProviderImpl(ContentProviderHelper.java:264)
    at com.android.server.am.ContentProviderHelper.getContentProvider(ContentProviderHelper.java:140)
    at com.android.server.am.ActivityManagerService.getContentProvider(ActivityManagerService.java:6470)
    at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:2395)
david-allison commented 8 months ago

This is fixable if the IO editor is opened after onCreate

criticalAY commented 8 months ago

Tried to reproduce it but unable to do so right now

david-allison commented 8 months ago

API 33 emulator:

f145def4c4ee9b944bcb1df6cd04a636a5cc8bd3

15653.webm

``` 14:22:34.271 ACRA E ACRA caught a SecurityException for com.ichi2.anki.debug java.lang.SecurityException: Permission Denial: opening provider com.android.providers.downloads.DownloadStorageProvider from ProcessRecord{edd183 20405:com.ichi2.anki.debug/u0a182} (pid=20405, uid=10182) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs at android.os.Parcel.createExceptionOrNull(Parcel.java:3011) at android.os.Parcel.createException(Parcel.java:2995) at android.os.Parcel.readException(Parcel.java:2978) at android.os.Parcel.readException(Parcel.java:2920) at android.app.IActivityManager$Stub$Proxy.getContentProvider(IActivityManager.java:5224) at android.app.ActivityThread.acquireProvider(ActivityThread.java:7007) at android.app.ContextImpl$ApplicationContentResolver.acquireUnstableProvider(ContextImpl.java:3420) at android.content.ContentResolver.acquireUnstableProvider(ContentResolver.java:2526) at android.content.ContentResolver.query(ContentResolver.java:1203) at android.content.ContentResolver.query(ContentResolver.java:1151) at android.content.ContentResolver.query(ContentResolver.java:1107) at com.ichi2.utils.ImportUtils$FileImporter.getFileNameFromContentProvider(ImportUtils.kt:250) at com.ichi2.utils.ImportUtils$FileImporter.getFileCachedCopy(ImportUtils.kt:130) at com.ichi2.utils.ImportUtils.getFileCachedCopy(ImportUtils.kt:67) at com.ichi2.anki.NoteEditor.onCollectionLoaded(NoteEditor.kt:658) at com.ichi2.anki.AnkiActivity.startLoadingCollection$lambda$0(AnkiActivity.kt:287) at com.ichi2.anki.AnkiActivity.$r8$lambda$kj9hf0g17vxBfzngPYSgjzadw2g(Unknown Source:0) at com.ichi2.anki.AnkiActivity$$ExternalSyntheticLambda1.execute(Unknown Source:2) at com.ichi2.async.CollectionLoader$load$1.invokeSuspend(CollectionLoader.kt:52) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at android.os.Handler.handleCallback(Handler.java:942) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7872) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@2dabbdc, Dispatchers.Main.immediate] Caused by: android.os.RemoteException: Remote stack trace: at com.android.server.am.ContentProviderHelper.checkAssociationAndPermissionLocked(ContentProviderHelper.java:651) at com.android.server.am.ContentProviderHelper.getContentProviderImpl(ContentProviderHelper.java:264) at com.android.server.am.ContentProviderHelper.getContentProvider(ContentProviderHelper.java:140) at com.android.server.am.ActivityManagerService.getContentProvider(ActivityManagerService.java:6470) at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:2395) ```
criticalAY commented 8 months ago

The source of this exception, https://github.com/ankidroid/Anki-Android/blob/09ab288ee1024fedad61a41441e8fa1963ea8237/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.kt#L654-L664

ImportUtils.getFileCachedCopy(this, saveImageUri)?.let { path -> 
             setupImageOcclusionEditor(path) 
         }

Here

david-allison commented 8 months ago

The code needs to be moved some time after onCreate

criticalAY commented 8 months ago

This is interesting now, it does crash on emulator but work fine on my device My device :

AnkiDroid Version = 2.18alpha1-debug (6a41697449c76aa6bc07f63565b175dcc2ef38cc)

Backend Version = 0.1.34-anki23.12.1 (23.12.1 1a1d4d5419c6b57ef3baf99c9d2d9cf85d36ae0a)

Android Version = 13 (SDK 33)

ProductFlavor = amazon

Manufacturer = OnePlus

Model = EB2101

Hardware = qcom

Webview User Agent = Mozilla/5.0 (Linux; Android 13; EB2101 Build/TP1A.220905.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/121.0.6167.178 Mobile Safari/537.36

ACRA UUID = f235f25a-b2fa-42ef-bd5e-c885be80ba41

Crash Reports Enabled = false

Emualtor:

AnkiDroid Version = 2.18alpha1-debug (6a41697449c76aa6bc07f63565b175dcc2ef38cc)

Backend Version = 0.1.34-anki23.12.1 (23.12.1 1a1d4d5419c6b57ef3baf99c9d2d9cf85d36ae0a)

Android Version = 13 (SDK 33)

ProductFlavor = amazon

Manufacturer = Google

Model = sdk_gphone_x86_64

Hardware = ranchu

Webview User Agent = Mozilla/5.0 (Linux; Android 13; sdk_gphone_x86_64 Build/TE1A.220922.034; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/103.0.5060.71 Mobile Safari/537.36

ACRA UUID = 12e3e0b2-1777-495a-a031-e3086a978ff5

Crash Reports Enabled = false

Not as easy as I thought it would be

github-actions[bot] commented 3 months ago

Hello 👋, this issue has been opened for more than 3 months with no activity on it. If the issue is still here, please keep in mind that we need community support and help to fix it! Just comment something like still searching for solutions and if you found one, please open a pull request! You have 7 days until this gets closed automatically

lukstbit commented 1 month ago

I think we are loosing the permission to access the uri between activities(I think it's a normal behavior). In the scenario above, IntentHandler receives the intent with the imageUri and can access the provider with the uri(adding inside IntentHandler a call to the method querying the provider succeeds) but when it passes it to SingleFragmentActivity with the image occlusion fragment it crashes as it doesn't have the permission anymore. I'm not sure how to fix this.

david-allison commented 1 month ago

Copying the image to cache inside IntentHandler appears to be a feasible solution.

Are there any blockers to this approach besides:

mikehardy commented 1 month ago

I think that's fine - I don't see either perf hit or inconvenient cache clear as being blockers in any way