Journal Sandboxer des applications avec bubblewrap (2/3) : applications de bureau

Posté par  . Licence CC By‑SA.
Étiquettes :
18
1
jan.
2024

Sommaire

(Écrit par moi en anglais, traduit à 95% par ChatGPT.)

La dernière fois, nous avons découvert comment utiliser bubblewrap pour isoler des applications CLI simples. Nous allons maintenant essayer d'isoler des applications de bureau.

Les applications de bureau nécessitent l'accès à de nombreuses ressources différentes : par exemple, la prise du serveur Wayland (ou X), la prise du serveur de son ou les services D-Bus. Vous pourriez autoriser un accès général à toutes ces ressources pour chaque application, mais cela augmenterait considérablement la surface d'attaque. Une alternative est de donner accès uniquement aux ressources utilisées par l'application que vous essayez d'isoler — bien que déterminer cela ne soit pas toujours évident puisque personne ne se soucie de documenter les ressources qu'ils utilisent.

Première étape : Une application Wayland

Pour commencer, afin d'illustrer le type de problèmes rencontrés, essayons simplement d'isoler foot, un émulateur de terminal très simple pour Wayland, comme s'il s'agissait d'une application CLI :

$ bwrap --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /sbin /sbin --ro-bind /etc /etc --proc /proc --dev /dev --tmpfs /tmp --clearenv --unshare-pid foot
warn: main.c:437: 'C' is not a UTF-8 locale, using 'C.UTF-8' instead
Fontconfig error: No writable cache directories
[fontconfig error repeated multiple times]
Fontconfig error: No writable cache directories
error: XDG_RUNTIME_DIR is invalid or not set in the environment.
 err: wayland.c:1456: failed to connect to wayland; no compositor running?

Il y a beaucoup d'erreurs à analyser ici :

  1. warn: main.c:437: 'C' is not a UTF-8 locale, using 'C.UTF-8' instead : nous réinitialisons les variables d'environnement. Cela inclut la locale ($LANG). Les applications CLI s'en préoccupent généralement peu, mais les applications de bureau, typiquement, si.

  2. Fontconfig error: No writable cache directories : fontconfig est une bibliothèque utilisée par de nombreuses applications de bureau ; elle souhaite écrire des données dans son répertoire de cache ~/.cache/fontconfig. Ici, je n'ai même pas de répertoire personnel, donc elle se plaint.

  3. error: XDG_RUNTIME_DIR is invalid or not set in the environment : les applications de bureau font généralement un usage intensif des services de session ; ils résident habituellement dans le chemin pointé par la variable d'environnement $XDG_RUNTIME_DIR (qui est par défaut /run/user/UID/). Nous avons également réinitialisé cette variable d'environnement.

  4. err: wayland.c:1456: failed to connect to wayland; no compositor running? : Les applications Wayland doivent se connecter au serveur Wayland, qui est exposé sur un socket Unix (dans le répertoire $XDG_RUNTIME_DIR). Sans cela, aucune application Wayland ne peut fonctionner.

Les erreurs 1 et 3 sont similaires : certaines variables d'environnement que l'application CLI typique ne considère pas importantes deviennent cruciales pour les applications de bureau. Heureusement, il est généralement sûr de simplement les transmettre à la sandbox sans changement. Voici une liste de variables d'environnement que je transmets telles quelles au sandbox : $HOME, $PATH, $LANG, $TERM, $XDG_RUNTIME_DIR, $XDG_SESSION_TYPE.

L'erreur 2 se produit parce qu'il n'y a pas de répertoire personnel dans le sandbox.

Avant de corriger l'erreur 4, corrigeons les erreurs 1-3. De plus, à partir de maintenant, je suppose que ces variables d'environnement sont définies dans votre environnement non sandboxé ; si ce n'est pas le cas, faites-le manuellement.

$ mkdir -p ~/sandboxes/foot
$ bwrap --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /sbin /sbin --ro-bind /etc /etc --proc /proc --dev /dev --tmpfs /tmp --clearenv --unshare-pid --bind ~/sandboxes/foot ~ --chdir ~ --setenv HOME "$HOME" --setenv PATH "$PATH" --setenv LANG "$LANG" --setenv TERM "$TERM" --setenv XDG_RUNTIME_DIR "$XDG_RUNTIME_DIR" --setenv XDG_SESSION_TYPE "$XDG_SESSION_TYPE" foot
 err: wayland.c:1456: failed to connect to wayland; no compositor running?

