Binarka pomocnicza OpenVPN zawodzi na Androidzie 10: odmowa dostępu
Android 10 odmawia uruchomienia pomocnika zapisanego przez aplikację we własnej pamięci. Czytaj logcat, dostarczaj pomocnika jako bibliotekę natywną i napraw kompilacje bundle.
Aplikacja działała na Androidzie 9 i pada na tym samym telefonie po aktualizacji do Androida 10. Twój profil jest nietknięty, certyfikaty są te same, a awaria przychodzi przed jakimkolwiek ruchem uwierzytelniania. Logcat najpierw pokazuje wskaźnik null, a kilka linii wyżej stoi Cannot run program ...: error=13, Permission denied. Ten komunikat dotyczy uruchamiania pliku, a nie czytania twojej konfiguracji.
Czego naprawdę odmawia błąd 13
Android 10, poziom API 29, uniemożliwił aplikacjom uruchamianie plików, które zapisywały we własnym katalogu danych. Reguła dotyczy pamięci najpierw zapisywalnej, a potem wykonywalnej, i dokładnie to tworzy launcher kopiujący z assets. Starsze kompilacje rdzenia ics-openvpn rozpakowują przy pierwszym uruchomieniu z APK plik wykonywalny openvpn i startują go przez ProcessBuilder. Rozpakowanie nadal się udaje; odrzucane jest wykonanie. Aplikacja celująca w API 29 dziedziczy regułę nawet wtedy, gdy działa na urządzeniu wydanym ze starszą wersją, dlatego aktualizacja może zepsuć kompilację, której nikt nie dotknął.
Nic w odmowie nie jest specyficzne dla jednej nazwy katalogu. Plik utworzony przez aplikację w jej katalogu danych jest traktowany jako niewykonywalny niezależnie od ścieżki, więc getFilesDir(), getCacheDir() i własny podkatalog zawodzą wszystkie w ten sam sposób.
Oddziel awarię pomocnika od problemu z profilem
Czytaj log wokół startu procesu, zamiast przewijać od góry. Sygnaturą, której szukasz, jest linia error=13, a ślad stosu nad nią zwykle nazywa metodę launchera, która wywołała ProcessBuilder:
adb logcat -d | grep -n -i -E "cannot run program|permission denied|startOpenVPN"Jeśli pomocnik nigdy nie wystartował, nie zobaczysz żadnego wyjścia logu OpenVPN: ani negocjacji szyfrów, ani wypchniętych tras, ani konfiguracji DNS. Jeśli te linie jednak się pojawią, a awaria przyjdzie później, binarka działała i problem leży w profilu, serwerze lub warstwie routingu. Ta jedna różnica oszczędza dużo czasu, który inaczej poszedłby na edytowanie plików .ovpn, które nigdy nie były winne. Mówi ci też, której z dwóch poprawek poniżej naprawdę potrzebujesz.
Sprawdź także poziom API na urządzeniu, które testujesz, a nie ten w swojej konfiguracji kompilacji. Obraz emulatora z API 28 niczego nie odtwarza, bo ograniczenie gryzie dopiero od 29 wzwyż. Jednocześnie sprawdź docelowy SDK w pliku kompilacji: to jego podniesienie wciąga tę regułę.
adb shell getprop ro.build.version.sdkDlaczego kopiowanie binarki gdzie indziej nie pomaga
Oczywiste obejście, zapisanie pomocnika w innym zapisywalnym katalogu, zawodzi z tego samego powodu co oryginał. Android nadal pozwala uruchamiać biblioteki współdzielone zainstalowane w katalogu bibliotek natywnych aplikacji, bo pakuje je instalator, a nie są zapisywane w czasie działania. Ten katalog to jedyne miejsce, na które launcher powinien wskazywać. Wszystko, co sam rozpakujesz w czasie działania, jakkolwiek to nazwiesz, zostaje w odrzuconej kategorii.
Pamięć zewnętrzna jest gorsza: jest zamontowana z noexec, a pomocnika tam umieszczonego nie uruchomi żadna aplikacja. Czyszczenie danych aplikacji też nie przywraca starego zachowania, bo następne uruchomienie po prostu tworzy tę samą niewykonywalną kopię.
Skieruj launcher na zapakowaną bibliotekę
Zmień nazwę pomocnika na nazwę biblioteki współdzielonej i dostarcz ją pod jniLibs dla każdego obsługiwanego ABI, a potem pozwól launcherowi zwracać ścieżkę zainstalowanej biblioteki od API 28 wzwyż:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return new File(context.getApplicationInfo().nativeLibraryDir,
"libovpnexec.so").getPath();
}Biblioteka musi być rozpakowana przy instalacji, żeby ta ścieżka zawierała prawdziwy plik, więc ustaw android:extractNativeLibs="true" na elemencie <application>. Jeśli łącznik manifestu wniesie inną wartość, dodaj tools:replace="android:extractNativeLibs" obok. dokumentacja elementu application opisuje, czym steruje ten atrybut.
Uważaj na zduplikowaną zmienną lokalną, gdy łatasz launcher ręcznie. Starsze źródła deklarują nativeAPI później w tej samej metodzie, a druga deklaracja to błąd kompilacji, nie działania, więc kompilacja szybko zawodzi i błąd łatwo zauważyć. Utrzymuj listę ABI w bloku Gradle w zgodzie z katalogami, które dodajesz.
Dopasuj artefakt wydania do kompilacji testowej
Debugowe APK potrafi podnieść tunel, podczas gdy bundle wydania wciąż się wywala, bo bundle pakuje biblioteki natywne w innej formie i może je serwować skompresowane z archiwum. Jeśli zawodzą tylko kompilacje pochodzące z bundle, dodaj to do gradle.properties i zbuduj ponownie:
android.bundle.enableUncompressedNativeLibs=falsePotem potwierdź, że zainstalowany pakiet zgłasza oczekiwane ABI, i powtórz sprawdzenie na urządzeniu 32- i 64-bitowym, jeśli dostarczasz oba. Menedżer pakietów podaje wybrane ABI, więc urządzenie, które spadło do 32 bitów, pojawia się tutaj, a nie w raporcie awarii:
adb shell dumpsys package com.example.openvpn | grep -i primaryCpuAbiBrakująca biblioteka dla jednego ABI odtwarza pierwotny błąd uprawnień tylko na tym urządzeniu, dlatego jeden udany telefon testowy nie jest dowodem. Dostarczaj flagę manifestu, nazwę biblioteki i ustawienie bundle razem; zmiana jednego i zapomnienie pozostałych wraca do tej samej awarii. Trwałym rozwiązaniem jest przejście na utrzymywane wydanie rdzenia zamiast noszenia łaty na zawsze.