VPN en chroot sous Android : pourquoi le tunnel fonctionne et pourquoi les applis l’ignorent
chroot change la résolution des chemins, pas le noyau, l’espace de noms réseau ni l’enregistrement VPN d’Android. Deux issues en découlent, et les deux surprennent.
Vous faites tourner une distribution Linux dans un chroot sur le téléphone, vous y installez un client VPN, et le shell annonce un tunnel établi. Puis vous passez au navigateur et le trafic sort par les données mobiles comme si rien ne s’était passé. Ou l’inverse : toutes les applis perdent la connexion parce que le client a ajouté une route par défaut dans l’espace de noms qu’Android utilise réellement. Ces deux symptômes viennent de la même pièce manquante, et aucun n’est un bug du client VPN.
Le shell dit connecté, les applis ne sont pas d’accord
La première issue est la plus courante. Un tunnel en espace utilisateur ouvert depuis un chroot peut terminer sa poignée de main, afficher ses statistiques et transporter le trafic que produit ce même shell, tandis que les applis Android continuent d’utiliser le réseau par défaut du téléphone. Rien ne les intercepte, car rien n’a dit à Android qu’un VPN existe.
La seconde issue est la dangereuse. Si le chroot partage l’espace de noms réseau d’Android, ce qui est le cas par défaut, un processus assez privilégié peut ajouter des routes et une interface TUN qui touchent tout le téléphone. Le tunnel fonctionne alors pour chaque appli, y compris celles que vous ne vouliez pas router, et la connectivité du téléphone dépend désormais d’un démon sans gestion de cycle de vie, sans indicateur de connexion et sans stratégie toujours actif.
Laquelle des deux vous avez obtenue se tranche avec une commande. Comparez ip route dans le chroot avec la même commande depuis un shell Android à l’extérieur. Une sortie identique signifie que le chroot est dans l’espace de noms de l’hôte : tout changement réseau qu’il fait vaut pour tout le téléphone. Une sortie différente signifie que le lanceur a créé un espace de noms pour lui, et que son tunnel est invisible pour Android par construction.
Ce que chroot a changé et ce qu’il n’a pas changé
| Object | Effect of a chroot |
|---|---|
| Pathname resolution | Changed: lookups start at the new root |
| Kernel and drivers | Unchanged: the phone's kernel, its TUN driver, its limits |
| Network namespace | Unchanged unless the launcher creates one |
| Android VPN registration | Unchanged: no app consented, no VPN is selected |
L’appel système chroot fait une chose : il change le répertoire utilisé pour la résolution des chemins. La page de manuel dit explicitement que ce n’est pas une frontière de sécurité, et ne dit rien des interfaces, des routes ni de l’état du VPN. Tout ce que les gens attendent d’un conteneur Linux — une pile réseau privée, un système de fichiers filtré, des limites de ressources — vient d’autres mécanismes que le lanceur a pu utiliser ou non.
C’est pourquoi les affirmations selon lesquelles un chroot isole un VPN répondent souvent à la mauvaise question. La question pertinente est dans quel espace de noms le processus tourne et dans quel domaine SELinux il est confiné, et les deux sont des propriétés de la façon dont il a été lancé, pas du mot chroot.
TUN demande trois choses, et un nœud de périphérique n’en est qu’une
Un tunnel en espace utilisateur classique a besoin d’un périphérique TUN qu’il peut ouvrir, du support du noyau pour ce périphérique, et de la permission de configurer l’interface et les routes. Ce sont des exigences distinctes, et créer un nom de chemin sous /dev n’en satisfait aucune à lui seul :
ls -l /dev/net/tun
ip tuntap add dev tun0 mode tun
Sous Android, le support TUN est généralement compilé dans le noyau, parce que le service VPN de la plateforme l’utilise, mais cela ne veut pas dire que votre processus a le droit de l’utiliser. Configurer l’interface exige la capacité d’administration réseau, et SELinux décide si le processus peut atteindre le nœud du périphérique. Un refus ressemble à une erreur de permission sans cause évidente, et la réponse est dans la trace d’audit :
dmesg | grep avc
logcat -d | grep "avc: denied"
Root ne règle pas cela. SELinux contraint les processus en uid 0 exactement comme les autres, donc un chroot lancé depuis un shell root peut encore se voir refuser un périphérique qu’il voit. Désactiver l’application pour faire marcher un vieil installeur est une décision pour tout le téléphone aux conséquences bien au-delà du tunnel, et ce n’est pas un correctif pour un binaire obsolète.
Pourquoi les applis Android ignorent votre tunnel
Le modèle pris en charge par Android est une appli VPN qui s’enregistre auprès du service VPN de la plateforme, demande une fois le consentement de l’utilisateur et reçoit un descripteur de fichier pour son tunnel. La plateforme achemine alors le trafic dans ce descripteur et applique la portée d’applis choisie par l’utilisateur ou l’administrateur, ce qui explique que le même mécanisme puisse être par appli, par profil et combiné avec toujours actif et blocage sans VPN. Le guide de développement VPN d’Android décrit cette poignée de main, et l’essentiel est qu’il s’agit d’un enregistrement, pas d’une astuce de routage.
Un démon dans un chroot ne s’enregistre jamais. Pas de boîte de consentement, pas d’icône de clé dans la barre d’état, pas de liste d’inclusion ou d’exclusion par appli, et aucune participation à la stratégie toujours actif. Les applis continuent donc d’utiliser le réseau par défaut, et le tunnel ne transporte que ce que le shell lui-même envoie.
L’autre sens n’aide pas non plus. Pousser des routes dans l’espace de noms de l’hôte capture tout, y compris le trafic que la plateforme comptait garder local, et le fait sans contrôle par appli et sans moyen pour l’utilisateur de le voir ou de le révoquer. Il n’existe pas de réglage intermédiaire où un démon en chroot route le trafic d’une seule appli.
La compatibilité binaire est un mur à part
Même quand le côté réseau est intéressant, l’exécutable a ses propres exigences : architecture du processeur, chargeur dynamique, bibliothèque C et les fonctions du noyau qu’il attend. Android utilise bionic et non glibc, et un vieux binaire compilé pour une distribution de bureau peut demander des bibliothèques qui n’existent plus sur l’appareil.
Lier le nom d’un fichier de bibliothèque à une version plus récente ne crée pas de compatibilité ABI, et une bibliothèque cryptographique archivée issue d’un paquet de dix ans n’est pas un composant maintenu. Si le plan en dépend, le plan est de faire tourner du code non maintenu avec des privilèges réseau sur un téléphone.
Prouvez-le dans le contexte Android, puis nettoyez
Testez ce qui compte vraiment pour vous, du côté qui compte : ouvrez l’appli concernée, atteignez la destination privée concernée, vérifiez quel résolveur a répondu, puis coupez le tunnel et confirmez le comportement documenté. Un résultat obtenu depuis un terminal dans le chroot ne prouve quelque chose que sur le contexte d’exécution de ce terminal.
Si vous avez changé des routes de l’hôte pendant l’expérience, rétablissez-les et notez ce que vous avez changé. Vérifiez ensuite si le téléphone a un VPN toujours actif configuré, car une ancienne expérience qui a laissé une règle peut ressembler à une panne de la plateforme des mois plus tard.
Pour un vrai besoin d’accès distant, utilisez le client pris en charge du service ou une conception de passerelle approuvée. La question qui vaut la peine n’est pas de savoir si l’on peut faire tourner un installeur root, mais si une implémentation prise en charge fait passer les applis dont vous avez besoin au-delà de la frontière voulue, sans laisser de changements privilégiés sans propriétaire sur l’appareil.