Nous arrivons maintenant au cœur du sujet : les ressources de session/système. foot, étant une application Wayland, a besoin d'accéder au serveur Wayland, en utilisant le socket Unix situé à $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY. Nous pourrions donner un accès général à $XDG_RUNTIME_DIR (en ajoutant --bind "$XDG_RUNTIME_DIR" "$XDG_RUNTIME_DIR"), mais cette commande révélera qu'il s'agit probablement d'une mauvaise idée :

$ ls $XDG_RUNTIME_DIR
at-spi  bus  dbus-1  dconf  doc  gnupg  nvim.56001.0  nvim.57140.0  p11-kit  pipewire-0  pipewire-0.lock  pipewire-0-manager  pipewire-0-manager.lock  pulse  ssh-agent.socket  ssh-cp  sway-ipc.1000.669.sock  systemd  wayland-1  wayland-1.lock

Vous ne voulez vraiment pas que les applications isolées dans un sandbox aient accès à votre agent SSH simplement parce qu'elles doivent pouvoir accéder à votre serveur Wayland (entre autres). Des décisions précises sur ce qui est accessible ou non sont nécessaires. Dans notre exemple simple, nous aurons juste besoin du serveur Wayland :

$ bwrap --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /sbin /sbin --ro-bind /etc /etc --proc /proc --dev /dev --tmpfs /tmp --clearenv --unshare-pid --bind ~/sandboxes/foot ~ --chdir ~ --setenv HOME "$HOME" --setenv PATH "$PATH" --setenv LANG "$LANG" --setenv TERM "$TERM" --setenv XDG_RUNTIME_DIR "$XDG_RUNTIME_DIR" --setenv XDG_SESSION_TYPE "$XDG_SESSION_TYPE" --setenv WAYLAND_DISPLAY "$WAYLAND_DISPLAY" --tmpfs "$XDG_RUNTIME_DIR" --ro-bind "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" foot

Notre commande bwrap commence à devenir assez longue ! Mais elle fonctionne, et le shell à l'intérieur de l'instance foot isolée dans le sandbox n'a pas accès à notre précieux $HOME (ni à notre agent SSH).

Ressources Simples Couramment Nécessaires

Il existe des ressources, comme le serveur Wayland, qui sont faciles à transmettre aux applications dans la sandbox et nécessaires pour un grand nombre d'applications. Je pourrais vous donner un exemple pour chacune d'entre elles, mais cela serait assez long. À la place, je vais simplement les lister, et expliquer comment les transmettre à l'environnement sandboxé :

  • Le compositeur Wayland : --setenv WAYLAND_DISPLAY "$WAYLAND_DISPLAY" --ro-bind "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY"

  • Le serveur X : --setenv DISPLAY "$DISPLAY" --ro-bind /tmp/.X11-unix /tmp/.X11-unix

  • Pulseaudio/PipeWire (serveur sonore) : --ro-bind "$XDG_RUNTIME_DIR/pulse/native" "$XDG_RUNTIME_DIR/pulse/native" --ro-bind-try ~/.config/pulse/cookie ~/.config/pulse/cookie --ro-bind-try "$XDG_RUNTIME_DIR/pipewire-0" "$XDG_RUNTIME_DIR/pipewire-0"

  • GPU (typiquement pour les jeux) : --dev-bind /dev/dri /dev/dri --ro-bind /sys /sys

  • V4L (webcam) : --dev-bind /dev/v4l /dev/v4l --dev-bind /dev/video0 /dev/video0

  • resolved (si vous utilisez systemd pour la résolution de noms de domaine) : --ro-bind /run/systemd/resolve /run/systemd/resolve

Mais bien sûr, si je dis "ce sont les plus faciles à passer à l'environnement sandboxé", cela signifie qu'il y en a d'autres qui sont plus délicats.

D-Bus

Le premier point délicat (et le plus important) est D-Bus. Je ne vais pas vous expliquer ce que c'est ; Wikipédia a une page merveilleuse à ce sujet. Pour faire court, c'est un système permettant à vos applications de communiquer entre elles.

La manière simple de gérer cela est de simplement le partager avec l'environnement sandboxé : --setenv DBUS_SESSION_BUS_ADDRESS "$DBUS_SESSION_BUS_ADDRESS" --ro-bind "$XDG_RUNTIME_DIR/bus" "$XDG_RUNTIME_DIR/bus". Cela présente le même problème que le partage général de $XDG_RUNTIME_DIR : de nombreuses applications offrent des moyens de les contrôler via D-Bus. Donner un accès direct au bus de session à une application sandboxée signifie donner un contrôle direct à ces applications. Aïe !

