De app wurke op Android 9 en stjert op deselde telefoan nei de fernijing nei Android 10. Jo profyl is ûnoantaaste, de sertifikaten binne itselde, en de crash komt foar alle autentikaasjeferkear. Logcat lit earst in nulwizer sjen, en in pear rigels dêrboppe stiet Cannot run program ...: error=13, Permission denied. Dy melding giet oer it útfieren fan in triem, net oer it lêzen fan jo konfiguraasje.

Wat flater 13 eins wegeret

Android 10, API-nivo 29, stoppe apps derfan om triemmen út te fieren dy’t se yn har eigen datamap skreaunen. De regel rjochtet him op opslach dy’t earst skriuwber en dan útfiereber is, en dat is krekt wat in starter dy’t út assets kopiearret makket. Aldere bouwen fan de ics-openvpn-kearn pakke by de earste start in útfiereber triem openvpn út de APK en starte dy mei ProcessBuilder. It útpakken slagget noch altyd; it útfieren is wat wegere wurdt. In app dy’t op API 29 rjochtet, erft de regel sels as er draait op in apparaat dat mei in âldere ferzje kaam, en dêrom kin in fernijing in bouw brekke dy’t nimmen oanrekke hat.

Neat oan it wegerjen is spesifyk foar ien mapnamme. In triem dy’t de app ûnder syn datamap makke hat, wurdt as net-útfiereber behannele itsij it paad, dus getFilesDir(), getCacheDir() en in eigen submap mislearje allegear itselde.

Skied de helper-crash fan in profylprobleem

Lês it log om de prosesstart hinne ynstee fan fan boppen te rôljen. De sinjatuer dy’t jo sykje moatte is de rigel error=13, en de stacktrace dêrboppe neamt meast de startermetoade dy’t ProcessBuilder oanrôp:

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

As de helper nea start is, sjogge jo hielendal gjin OpenVPN-logútfier: gjin sifersûnderhanneling, gjin trochstutsen rûtes, gjin DNS-konfiguraasje. As dy rigels wol ferskine en de flater komt letter, dan draaide de binary en sit it probleem yn it profyl, de tsjinner of de routinglaach. Dat iene ûnderskie sparret in soad tiid dy’t oars nei it bewurkjen fan .ovpn-triemmen gien wie dy’t nea skuldich wiene. It fertelt jo ek hokker fan de twa oplossings hjirûnder jo echt nedich hawwe.

Kontrolearje ek it API-nivo op it apparaat dat jo testen, net dat yn jo bouwkonfiguraasje. In emulatorbyld op API 28 reprodusearret neat, om’t de beheining pas fan 29 ôf byt. Kontrolearje tagelyk it doel-SDK yn jo bouwtriem: it ferheegjen dêrfan is wat de regel derby hellet.

adb shell getprop ro.build.version.sdk

Wêrom it kopiearjen fan de binary earne oars net helpt

De foar de hân lizzende omwei, it skriuwen fan de helper nei in oare skriuwbere map, mislearret om deselde reden as it orizjineel. Android tastiet noch altyd it útfieren fan dielde bibleteken dy’t yn ’e native bibleteekmap fan de app ynstallearre binne, om’t de ynstallearder se ferpakt ynstee fan se by runtime te skriuwen. Dy map is it iene plak dêr’t de starter nei wize moat. Alles wat jo sels by runtime útpakke, hoe jo it ek neame, bliuwt yn ’e wegere kategory.

Eksterne opslach is slimmer: it is mei noexec oanhongen, en in helper dy’t dêr set wurdt kin troch gjin inkele app start wurde. It wiskjen fan appgegevens makket it âlde gedrach ek net wer, om’t de folgjende start gewoan deselde net-útfierebere kopy opnij makket.

Rjochtsje de starter op in ferpakte bibleteek

Neam de helper om ta in namme fan in dielde bibleteek en leverje him ûnder jniLibs foar elke ABI dy’t jo stypje, en lit de starter dan fan API 28 ôf it paad fan ’e ynstallearre bibleteek weromjaan:

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

De bibleteek moat by ynstallaasje útpakt wurde om dat paad in echte triem befetsje te litten, dus set android:extractNativeLibs="true" op it <application>-elemint. As in manifest-foegjen in oare wearde derby hellet, foegje tools:replace="android:extractNativeLibs" dêr njonken ta. De referinsje foar it application-elemint dokumintearret wat it attribút regelet.

Pas op foar in dûbele lokale fariabele as jo de starter mei de hân patchje. Aldere boarnen ferklearje nativeAPI letter yn deselde metoade, en in twadde ferklearring is in kompilaasjeflater ynstee fan in runtime-flater, dus de bouw mislearret fluch en de flater is maklik te sjen. Hâld de ABI-list yn it Gradle-blok gelyk mei de mappen dy’t jo tafoegje.

Lit it release-artefakt by de testbouw passe

In debug-APK kin de tunnel opsette wylst in release-bondel noch crasht, om’t in bondel native bibleteken yn in oare foarm ferpakt en se komprimearre út it argyf tsjinje kin. As allinnich bondel-ôflate bouwen mislearje, foegje dit ta oan gradle.properties en bou opnij:

android.bundle.enableUncompressedNativeLibs=false

Befêstigje dan dat it ynstallearre pakket de ABI rapportearret dy’t jo ferwachtsje, en werhelje de kontrôle op in 32-bit- en in 64-bit-apparaat as jo beide leverje. De pakketbehearder neamt de ABI dy’t er keas, dus in apparaat dat weromfoel nei 32 bit ferskynt hjir ynstee fan yn in crashrapport:

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

In ûntbrekkende bibleteek foar ien ABI reprodusearret de oarspronklike tagongsflater allinnich op dat apparaat, en dêrom is ien suksesfolle testtelefoan gjin bewiis. Leverje de manifestflagge, de bibleteeknamme en de bondelynstelling tegearre; ien feroarje en de oaren ferjitte bringt jo wer by deselde crash. De duorjende oplossing is oerstappe nei in ûnderholden kearnrelease ynstee fan de patch foar ivich mei te dragen.

Relatearre artikelen