"Storage Scopes", la nouvelle fonctionnalité de GrapheneOS

Plongée dans le fonctionnement du stockage des applications sur Android. Nous découvrirons des solutions mises en oeuvre par AOSP et GrapheneOS pour prévenir des accès invasifs à l'ensemble de vos fichiers.

"Storage Scopes", la nouvelle fonctionnalité de GrapheneOS

Bien que j'écrive régulièrement à propos de GrapheneOS, je ne prends pas forcément la peine de le faire à n'importe quel ajout (même s'ils peuvent être intéressants). Mais ce dernier ajout, Storage Scopes, en vaut particulièrement la peine. Pour mieux comprendre de quoi il s'agit, il faut d'abord s'intéresser au modèle de permissions d'Android. Nous l'avons mentionné à plusieurs reprises déjà : les permissions sur Android régissent évidemment les accès des fichiers aux applications.

Un schéma récapitulatif des premières parties est présent plus bas dans l'article, n'hésitez pas à l'ouvrir dans une autre fenêtre pour mieux suivre si besoin est.

Le stockage interne des applications

Sur une version moderne d'Android, une application dispose de son propre stockage avec ses fichiers spécifiques. L'application n'a pas besoin de demander une permission pour cela. Ce stockage est entre autres un bac à sable pour l'application qui n'est visible et accessible que par ladite application. Il peut être de deux différentes natures :

  • Un stockage isolé dans la mémoire dite "interne" (dans /data/data en lecture seule et /data/user). C'est un endroit strictement réservé à l'application, d'habitude utilisé pour des fichiers essentiels au bon fonctionnement de l'application. Les données de cette dernière sont supprimées lorsqu'elle est désinstallée.
  • Un stockage à portée restreinte dans la mémoire dite "externe" (partie visible par l'utilisateur) dans le répertoire Android/data. Chaque application est autorisée par défaut à avoir son répertoire dans Android/data, et sa visibilité sera limitée à ce répertoire.
Notez par ailleurs que Android/data est inaccessible par des explorateurs de fichiers par exemple en dehors de ceux autorisés par l'OS. C'est le cas de l'explorateur par défaut (AOSP) sur GrapheneOS.

C'est depuis Android 10 que les applications (niveau d'API 29+) disposent de leur propre répertoire par défaut dans la mémoire externe sans qu'ils aient la possibilité de voir le reste des données de l'utilisateur. Ils disposent également de la possibilité de créer certains types de fichiers à certains endroits standardisés sans aucune permission également. Depuis Android 11, les applications (niveau d'API 30+) doivent composer avec cette gestion qui porte le nom de scoped storage.

Il va de soi que ce fut une avancée majeure pour la vie privée, et c'est un paradigme radicalement différent du modèle des OS traditionnels où chaque application partage le même ensemble de données de l'utilisateur.

Et la productivité ?

Certaines applications ont pourtant besoin d'avoir accès à certains fichiers de l'utilisateur, d'autres applications, etc. Ces fichiers sont persistants et destinés à être partagés entre plusieurs applications. On dit qu'ils sont présents dans la mémoire externe dans un "stockage partagé" (ou plus simplement l'ensemble de vos données personnelles). L'accès à ce stockage est régulé sur Android avec son modèle de permissions, qui se porte garant de la confidentialité des données de l'utilisateur final.

Il existe bien évidemment des approches modernes pour permettre la productivité dans un contexte d'applications à visibilité restreinte :

  • L'API MediaProvider est utilisée par les applications pour contribuer (sans permission) et accéder (avec permission) à une collection de types de fichiers comme des images, sons et vidéos de l'utilisateur (mais aussi documents et téléchargements).
  • Storage Access Framework (qui existe depuis Android 4.4) permet aux applications d'accéder à n'importe quel type de fichier. L'OS présente une fenêtre de sélection à l'utilisateur, qui donnera explicitement la permission d'accès à un fichier/dossier pour l'application en question.

Il faut aussi savoir qu'Android marque les fichiers créés avec des attributs. Un de ces attributs permet à Android de savoir quelle application a créé (et "possède" donc) tel fichier. Des mécanismes très similaires existent chez iOS.

API MediaProvider

Aujourd'hui, l'API MediaProvider est ce qui permet aux applications de manipuler et partager des fichiers extrêmement communs comme des images, des sons ou des vidéos. Cette API permet aussi de manipuler documents et téléchargements (tout type de fichier), dans leurs dossiers standards respectifs.

Un fichier "média" est indexé dans des collections par Android s'il est présent dans un dossier standard ( DCIM/, Music/, etc.) et/ou que ce dossier ne contient pas de fichier .nomedia. Les applications peuvent également contribuer directement à ces collections.

Par défaut (sans aucune permission), MediaProvider permet en effet à l'application de contribuer aux différentes collections. Cependant, l'application n'a pas de visibilité sur les autres éléments qu'elle n'a pas contribués elle-même, sauf si la permission d'accès à la mémoire READ_EXTERNAL_STORAGE "Media seulement" est octroyée.

Note : si l'application veut pouvoir renommer et supprimer ces fichiers d'autres applications, elle doit en plus demander la permission MANAGE_MEDIA. L'utilisateur devra autoriser un accès spécial.

Avant Android 10, avec les permissions READ_EXTERNAL_STORAGE et WRITE_EXTERNAL_STORAGE, MediaProvider donnait l'accès à quasiment tous les fichiers de l'utilisateur sur la mémoire externe. C'est pour cela que des applications antérieures à ces changements (ciblant un niveau d'API inférieur à 29) demandent la permission "Files & Media", tandis que les applications modernes ne pourront demander que la permission "Media seulement" (il existe des cas particuliers que nous verrons ci-dessous).

Exemples pour mieux comprendre comment fonctionne l'API :

  • Avez-vous déjà remarqué des applications peuvent télécharger dans votre dossier "Téléchargements" sans que vous ne lui aviez donné la permission ? C'est parce que l'application a par défaut le droit de contribuer à la collection MediaStore.Downloads (ce qui je pense est assez explicite).
  • Une application "Camera" n'a en réalité aucunement besoin d'une permission d'accès quelle qu'elle soit pour fonctionner. Elle peut contribuer à la collection MediaStore.Images et accéder uniquement à ses propres fichiers pour fonctionner (Google Camera est un mauvais élève pour une raison que j'ignore, mais GrapheneOS Camera implémente MediaProvider, et SAF pour les endroits personnalisés).
  • Maintenant, si une application "Galerie" doit accéder à ces fichiers, qui ont donc été produits par une application différente, il sera naturellement nécessaire qu'elle dispose de la permission READ_EXTERNAL_STORAGE ("Media seulement" sur Android 10+).
developer.android.com

Les différentes collections sont visibles dans l'explorateur de fichiers AOSP :

Images, Videos, Audio, Documents et Downloads sont des collections de fichiers indexés avec leur type respectif. Si je touche "Pixel 6", j'accède simplement à mon répertoire utilisateur sur la mémoire externe.

Chaque collection ne correspond pas vraiment à un dossier unique (bien que certains soient standardisés), mais l'ensemble des fichiers indexés correspondants.

Storage Access Framework ♥ Scoped Storage

Storage Access Framework (que je vais abréger par SAF pour la suite) combiné à Scoped Storage est naturellement la façon moderne de permettre la productivité dans un contexte soucieux de la vie privée. SAF fonctionne avec tout type de fichier/dossier présent dans le répertoire utilisateur sur la mémoire externe (à certaines exceptions près).

Ici, vous n'octroyez que des permissions précises aux fichiers ou dossiers dont vous avez besoin pour une application donnée. Pour ce faire, l'OS vous présente avec une fenêtre fournie par votre explorateur de fichiers. Vous l'avez forcément déjà utilisée !

Exemple : si je veux mettre en ligne une image sur mon navigateur sans que ce dernier ait accès à tous mes autres fichiers, ce navigateur doit implémenter SAF.
Note : l'OS vous interdit de choisir entièrement votre répertoire utilisateur, pour éviter des fausses manipulations. Sinon cela reviendrait à donner une permission classique et invasive.

Malheureusement, l'approche traditionnelle qui était largement utilisée était pour les applications de demander une visibilité voire un contrôle presque total sur le répertoire de l'utilisateur dans la mémoire externe. Cette approche est en réalité encore possible pour des applications modernes. De deux façons, en fait :

  • Elles peuvent explicitement désactiver la fonctionnalité scoped storage, pour revenir à la gestion "classique" pré-Android 10. Ensuite, elles peuvent déclarer WRITE_EXTERNAL_STORAGE ou READ_EXTERNAL_STORAGE pour demander un accès en lecture/écriture à la mémoire externe pour tout type de fichier. C'est en général une mauvaise pratique qui doit être assidûment justifiée par l'application.
  • Une permission MANAGE_EXTERNAL_STORAGE a été ajoutée pour les applications depuis Android 11. C'est une permission réservée aux applications qui en ont un besoin vital comme les explorateurs de fichiers. L'autorisation explicite de l'utilisateur est toujours requise, mais sa représentation de haut niveau est devenue un accès spécial ("All files access"). Pour éviter les dérives, Play Store restreint la publication d'applications déclarant cette permission.

Les applications peuvent également décider de ne pas cibler un niveau d'API supérieur ou égal à 29 pour ne pas à avoir à désactiver explicitement Scoped Storage, mais cette approche n'est pas désirable à long terme (Play Store ayant notamment son mot à dire). Cela dit, vous utilisez peut-être (par choix ou contrainte, peu importe) encore des applications ciblant un niveau d'API relativement ancien.

Voici un schéma récapitulatif du fonctionnement d'une application moderne. Je ne peux malheureusement pas faire figurer tous les cas particuliers mais cela devrait vous donner une bonne idée de la situation !

Les problèmes...

Pour récapituler sans se prendre la tête avec des concepts de bas niveau, une application peut demander un accès étendu au répertoire utilisateur de la mémoire externe de 3 façons :

  • La permission "Files & Media" (applications anciennes, ou modernes désactivant la gestion moderne de la mémoire) : accès presque total à la mémoire externe.
  • La permission "Media seulement" (applications modernes) : accès au contenu des différentes collections MediaStore, y compris le contenu de l'utilisateur et d'autres applications (fichiers médias indexés seulement).
  • L'accès spécial "All files access" (applications modernes) : accès presque total à la mémoire externe.
Par "presque total" j'entends quelques exceptions comme naturellement les chemins Android/data et Android/obb.

Et là, en fait, on se heurte au bon vouloir des développeurs d'applications. Certaines applications refusent de fonctionner si on ne leur donne pas un accès absolu à la mémoire externe, ou alors des fonctionnalités basiques comme la mise en ligne d'images ne fonctionnera pas sans cet accès invasif. Ces applications demandent une méthode invasive d'accès à vos fichiers et n'implémetent généralement pas SAF.

L'application Discord est un bon exemple : pour envoyer des images, elle a une galerie interne qui demande d'accéder à la collection MediaStore.images contenant les fichiers d'autres applications, mais cela nécessite de lui donner la permission READ_EXTERNAL_STORAGE (aussi appelée "Media"). C'est assez invasif comme approche puisqu'on pourrait tout simplement utiliser SAF pour ne donner la permission d'accès qu'au fichier (image, vidéo) que l'on souhaite partager. En pratique on peut palier cela on partageant l'image à Discord depuis une autre application (typiquement votre galerie), mais peu d'utilisateurs en ont l'habitude.

Les applications qui pourraient simplement se contenter de SAF et d'un accès à portée limitée (scoped storage), mais qui nécessitent une permission invasive d'accès à la mémoire externe et donc à l'ensemble de vos fichiers, sont malheureusement très nombreuses.

Alors, quelles solutions ? La première est de préférer des applications modernes qui sont plus susceptibles d'implémenter ces bonnes pratiques. Mais ce n'est pas toujours possible de choisir une application, donc...

Les profils utilisateur

Il existait déjà une solution dans AOSP : les profils utilisateur (user profiles). Dans Android moderne, les données sont compartimentalisées par utilisateur. Par défaut, vous utilisez l'utilisateur principal, à partir duquel il est possible d'ajouter des profils secondaires.

Ces profils secondaire sont des espaces de travail séparés et isolés ; sur un Google Pixel récent, chaque profil dispose de ses propres clés de chiffrement et de son propre slot Weaver sur Titan M. Une application avec des permissions invasives comme nous en avons vues ci-dessus n'aura accès qu'aux données du répertoire du profil utilisateur dans lequel cette application est installée et lancée.

Cette fonctionnalité n'est malheureusement pas présente sur toutes les distributions d'Android, car les constructeurs ont tendance à la désactiver. GrapheneOS non seulement veille à l'activer mais repousse ses limitations en permettant l'usage jusqu'à 16 profils utilisateur. Et depuis peu, GrapheneOS permet à différents profils de s'échanger des notifications.

Cette dernière n'est pas une fonctionnalité que j'utilise moi-même donc j'ai demandé à un utilisateur de faire une capture histoire de vous montrer :

Merci à /tmp

Storage Scopes

La dernière solution en date qui vient tout juste d'arriver dans la version stable de GrapheneOS est la fonctionnalité Storage Scopes. L'idée est de pousser le concept de scoped storage jusqu'à son principe élémentaire : en des termes plus français, l'application peut avoir un accès à la mémoire externe (y compris en dehors de Android/data, nous en avions parlé plus haut), mais qui sera restreint à ses propres fichiers/dossiers.

C'est une approche compatible avec la gestion moderne du stockage des applications sur Android. Android l'aurait même considérée mais finalement cette fonctionnalité n'est pas arrivée.

Ainsi, l'application sera en quelque sorte "dupée" puisque bien qu'elle aura un accès étendu dans la hiérarchie de la mémoire externe, elle ne peut que voir, créer et altérer ses propres fichiers. Les permissions qui étaient demandées dans son AndroidManifest.xml (fichier de l'application qui déclare les permissions qui vont être demandées) sont "satisfaites", et l'application ne rechignera pas.

Simplement activer cette fonctionnalité suffit pour une majorité d'applications concernées. Il n'y a pas d'autre chose à faire : l'application pourra créer ses propres dossiers et fichiers, sans pouvoir accéder à ceux des autres applications. C'est ainsi que la fonctionnalité fonctionne.

Quid de la productivité ?

Parfois vous avez tout de même besoin qu'une application accède à des fichiers que ne lui sont pas attribués. Pour cela, Storage Scopes implémente SAF dans les paramètres OS de l'application afin que l'utilisateur puisse donner à l'application l'accès aux fichiers/dossiers souhaités.

Cette liste de fichiers/dossiers autorisés est persistée par l'extension PackageManagerService de GrapheneOS (GosPackageState).

Un exemple pour mieux comprendre (avec VLC) :

  1. VLC demande l'accès à tous mes fichiers. J'ai le choix d'accepter, de refuser, ou de restreindre l'accès de sa portée dans la mémoire externe (Storage Scopes).
  2. Je choisis cette dernière option (Storage Scopes), puis je la configure pour n'autoriser l'accès qu'à un fichier particulier (un clip MP4).
  3. Enfin, on peut observer que VLC a par conséquent une visibilité restreinte dans la mémoire externe, sans qu'elle ne s'en rende compte.

Un autre exemple plus vilain cette fois-ci : Microsoft Lens.

Typiquement un cas parfait d'application qui demande un accès à la mémoire externe, sans quoi elle refuse de fonctionner (ce qui est idiot, puisqu'elle peut fonctionner avec ses propres photographies). Activer Storage Scopes revient en quelque sorte à dire - pardonnez la grossièreté - "ta gueule et fonctionne".

Et si on s'amusait avec un explorateur de fichiers ? Prenons Google Files.

Google Files ne peut ici rien voir d'autre que son propre répertoire dans Android/data. Quant au reste, il ne verra rien, sauf si je décide d'ajouter manuellement les fichiers/dossiers que je souhaite.

Pour finir, Play Store et Play services peuvent également être restreints de cette façon sur GrapheneOS. Vous savez peut-être déjà que Play services fonctionne avec les mêmes restrictions qu'une application lambda sur GrapheneOS, mais en l'occurrence il était nécessaire de lui donner la permission de stockage pour le service FIDO2, ainsi qu'à Play Store pour le téléchargement de gros jeux avec des fichiers OBB.

Storage Scopes est une solution élégante dans ce cas de figure, d'autant plus qu'un toggle est ajouté pour les marchés d'application leur permettant un accès unique à Android/obb.

Il y a vraiment un tas de possibilités qu'on pourrait mentioner :

  • Restreindre une galerie (comme Google Photos) à certains dossiers
  • Restreindre Google Camera qui n'a pas besoin de permission, donc peut contribuer avec ses images sans accéder au reste de la collection
  • Utiliser des applications comme Tachiyomi (pour les lecteurs de manga) qui désactivent scoped storage sans réelle justification

Je vous suggère de faire un petit tour dans le tableau de contrôle de vie privée dans les paramètres Android pour faire le point sur l'utilisation non-appropriée de permissions invasives par vos applications.

Android étant un écosystème largement diversifié (ce qui est une force et une faiblesse), il est toujours agréable de constater qu'il est possible de bénéficier de cette diversité tout en compensant les mauvais pratiques et lacunes en matière de vie privée de certains développeurs. Par ailleurs, c'est un problème qui n'existe pas vraiment sur iOS en raison des restrictions fortes d'Apple. Encore une fois, c'est une bonne ou une mauvaise chose, selon l'angle adopté.


Certaines limitations sont documentées ici, mais elles ne sont probablement pas gênantes en utilisation pratique. Le site officiel du projet, qui devrait bientôt être mis à jour avec davantage d'informations, explique également la fonctionnalité dans ses propres mots.

J'espère que cet article aura pu vous permettre de mieux comprendre cette nouvelle fonctionnalité, ainsi que la gestion moderne du stockage des applications dans Android par la même occasion !