Une autre façon, bien sûr, est de ne pas donner accès à D-Bus dans l'environnement sandboxé du tout. Parfois, cela fonctionne. Souvent, l'application va simplement planter ou se comporter de manière incorrecte.

Les développeurs de Flatpak ont le même problème et ont développé une solution : xdg-dbus-proxy, qui est essentiellement un serveur D-Bus qui fera office de proxy pour le trafic D-Bus avec un serveur D-Bus, mais avec des règles de contrôle d'accès.

Pour illustrer, essayons d'isoler Firefox, d'abord sans D-Bus :

$ bwrap --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /sbin /sbin --ro-bind /etc /etc --proc /proc --dev /dev --tmpfs /tmp --clearenv --unshare-pid --bind ~/sandboxes/firefox ~ --chdir ~ --setenv HOME "$HOME" --setenv PATH "$PATH" --setenv LANG "$LANG" --setenv TERM "$TERM" --setenv XDG_RUNTIME_DIR "$XDG_RUNTIME_DIR" --setenv XDG_SESSION_TYPE "$XDG_SESSION_TYPE" --setenv WAYLAND_DISPLAY "$WAYLAND_DISPLAY" --tmpfs "$XDG_RUNTIME_DIR" --ro-bind "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" firefox

Ensuite, lançons Firefox une seconde fois (par exemple pour visiter github.com) :

$ bwrap --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /sbin /sbin --ro-bind /etc /etc --proc /proc --dev /dev --tmpfs /tmp --clearenv --unshare-pid --bind ~/sandboxes/firefox ~ --chdir ~ --setenv HOME "$HOME" --setenv PATH "$PATH" --setenv LANG "$LANG" --setenv TERM "$TERM" --setenv XDG_RUNTIME_DIR "$XDG_RUNTIME_DIR" --setenv XDG_SESSION_TYPE "$XDG_SESSION_TYPE" --setenv WAYLAND_DISPLAY "$WAYLAND_DISPLAY" --tmpfs "$XDG_RUNTIME_DIR" --ro-bind "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" firefox https://github.com

On pourrait s’attendre à ce que Firefox s'ouvre dans un nouvel onglet de l'instance (sandboxée) existante de Firefox. À la place, on obtient ce message d'erreur :

Firefox est déjà en cours d'exécution, mais ne répond pas. Pour utiliser Firefox, vous devez d'abord fermer le processus Firefox existant, redémarrer votre appareil, ou utiliser un profil différent.

Qu'est-ce qui se passe ? Par conception, nous utilisons le même profil dans les deux cas, mais avec deux instances différentes. Firefox ne supporte pas cela. Au lieu de cela, lorsque vous exécutez la commande firefox, elle commence par vérifier s'il existe une instance en cours d'exécution pour ce profil (en vérifiant un fichier de verrouillage), et si c'est le cas, elle lui donnera l'instruction d'ouvrir l'URL donnée dans un nouvel onglet (via D-Bus). Comme nous partageons les fichiers de profil (y compris le fichier de verrouillage), la seconde commande firefox détecte l'existence de la première instance, essaie d'ouvrir l'URL dans celle-ci, mais ne peut pas, car elle n'a pas accès à D-Bus.

Maintenant, faisons la même expérience, mais en utilisant xdg-dbus-proxy :

$ mkdir $XDG_RUNTIME_DIR/xdg-dbus-proxy
$ xdg-dbus-proxy  $DBUS_SESSION_BUS_ADDRESS $XDG_RUNTIME_DIR/xdg-dbus-proxy/firefox-main-instance.sock --filter --log --own=org.mozilla.firefox.* &
$ bwrap --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /sbin /sbin --ro-bind /etc /etc --proc /proc --dev /dev --tmpfs /tmp --clearenv --unshare-pid --bind ~/sandboxes/firefox ~ --chdir ~ --setenv HOME "$HOME" --setenv PATH "$PATH" --setenv LANG "$LANG" --setenv TERM "$TERM" --setenv XDG_RUNTIME_DIR "$XDG_RUNTIME_DIR" --setenv XDG_SESSION_TYPE "$XDG_SESSION_TYPE" --setenv WAYLAND_DISPLAY "$WAYLAND_DISPLAY" --tmpfs "$XDG_RUNTIME_DIR" --ro-bind "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" --setenv DBUS_SESSION_BUS_ADDRESS unix:path="$XDG_RUNTIME_DIR"/bus --bind "$XDG_RUNTIME_DIR"/xdg-dbus-proxy/firefox-main-instance.sock "$XDG_RUNTIME_DIR"/bus firefox

