Appen fungerade på Android 9 och dör på samma telefon efter uppdateringen till Android 10. Din profil är orörd, certifikaten är desamma, och kraschen kommer före all autentiseringstrafik. Logcat visar först en nollpekare, och några rader ovanför står Cannot run program ...: error=13, Permission denied. Meddelandet handlar om att köra en fil, inte om att läsa din konfiguration.

Vad fel 13 egentligen vägrar

Android 10, API-nivå 29, stoppade appar från att köra filer de hade skrivit i sin egen datamapp. Regeln riktar sig mot lagring som först är skrivbar och sedan körbar, och det är precis vad en startare som kopierar ut ur assets skapar. Äldre byggen av ics-openvpn-kärnan packar upp en körbar fil openvpn ur APK:n vid första körningen och startar den med ProcessBuilder. Uppackningen lyckas fortfarande; det är körningen som nekas. En app som riktar sig mot API 29 ärver regeln även när den körs på en enhet som kom med en äldre version, och därför kan en uppdatering bryta ett bygge som ingen har rört.

Inget i nekandet är specifikt för ett enda mappnamn. En fil som appen har skapat under sin datamapp behandlas som icke körbar oavsett sökväg, så getFilesDir(), getCacheDir() och en egen undermapp misslyckas alla på samma sätt.

Skilj hjälparens krasch från ett profilproblem

Läs loggen kring processens start i stället för att rulla från toppen. Signaturen du letar efter är raden error=13, och stackspåret ovanför namnger oftast den startarmetod som anropade ProcessBuilder:

adb logcat -d | grep -n -i -E "cannot run program|permission denied|startOpenVPN"

Om hjälparen aldrig startade ser du ingen OpenVPN-loggutskrift alls: ingen chifferförhandling, inga pushade rutter, ingen DNS-konfiguration. Om de raderna ändå dyker upp och felet kommer senare körde binären, och problemet ligger i profilen, servern eller routinglagret. Den enda skillnaden sparar mycket tid som annars hade gått åt till att redigera filer .ovpn som aldrig var skyldiga. Den säger också vilken av de två rättelserna nedan du faktiskt behöver.

Kontrollera också API-nivån på enheten du testar, inte den i din byggkonfiguration. En emulatorbild på API 28 återskapar ingenting, eftersom begränsningen biter först från 29 och uppåt. Kontrollera samtidigt mål-SDK:n i din byggfil: det är höjningen av den som drar in regeln.

adb shell getprop ro.build.version.sdk

Varför det inte hjälper att kopiera binären någon annanstans

Den uppenbara omvägen, att skriva hjälparen till en annan skrivbar mapp, misslyckas av samma skäl som originalet. Android tillåter fortfarande att delade bibliotek som installerats i appens inbyggda biblioteksmapp körs, eftersom installationsprogrammet paketerar dem i stället för att de skrivs vid körning. Den mappen är den enda plats startaren borde peka på. Allt du själv packar upp vid körning, vad du än kallar det, stannar i den nekade kategorin.

Extern lagring är värre: den är monterad med noexec, och en hjälpare som ligger där kan inte startas av någon app. Att rensa appdata återställer inte heller det gamla beteendet, eftersom nästa körning helt enkelt skapar samma icke körbara kopia igen.

Peka startaren mot ett paketerat bibliotek

Byt namn på hjälparen till ett namn för delat bibliotek och leverera den under jniLibs för varje ABI du stöder, och låt sedan startaren returnera sökvägen till det installerade biblioteket från API 28 och framåt:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    return new File(context.getApplicationInfo().nativeLibraryDir,
                    "libovpnexec.so").getPath();
}

Biblioteket måste packas upp vid installationen för att den sökvägen ska innehålla en riktig fil, så ställ in android:extractNativeLibs="true" på elementet <application>. Om en manifest-sammanslagning drar in ett annat värde, lägg till tools:replace="android:extractNativeLibs" bredvid. referensen för application-elementet dokumenterar vad attributet styr.

Se upp för en dubblerad lokal variabel när du patchar startaren för hand. Äldre källor deklarerar nativeAPI senare i samma metod, och en andra deklaration är ett kompileringsfel och inte ett körtidsfel, så bygget misslyckas snabbt och felet är lätt att se. Håll ABI-listan i Gradle-blocket i takt med mapparna du lägger till.

Få utgivningsartefakten att matcha testbygget

En debug-APK kan resa tunneln medan en utgivnings-bundle fortfarande kraschar, eftersom en bundle paketerar inbyggda bibliotek i en annan form och kan leverera dem komprimerade från arkivet. Om bara bundle-härledda byggen misslyckas, lägg till detta i gradle.properties och bygg om:

android.bundle.enableUncompressedNativeLibs=false

Bekräfta sedan att det installerade paketet rapporterar den ABI du förväntar dig, och upprepa kontrollen på en 32-bitars och en 64-bitars enhet om du levererar båda. Pakethanteraren namnger den ABI den valde, så en enhet som föll tillbaka till 32 bitar dyker upp här i stället för i en kraschrapport:

adb shell dumpsys package com.example.openvpn | grep -i primaryCpuAbi

Ett saknat bibliotek för en ABI återskapar det ursprungliga åtkomstfelet bara på den enheten, och därför är en enda lyckad testtelefon inget bevis. Leverera manifestflaggan, biblioteksnamnet och bundle-inställningen tillsammans; ändrar du en och glömmer de andra är du tillbaka vid samma krasch. Den varaktiga lösningen är att gå över till en underhållen kärnutgåva i stället för att bära patchen för evigt.

Relaterade artiklar