12

Ho un'app che, quando viene notificata da un ContentObserver di una modifica a ContentProvider, tenta di interrogare il provider su un thread in background. Questo provoca un SecurityException per essere gettato:In che modo un thread creato da un'app può essere considerato un'app diversa da ContentProvider dell'app?

 
8-10 15:54:29.577 3057-3200/com.xxxx.mobile.android.xxx W/Binder﹕ Caught a RuntimeException from the binder stub implementation. 
    java.lang.SecurityException: Permission Denial: reading com.xxx.mobile.android.mdk.model.customer.ContentProvider uri content://com.xxx.mobile.android.consumer.xxx/vehicle from pid=0, uid=1000 requires the provider be exported, or grantUriPermission() 
at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:539) 
      at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:452) 
      at android.content.ContentProvider$Transport.query(ContentProvider.java:205) 
      at android.content.ContentResolver.query(ContentResolver.java:478) 
      at android.content.ContentResolver.query(ContentResolver.java:422) 

Come sarebbe un filo creato da un app finire con un UID diverso da ContentProvider della app?

Effettuando un punto di interruzione un'eccezione in android.content.ContentProvider vedo che UserHandle.isSameApp(uid, mMyUid) è false e UserHandle.isSameUser(uid, mMyUid) è true. Vedo anche che il provider UID è 10087.

+0

Stai chiedendo di uid = 1000: è l'id utente del sistema Android. È probabile che la richiesta venga inoltrata internamente al sistema per l'elaborazione. – adelphus

+0

@adelphus Sì. Ho pensato che fosse la causa dell'eccezione di sicurezza, ma ora non sono sicuro perché 'UserHandle.isSameUser' restituisce' true'. –

+0

Gli utenti Android non sono correlati ai valori di app uid. Non confonderli! I valori di app uid vengono utilizzati per applicare il sandboxing tra le app, la sicurezza degli utenti è implementata in modo diverso. – adelphus

risposta

1

Ho avuto lo stesso problema durante il tentativo di interagire con il mio ContentProvider in un callback di sistema (LeScanCallback). Il problema è che il thread di callback è di proprietà del sistema Android e non della mia app, anche se il codice è nella mia app.

Passare il lavoro dal callback a uno dei miei thread di app prima di tentare di interagire con il mio ContentProvider ha risolto il problema con successo.

Per ridurre la piastra di riscaldamento per la creazione di fili e il riciclaggio (necessario per le richiamate frequenti per ridurre il sovraccarico), ho utilizzato AndroidAnnotation's @Background annotation sul metodo delegato.

5

Il valore uid di 1000 appartiene al sistema Android. Molte funzionalità di Android coinvolgono richieste di proxy al thread di sistema per l'elaborazione. Se viene lanciata un'eccezione durante questo, l'errore includerà l'uid del sistema, piuttosto che il richiedente originale.

Per gli altri punti:

UserHandle.isSameApp(uid, mMyUid) is false

UserHandle.isSameUser(uid, mMyUid) is true

Questi sono più facili da spiegare, cercando al source. Su un dispositivo Android con supporto multiutente, ciascun utente è definito da un intervallo di UID. isSameApp è falso perché il modulo dei ids non corrispondono:

public static final boolean isSameApp(int uid1, int uid2) { 
     return getAppId(uid1) == getAppId(uid2); 
} 

public static final int getAppId(int uid) { 
     return uid % PER_USER_RANGE; 
} 

Analogamente, i due ids appartengono allo stesso utente perché vivono nella stessa gamma:

public static final boolean isSameUser(int uid1, int uid2) { 
     return getUserId(uid1) == getUserId(uid2); 
} 

public static final int getUserId(int uid) { 
     if (MU_ENABLED) { 
      return uid/PER_USER_RANGE; 
     } else { 
      return 0; 
     } 
} 

noti che questa logica è viziato perché significa che tutti gli utenti del sistema Android (< 10000) verranno considerati come "appartenenti" al primo utente.

Si noti inoltre che se un secondo utente installa più di 1000 app (!), C'è la possibilità che un'app venga scambiata per un'app di sistema (sia uid % PER_USER_RANGE restituirà 1000). Non importa, però, perché il forte sandboxing impedirebbe qualsiasi cosa troppo male da accadere.

+0

Questo mi aiuta a capire come funzionano gli ID. Grazie. Hai un suggerimento su come evitare l'eccezione di sicurezza senza aprire il content provider per accedere ad altre app? –

+0

Il modo normale di proteggere i fornitori di contenuti consiste nel definire e includere le autorizzazioni personalizzate [come questa] (http://stackoverflow.com/questions/14793672/requesting-read-permission-from-my-own-contentprovider-in-another- app). Ma non sono del tutto sicuro di cosa stai cercando di ottenere, quindi forse questo non è ciò di cui hai bisogno. – adelphus

+0

Questo collegamento sembra utile per connettere un'app al fornitore di contenuti di un'altra. Ma nel mio caso ho solo un'app. E il fornitore di contenuti di questa app sembra pensare che un thread generato dall'app provenga da un'altra app, e quindi genera un'eccezione di sicurezza. –

2

Se un Thread viene avviato da qualsiasi componente dell'applicazione con il provider, è possibile accedere a ContentProvider senza alcun SecurityException.

Sto usando uno ContentProvider nella mia applicazione proprio come un livello di astrazione extra e non ho esposto il contenuto ad altre applicazioni. Sto accedendo allo ContentProvider nella discussione in background (non AsyncTask, ma un semplice java.lang.Thread). Non ricevo alcun SecurityException. Quello che segue è il codice dalla mia applicazione.

AndroidManifest.xml

<provider 
    android:authorities="com.sample.provider" 
    android:name="com.sample.MyProvider" 
    android:exported="false" /> 

MainActivity

public void performContinue(Bundle extras){ 
    Thread thread = new Thread(new Runnable() { 
     @Override 
     public void run() { 
      String AUTHORITY = "com.sample.provider"; 
      Uri BASE_URI = Uri.parse("content://" + AUTHORITY); 
      Uri currentUri = BASE_URI.buildUpon().appendPath("SAMPLE_COUNT").build(); 
      final Cursor query = InputActivity.this.getContentResolver().query(currentUri, null, null, null, null); 
      if (query != null) { 
       final int count = query.getCount(); 
       Log.d("DEBUG","CONTENT = " + count); 
      }else{ 
       Log.d("DEBUG","CONTENT = CURSOR NULL"); 
      } 
     } 
    }); 
    thread.setName("THREAD_1"); 
    thread.start(); 
} 


Non mi sembra di ottenere qualsiasi SecurityException. Idealmente, dobbiamo usare AsyncQueryHandler per accedere allo ContentProvider, perché questo consente di eseguire tutto il processo di recupero nel thread in background e userà il thread UI per pubblicare i risultati nell'interfaccia utente. Ma dopo aver visto questo post, volevo solo vedere se potevo semplicemente usare Thread e controllare se potevo comunque accedervi senza alcuna Eccezione. Funziona bene.

Problemi correlati