Ici, nous donnons à notre première instance de Firefox le droit de posséder les noms org.mozilla.firefox.*, ce qui signifie agir en tant que service D-Bus.

$ xdg-dbus-proxy  $DBUS_SESSION_BUS_ADDRESS $XDG_RUNTIME_DIR/xdg-dbus-proxy/firefox-secondary-instance.sock --filter --log --talk=org.mozilla.firefox.* &
$ bwrap --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /sbin /sbin --ro-bind /etc /etc --proc /proc --dev /dev --tmpfs /tmp --clearenv --unshare-pid --bind ~/sandboxes/firefox ~ --chdir ~ --setenv HOME "$HOME" --setenv PATH "$PATH" --setenv LANG "$LANG" --setenv TERM "$TERM" --setenv XDG_RUNTIME_DIR "$XDG_RUNTIME_DIR" --setenv XDG_SESSION_TYPE "$XDG_SESSION_TYPE" --setenv WAYLAND_DISPLAY "$WAYLAND_DISPLAY" --tmpfs "$XDG_RUNTIME_DIR" --ro-bind "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" --setenv DBUS_SESSION_BUS_ADDRESS unix:path="$XDG_RUNTIME_DIR"/bus --bind "$XDG_RUNTIME_DIR"/xdg-dbus-proxy/firefox-secondary-instance.sock "$XDG_RUNTIME_DIR"/bus firefox https://github.com

Ici, nous donnons à notre seconde instance de Firefox le droit d'utiliser les services enregistrés sous les noms org.mozilla.firefox.*. Vous devriez voir un nouvel onglet s’ouvrir dans la première instance de Firefox.

Vous devriez maintenant connaître les bases de l'utilisation de D-Bus dans un environnement sandboxé. Quelques remarques finales sur ce sujet :

  • --own implique --talk, donc si vous voulez juste créer une seule commande pour isoler Firefox, --own devrait suffire pour les deux cas d'utilisation (instance principale et secondaire).

  • J'ai utilisé --log dans xdg-dbus-proxy, afin que vous puissiez observer ce qui se passe. Vous pouvez le supprimer une fois que tout fonctionne à votre satisfaction.

  • xdg-dbus-proxy prend en charge des permissions plus précises (par exemple, permettant uniquement d'utiliser la méthode OpenURL). Si cette fonctionnalité vous intéresse, lisez la documentation : l'essayer est laissé en exercice au lecteur.

  • J'ai parlé et démontré le bus de session, mais il y a en fait deux bus D-Bus : le bus système (à l'échelle du système, possédé par root) et le bus de session (à l'échelle de la session, possédé par l'utilisateur propriétaire de la session). La plupart des applications de bureau utiliseront exclusivement le bus de session, mais quelques-unes voudront accéder au bus système (un exemple important est wine, qui utilise le service D-Bus système org.freedesktop.UDisks ou org.freedesktop.UDisks2, selon votre version de wine). Les mêmes principes s'appliquent, et faire en sorte que cela fonctionne est également laissé en exercice au lecteur (sérieusement, ce billet de blog devient vraiment long, et nous ne sommes pas encore tout à fait terminés).

  • Vous pouvez utiliser busctl --user pour introspecter le bus de session (omettez --user pour le bus système).

XDG Desktop Portal

