Les différents types de permissions
- Modèle de sécurité Android : chaque application est exécutée dans son propre processus avec son propre user ID
-
Plusieurs niveaux de protection pour les permissions :
- normal : pas d'alerte spécifique de l'utilisateur
-
dangerous : l'utilisateur peut être informé et refuser
- Il s'agit de permissions permettant d'obtenir des données confidentielles de l'appareil (localisation, lecture/écriture de contacts, accès à l'agenda, réception/envoi de SMS, enregistrement de vidéo...)
-
Depuis Android 11 :
- L'utilisateur peut accepter temporairement une permission dangereuse
- Une permission acceptée de façon persistente est automatiquement réinitialisée (redemande nécessaire) si l'application n'a pas été utilisée depuis longtemps (plusieurs mois)
- signature : seule une application signée avec le même certificat peut obtenir la permission
-
privilegied : permissions système inaccessibles depuis une application standard
- Application devant résider dans /system/priv-app : nécessite une accès root
- Application devant être mise en liste blanche dans /etc/permissions
Demande de permission
Un composant nécessitant une permission la demande dans le manifeste de l'application : <uses-permission android:name="nom.de.la.permission" /> :
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="fr.upem.coursand"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.FLASHLIGHT" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_CALL_LOG" /> <uses-permission android:name="android.permission.READ_SMS" /> <uses-permission android:name="android.permission.READ_CALENDAR" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <!-- ... --> </application> </manifest>
- Certaines permissions ne sont accordables qu'aux applications signées par le mainteneur du système (par exemple la permission BRICK)
-
Les permissions dangereuses doivent être accordées par l'utilisateur la première fois qu'elles sont utilisées
- Vérification préalable de l'accord de la permission
-
Si pas d'accord, demande à l'utilisateur en affichant un dialogue modal
- Permission acceptée : fonctionnalité utilisable
- Permission refusée : fonctionnement de l'application sans utiliser la fonctionnalité
- Permission révocable par l'utilisateur à tout moment (dans Paramètres>Applications)
Exemple de code pour une activité nécessitant une permission dangereuse :
package fr.upem.coursand.permission; import android.app.Activity; import android.app.AlertDialog; import android.content.pm.PackageManager; import android.os.Bundle; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import java.util.HashMap; import java.util.Map; /** Template for an activity that needs to acquire a dangerous permission */ public class RequiringPermissionActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } /** Store here the permission requests with the associated onSuccess and onFailure runnables */ private Map<Integer, Runnable[]> permissionRequests = new HashMap<>(); private int nextPermissionRequestCode = 1; /** Run code that requires a prior permission grant * @param permissionName name of the permission to claim * @param rationale message to display to explain to the user why this permission must be used * @param onFailure code to execute if the permission is refused * @param onSuccess code to execute if the permission is granted */ protected void runWithPermission(String permissionName, String rationale, Runnable onFailure, Runnable onSuccess) { // Is the permission already granted? if (ContextCompat.checkSelfPermission(this, permissionName) != PackageManager.PERMISSION_GRANTED) { // The permission is not granted yet // Should we show an explanation? if (rationale != null && ActivityCompat.shouldShowRequestPermissionRationale(this, permissionName)) { // show an explanation with a dialog AlertDialog alertDialog = new AlertDialog.Builder(this).create(); alertDialog.setTitle("Permission information"); alertDialog.setMessage(rationale); alertDialog.setIcon(android.R.drawable.ic_dialog_info); // if ok is pressed rerun the method (but not showing now the rationale) alertDialog.setButton("OK", ((dialogInterface, i) -> runWithPermission(permissionName, null, onFailure, onSuccess))); alertDialog.show(); } else { int code = nextPermissionRequestCode++; // use an unique id for the permission request permissionRequests.put(code, new Runnable[]{onFailure, onSuccess}); // request asynchronously the permission ActivityCompat.requestPermissions(this, new String[]{permissionName}, code); } } else onSuccess.run(); // the permission has already been given already given } /** This method is called when the user has answered to the permission request */ @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { Runnable[] runnables = permissionRequests.get(requestCode); if (runnables != null) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) runnables[1].run(); // run onSuccess since the permission has been granted else runnables[0].run(); // permission not granted } } }
Création de permissions nouvelles (utile pour un ContentProvider)
- Par défaut le ContentProvider peut être accédé uniquement par son application hôte`
- Création de nouvelles permissions (pouvant être employées par d'autres applications pour accéder par exemple aux données de notre application) dans le manifeste.
Voici un exemple pour donner l'accès en lecture/écriture à un ContentProvider :
<permission android:name="fr.upemlv.gpslog.GPS_TRACE_READ" android:label="@string/perm_label_GPSTraceRead" android:description="@string/perm_desc_GPSTraceRead" android:permissionGroup="android.permission-group.PESONAL_INFO" android:protectionLevel="dangerous" /> <permission android:name="fr.upemlv.gpslog.GPS_TRACE_WRITE" android:label="@string/perm_label_GPSTraceWrite" android:description="@string/perm_desc_GPSTraceWrite" android:permissionGroup="android.permission-group.PESONAL_INFO" android:protectionLevel="dangerous" />
Remarque : pour être accessible d'une autre application, le ContentProvider
doit également posséder la propriété android:exported="true"
Protection d'un composant par une permission
-
<{application, activity, service, receiver, provider} android:permission="p" ...>
- Une application externe doit disposer de p pour interagir avec ce composant
-
<provider> propose également deux propriétés plus précises :
- android:readPermission
- android:writePermission
- Permissions accordables avec une granularité fine sur un provider (en fonction de l'URI)
<provider ...> <path-permission android:path="string" android:pathPrefix="string" android:pathPattern="string" android:permission="string" android:readPermission="string" android:writePermission="string" /> </provider>
Permissions temporaires pour l'accès aux données
- Autorisation de permissions temporaires avec <provider android:grantUriPermissions="true" ...>
- Granularité sur URI avec <grant-uri-permission android:path="string" android:pathPattern="string" android:pathPrefix="string" />
- Permet à une application de déléguer temporairement une permission qu'elle possède (par exemple gpslog peut déléguer une permission de lecture à une application de cartographie pour visualiser les traces)
Transmission d'Intent avec permission temporaire d'accès :
Uri uri = Uri.parse("content://fr.upemlv.gpslogger.provider/log/" + id); Intent intent = new Intent(); intent.setAction(MAP_VIEW); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setData(uri); startActivity(intent) ;
Accord/révocation de permissions (méthodes de Context) :
public void grantUriPermission(String toPackage, Uri uri, int modeFlags) public void revokeUriPermission(Uri uri, int modeFlags)
Révocation de permissions
- Révocation (ou accord) possible d'une permission dangereuse d'une application par l'utilisateur possible dans Paramètres>Applications
- Révocation automatique par le système si l'application estmise en hibernation (pas d'utilisation pendant plusieurs mois)
- Révocation possible par l'application elle-même depuis Android 13 avec revokeSelfPermissionOnKill
-
Accord ou révocation de permission possible avec adb en CLI :
- Accord : adb shell pm grant com.name.app android.permission.PERMISSION_NAME
- Révocation : adb shell pm grant com.name.app android.permission.PERMISSION_NAME
Coopération malveillante inter-applicative
-
Possibilité pour des applications Android de communiquer entre-elles de nombreuses façons :
- Une application A peut démarrer une activité d'une application B avec un Intent (et recevoir un Intent de résultat renvoyé par B)
- Une application A peut démarrer un service d'une application B avec un Intent (transparent pour l'utilisateur)
- Des applications peuvent communiquer par des ContentProvider
- Des appplications peuvent utiliser des fichiers pour communiquer
- Une application peut envoyer un Intent avec sendBroadcast et d'autres applications peuvent le capturer avec un BroadcastReceiver
- Un serveur sur Internet peut servir de point de communication entre applications
-
Conséquence :
- Une application ne disposant pas d'une permission spécifique peut exploiter la permission d'une autre application
- En particulier une application disposant de la permission INTERNET peut exporter des données sensibles acquises grâce à certaines permissions (géolocalisation, carnet d'adresses...) vers un serveur web
-
Solution (difficile à mettre en œuvre) pour confiner les données :
-
Une application disposant d'une permission permettant l'accès à des données confidentielles :
- ne devrait pas avoir accès à Internet (pas de possibilité d'export vers un serveur)
- ne devrait pouvoir utiliser qu'un espace confiné du système de fichiers (inaccessible en lecture pour les autres applications)
- ne devrait pas avoir le droit d'envoyer des Intent vers d'autres applications (avec startActivity, startService ou sendBroadcast)
-
Une application disposant d'une permission permettant l'accès à des données confidentielles :