El binari auxiliar d’OpenVPN falla a Android 10: permís denegat
Android 10 es nega a executar un auxiliar que l’aplicació ha escrit al seu propi emmagatzematge. Llegeix logcat, distribueix l’auxiliar com a biblioteca nativa i arregla les compilacions de bundle.
L’aplicació funcionava a Android 9 i mor al mateix telèfon després de l’actualització a Android 10. El teu perfil està intacte, els certificats són els mateixos i l’error arriba abans de qualsevol trànsit d’autenticació. Logcat mostra primer un punter nul, i unes línies més amunt hi ha Cannot run program ...: error=13, Permission denied. Aquest missatge tracta d’executar un fitxer, no de llegir la teva configuració.
Què és el que realment rebutja l’error 13
Android 10, nivell d’API 29, va impedir que les aplicacions executessin fitxers que escrivien al seu propi directori de dades. La regla apunta a l’emmagatzematge que primer és escribible i després executable, que és exactament el que crea un llançador que copia des d’assets. Les compilacions antigues del nucli ics-openvpn desempaqueten un executable openvpn de l’APK a la primera execució i el inicien amb ProcessBuilder. El desempaquetat encara té èxit; el que es rebutja és l’execució. Una aplicació que apunta a l’API 29 hereta la regla fins i tot quan s’executa en un dispositiu que va sortir amb una versió anterior, i per això una actualització pot trencar una compilació que ningú no ha tocat.
Res del rebuig no és específic d’un nom de carpeta. Un fitxer que l’aplicació ha creat sota el seu directori de dades es tracta com a no executable sigui quin sigui el camí, així que getFilesDir(), getCacheDir() i un subdirectori personalitzat fallen tots de la mateixa manera.
Separa l’error de l’auxiliar d’un problema de perfil
Llegeix el registre al voltant de l’inici del procés en lloc de desplaçar-te des de dalt. La signatura que cal cercar és la línia error=13, i la traça de pila que hi ha a sobre sol nomenar el mètode del llançador que va cridar ProcessBuilder:
adb logcat -d | grep -n -i -E "cannot run program|permission denied|startOpenVPN"Si l’auxiliar no s’ha iniciat mai, no veuràs cap sortida del registre d’OpenVPN: ni negociació de xifratges, ni rutes empenyudes, ni configuració de DNS. Si aquestes línies sí que apareixen i l’avaria arriba més tard, el binari s’ha executat i el problema és al perfil, al servidor o a la capa d’encaminament. Aquesta única distinció estalvia molt de temps que es perdria editant fitxers .ovpn que mai no en tenien la culpa. També t’indica quina de les dues solucions de sota necessites realment.
Comprova també el nivell d’API del dispositiu que proves, no el de la teva configuració de compilació. Una imatge d’emulador a l’API 28 no reprodueix res, perquè la restricció només mossega a partir del 29. Comprova alhora l’SDK objectiu del fitxer de compilació: apujar-lo és el que arrossega la regla.
adb shell getprop ro.build.version.sdkPer què copiar el binari a un altre lloc no ajuda
La solució òbvia, escriure l’auxiliar en un altre directori escribible, falla pel mateix motiu pel qual va fallar l’original. Android encara permet executar biblioteques compartides instal·lades al directori de biblioteques natives de l’aplicació, perquè les empaqueta l’instal·lador en lloc d’escriure’s en temps d’execució. Aquest directori és l’únic lloc al qual el llançador hauria d’apuntar. Qualsevol cosa que desempaquetes tu mateix en temps d’execució, digues com digues, es queda a la categoria rebutjada.
L’emmagatzematge extern és pitjor: està muntat amb noexec, i cap aplicació no pot iniciar un auxiliar col·locat allà. Esborrar les dades de l’aplicació tampoc no restaura el comportament antic, perquè la propera execució simplement torna a crear la mateixa còpia no executable.
Apunta el llançador a una biblioteca empaquetada
Canvia el nom de l’auxiliar a un nom de biblioteca compartida i distribueix-lo sota jniLibs per a cada ABI que admetis, i fes que el llançador retorni el camí de la biblioteca instal·lada a partir de l’API 28:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return new File(context.getApplicationInfo().nativeLibraryDir,
"libovpnexec.so").getPath();
}La biblioteca s’ha de desempaquetar en el moment d’instal·lar perquè aquest camí contingui un fitxer real, així que defineix android:extractNativeLibs="true" a l’element <application>. Si un fusionador de manifest hi introdueix un valor diferent, afegeix tools:replace="android:extractNativeLibs" al costat. La referència de l’element application documenta què controla l’atribut.
Vigila una variable local duplicada quan pedaçes el llançador a mà. Les fonts antigues declaren nativeAPI més endavant al mateix mètode, i una segona declaració és un error de compilació, no d’execució, així que la compilació falla de seguida i l’error és fàcil de veure. Mantén la llista d’ABI del bloc de Gradle sincronitzada amb les carpetes que afegeixes.
Fes que l’artefacte de versió coincideixi amb la compilació de prova
Un APK de depuració pot aixecar el túnel mentre un bundle de versió encara peta, perquè un bundle empaqueta les biblioteques natives en una forma diferent i les pot servir comprimides des de l’arxiu. Si només fallen les compilacions derivades del bundle, afegeix això a gradle.properties i torna a compilar:
android.bundle.enableUncompressedNativeLibs=falseDesprés confirma que el paquet instal·lat informa de l’ABI que esperes, i repeteix la comprovació en un dispositiu de 32 bits i un de 64 bits si distribueixes tots dos. El gestor de paquets anomena l’ABI que ha triat, així que un dispositiu que ha retrocedit a 32 bits apareix aquí en lloc d’en un informe d’avaria:
adb shell dumpsys package com.example.openvpn | grep -i primaryCpuAbiUna biblioteca que falta per a un ABI reprodueix l’error de permís original només en aquell dispositiu, i per això un sol telèfon de prova que funcioni no és una prova. Distribueix la marca del manifest, el nom de la biblioteca i el paràmetre del bundle junts; canviar-ne un i oblidar els altres et torna al mateix error. La solució duradora és passar a una versió del nucli mantinguda en lloc d’arrossegar el pedaç per sempre.