XDG Desktop Portal est une tentative de flatpak (oui, encore eux. Je n'aime pas flatpak en tant que solution de distribution, mais il faut rendre hommage aux personnes derrière le projet : ils font du bon travail) de standardiser l'accès aux ressources de manière sécurisée, par exemple (mais pas seulement) :

  • Prendre des captures d'écran et des screencasts

  • Contourner la sandbox pour le système de fichiers

  • Accéder à la webcam

  • Ouvrir des URI (fichiers et ressources)

J'ai ajouté "de manière sécurisée", car l'objectif est d'exiger une interaction de l'utilisateur pour des opérations sensibles. Par exemple, XDG Desktop Portal ne donne pas un accès non sécurisé à l'ensemble du système de fichiers : il permet aux applications sandboxées de demander l'accès à un fichier en dehors de la sandbox au moyen d'un sélecteur de fichiers. L'utilisateur doit sélectionner explicitement le fichier. Dans d'autres cas, l'utilisateur peut par exemple devoir autoriser certaines actions.

Cela fonctionne en créant un service D-Bus dans l'environnement non sandboxé, et en donnant accès à org.freedesktop.portal.* (avec xdg-dbus-proxy, voir la section précédente) aux applications sandboxées.

D'abord, configurons-le. Voici mon fichier de configuration :

$ cat ~/.config/xdg-desktop-portal/portals.conf
[preferred]
default=gtk
org.freedesktop.impl.portal.ScreenCast=wlr
org.freedesktop.impl.portal.Screenshot=wlr

XDG Desktop Portal utilise un système de backend pour effectuer les actions. Différents environnements de bureau hôtes utiliseront différents backends. Ici, j'utilise le backend gtk par défaut, et le backend wlr pour les captures d'écran et les screencasts (car j'utilise sway). Référez-vous à la documentation pour différents paramétrages.

Il devrait être lancé automatiquement avec votre session ; sinon, systemctl --user start xdg-desktop-portal.service devrait faire l'affaire.

Essayons, en isolant à nouveau Firefox et en essayant d'ouvrir un fichier en dehors de la sandbox.

$ touch ~/sandboxes/flatpak-info
$ xdg-dbus-proxy  $DBUS_SESSION_BUS_ADDRESS $XDG_RUNTIME_DIR/xdg-dbus-proxy/firefox-main-instance.sock --filter --log --own=org.mozilla.firefox.* --talk=org.freedesktop.portal.* &
$ bwrap --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /lib /lib --ro-bind /lib64 /lib64 --ro-bind /sbin /sbin --ro-bind /etc /etc --proc /proc --dev /dev --tmpfs /tmp --clearenv --unshare-pid --bind ~/sandboxes/firefox ~ --chdir ~ --setenv HOME "$HOME" --setenv PATH "$PATH" --setenv LANG "$LANG" --setenv TERM "$TERM" --setenv XDG_RUNTIME_DIR "$XDG_RUNTIME_DIR" --setenv XDG_SESSION_TYPE "$XDG_SESSION_TYPE" --setenv WAYLAND_DISPLAY "$WAYLAND_DISPLAY" --tmpfs "$XDG_RUNTIME_DIR" --ro-bind "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" --setenv DBUS_SESSION_BUS_ADDRESS unix:path="$XDG_RUNTIME_DIR"/bus --bind "$XDG_RUNTIME_DIR"/xdg-dbus-proxy/firefox-main-instance.sock "$XDG_RUNTIME_DIR"/bus --bind ~/sandboxes/flatpak-info "$XDG_RUNTIME_DIR"/flatpak-info --bind ~/sandboxes/flatpak-info /.flatpak-info firefox

Nous avons dû créer deux fichiers vides dans la sandbox, /.flatpak-info et $XDG_RUNTIME_DIR/flatpak-info, afin que Firefox puisse savoir que nous sommes dans une sandbox et devrait utiliser le portail. De nombreuses applications sont dans le même cas.

Maintenant, si vous essayez d'ouvrir un fichier (fichier HTML ou image) en utilisant Ctrl-O, vous devriez voir votre système de fichiers complet, non sandboxé, dans le sélecteur de fichiers, et vous devriez être capable d'ouvrir un fichier. Cependant, si vous visitez l'URL file:///, vous devriez constater que Firefox lui-même est toujours sandboxé.

Conclusion

Vous devriez maintenant connaître presque tout ce qu'il y a à savoir pour isoler les applications de bureau.

Si une application vous pose problème, vous pouvez essayer ce qui suit :

  • Utilisez busctl --user monitor pour observer ce qui se passe dans le cas non sandboxé, quels noms l'application essaie d'acquérir, quels services D-Bus elle essaie d'accéder

  • Utilisez strace -e file pour voir à quels fichiers l'application essaie d'accéder. Faites particulièrement attention aux erreurs ENOENT (fichier n'existe pas)

  • Recherchez l'application sur le répertoire flathub ; vous pourriez y trouver des solutions de contournement que le projet flatpak a dû utiliser pour cette application spécifique

Ceci conclut notre aperçu de bubblewrap. Cependant, la commande bwrap commence à devenir très encombrante. La semaine prochaine, je vous présenterai le script que j'utilise réellement pour le rendre gérable.

  • # Flatpak

    Posté par  (site web personnel) . Évalué à 3.

    Merci pour ce journal. Très intéressant comme le précédent. J'attends le 3ème avec impatience.

    Je n'aime pas flatpak en tant que solution de distribution, mais il faut rendre hommage aux personnes derrière le projet : ils font du bon travail)

    Par curiosité, qu'est-ce que tu reproches à Flatpak ? Et du coup, quels seraient tes critères pour une "bonne" solution de distribution ?

    Matthieu Gautier|irc:starmad

    • [^] # Re: Flatpak

      Posté par  . Évalué à 4.

      Mon reproche est simple : ça fait doublon avec ma distribution. Une bonne solution ? La distribution Linux de ton choix.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.