VPN in chroot unter Android: Warum der Tunnel läuft und Apps ihn ignorieren
chroot ändert die Pfadauflösung, nicht den Kernel, den Netzwerk-Namespace oder die VPN-Registrierung von Android. Daraus folgen zwei Ergebnisse, und beide überraschen.
Man bringt eine Linux-Distribution unter einem chroot auf dem Telefon zum Laufen, installiert darin einen VPN-Client, und die Shell meldet einen fertigen Tunnel. Dann wechselt man in einen Browser, und der Verkehr geht über mobile Daten hinaus, als wäre nichts geschehen. Oder das Gegenteil: Jede App auf dem Telefon verliert die Verbindung, weil der Client eine Standardroute in dem Namespace eingetragen hat, den Android tatsächlich nutzt. Beide Symptome stammen von demselben fehlenden Baustein, und keines ist ein Fehler des VPN-Clients.
Die Shell sagt verbunden, die Apps sind anderer Meinung
Das erste Ergebnis ist das übliche. Ein Userspace-Tunnel, der aus einem chroot geöffnet wurde, kann seinen Handshake abschließen, seine Statistik ausgeben und Verkehr tragen, den dieselbe Shell erzeugt, während Androids Apps weiterhin das Standardnetz des Telefons verwenden. Nichts fängt sie ab, weil nichts Android gesagt hat, dass es ein VPN gibt.
Das zweite Ergebnis ist das gefährliche. Teilt das chroot den Netzwerk-Namespace von Android, was standardmäßig der Fall ist, kann ein Prozess mit genügend Rechten Routen und ein TUN-Interface hinzufügen, die das ganze Telefon betreffen. Dann funktioniert der Tunnel für jede App, auch für die, die man nicht routen wollte, und die Verbindung des Telefons hängt nun von einem Daemon ohne Lifecycle-Verwaltung, ohne Verbindungsanzeige und ohne Immer-an-Richtlinie ab.
Welches der beiden Ergebnisse man bekommen hat, lässt sich mit einem Befehl beantworten. Vergleiche ip route im chroot mit demselben Befehl aus einer Android-Shell außerhalb. Identische Ausgabe heißt, das chroot liegt im Namespace des Hosts: Jede Netzwerkänderung, die es vornimmt, gilt für das ganze Telefon. Abweichende Ausgabe heißt, der Launcher hat einen eigenen Namespace dafür erzeugt, und sein Tunnel ist für Android konstruktionsbedingt unsichtbar.
Was chroot geändert hat und was nicht
| 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 |
Der Systemaufruf chroot tut eine Sache: Er ändert das Verzeichnis, das zur Pfadauflösung dient. Die Manpage sagt ausdrücklich, dass er keine Sicherheitsgrenze ist, und sagt nichts über Interfaces, Routen oder VPN-Zustand. Alles, was man von einem Linux-Container erwartet — ein eigener Netzwerk-Stack, ein gefiltertes Dateisystem, Ressourcengrenzen — kommt von anderen Mechanismen, die der Launcher genutzt haben mag oder nicht.
Deshalb beantworten Behauptungen, ein chroot isoliere ein VPN, meist die falsche Frage. Die relevante Frage ist, in welchem Namespace der Prozess läuft und in welcher SELinux-Domain er eingeschlossen ist, und beides sind Eigenschaften davon, wie er gestartet wurde, nicht des Wortes chroot.
TUN braucht drei Dinge, und ein Geräteknoten ist nur eines
Ein traditioneller Userspace-Tunnel braucht ein TUN-Gerät, das er öffnen kann, Kernel-Unterstützung für dieses Gerät und die Berechtigung, das Interface und die Routen zu konfigurieren. Das sind getrennte Anforderungen, und das Anlegen eines Pfadnamens unter /dev erfüllt für sich genommen keine davon:
ls -l /dev/net/tun
ip tuntap add dev tun0 mode tun
Unter Android ist TUN-Unterstützung meist in den Kernel einkompiliert, weil der eigene VPN-Dienst der Plattform sie nutzt, aber das heißt nicht, dass der eigene Prozess sie nutzen darf. Die Interface-Konfiguration braucht die Netzwerkverwaltungs-Fähigkeit, und SELinux entscheidet, ob der Prozess den Geräteknoten überhaupt erreichen darf. Eine Verweigerung sieht wie ein Berechtigungsfehler ohne offensichtliche Ursache aus, und die Antwort steht im Audit-Trail:
dmesg | grep avc
logcat -d | grep "avc: denied"
Root löst das nicht. SELinux beschränkt Prozesse mit uid 0 genauso wie andere, also kann einem aus einer Root-Shell gestarteten chroot ein Gerät verweigert werden, das es sieht. Die Durchsetzung abzuschalten, damit ein alter Installer läuft, ist eine Entscheidung für das ganze Telefon mit Folgen weit über den Tunnel hinaus und keine Reparatur für ein veraltetes Binary.
Warum Androids Apps den Tunnel ignorieren
Androids unterstütztes Modell ist eine VPN-App, die sich über den VPN-Dienst der Plattform registriert, den Nutzer einmal um Zustimmung bittet und einen Dateideskriptor für ihren Tunnel erhält. Die Plattform leitet Verkehr in diesen Deskriptor und setzt den App-Bereich durch, den Nutzer oder Administrator gewählt haben, weshalb derselbe Mechanismus pro App, pro Profil und kombiniert mit Immer-an und Blockieren ohne VPN funktionieren kann. Androids Entwicklerleitfaden zu VPN beschreibt diesen Handshake, und der Kernpunkt ist, dass es eine Registrierung ist, kein Routing-Trick.
Ein Daemon in einem chroot registriert sich nie. Es gibt keinen Zustimmungsdialog, kein Schlüsselsymbol in der Statusleiste, keine Einbeziehungs- oder Ausschlussliste pro App und keine Teilnahme an der Immer-an-Richtlinie. Apps nutzen daher weiter das Standardnetz, und der Tunnel trägt nur, was die Shell selbst sendet.
Die andere Richtung hilft ebenso wenig. Routen in den Host-Namespace zu schieben erfasst alles, auch Verkehr, den die Plattform lokal zu halten gedachte, und das ohne Steuerung pro App und ohne Möglichkeit für den Nutzer, es zu sehen oder zu widerrufen. Es gibt keine Zwischeneinstellung, in der ein chroot-Daemon Verkehr für nur eine App routet.
Binärkompatibilität ist eine eigene Mauer
Selbst wenn die Netzwerkseite interessant ist, hat die ausführbare Datei ihre eigenen Anforderungen: CPU-Architektur, dynamischer Loader, C-Bibliothek und die Kernel-Fähigkeiten, die sie erwartet. Android nutzt bionic statt glibc, und ein altes Binary, das für eine Desktop-Distribution gebaut wurde, kann Bibliotheken brauchen, die es auf dem Gerät nicht mehr gibt.
Einen Bibliotheksdateinamen auf eine neuere Version zu verlinken erzeugt keine ABI-Kompatibilität, und eine archivierte Kryptobibliothek aus einem zehn Jahre alten Paket ist keine gepflegte Komponente. Hängt der Plan davon ab, ist der Plan, ungepflegten Code mit Netzwerkrechten auf einem Telefon laufen zu lassen.
Beweise es im Android-Kontext und räume danach auf
Teste, worauf es dir wirklich ankommt, von der Seite, die zählt: Öffne die konkrete App, erreiche das konkrete private Ziel, prüfe, welcher Resolver geantwortet hat, trenne dann den Tunnel und bestätige das dokumentierte Verhalten. Ein Ergebnis aus einem Terminal im chroot ist ein Beleg über den Ausführungskontext dieses Terminals und nichts weiter.
Wenn du beim Experimentieren Host-Routen geändert hast, setze sie zurück und notiere, was du geändert hast. Prüfe dann, ob auf dem Telefon ein Immer-an-VPN eingerichtet ist, denn ein älteres Experiment, das eine Regel hinterlassen hat, kann Monate später wie ein Plattformfehler aussehen.
Für einen echten Fernzugriffsbedarf nutze den unterstützten Client des Dienstes oder ein freigegebenes Gateway-Design. Die Frage, die sich lohnt, ist nicht, ob man einen Root-Installer zum Laufen bringt, sondern ob eine unterstützte Implementierung die benötigten Apps über die beabsichtigte Grenze trägt, ohne herrenlose privilegierte Änderungen auf dem Gerät zu hinterlassen.