O binário auxiliar do OpenVPN falha no Android 10: permissão negada
O Android 10 recusa-se a executar um auxiliar que a aplicação escreveu no próprio armazenamento. Leia o logcat, entregue o auxiliar como biblioteca nativa e corrija as compilações de bundle.
A aplicação funcionava no Android 9 e morre no mesmo telefone depois da atualização para Android 10. O seu perfil está intacto, os certificados são os mesmos e a falha chega antes de qualquer tráfego de autenticação. O logcat mostra primeiro um ponteiro nulo, e algumas linhas acima está Cannot run program ...: error=13, Permission denied. Essa mensagem é sobre executar um ficheiro, não sobre ler a sua configuração.
O que o erro 13 recusa realmente
O Android 10, nível de API 29, impediu as aplicações de executar ficheiros que escreviam no seu próprio diretório de dados. A regra visa armazenamento primeiro gravável e depois executável, e é exatamente isso que cria um launcher que copia dos assets. Compilações antigas do núcleo ics-openvpn extraem um executável openvpn do APK no primeiro arranque e iniciam-no com ProcessBuilder. A extração continua a funcionar; o que é recusado é a execução. Uma aplicação que aponta à API 29 herda a regra mesmo quando corre num dispositivo que saiu com uma versão anterior, e é por isso que uma atualização pode quebrar uma compilação que ninguém tocou.
Nada na recusa é específico de um nome de pasta. Um ficheiro que a aplicação criou sob o seu diretório de dados é tratado como não executável seja qual for o caminho, por isso getFilesDir(), getCacheDir() e um subdiretório próprio falham todos da mesma forma.
Separe a falha do auxiliar de um problema de perfil
Leia o registo em torno do arranque do processo em vez de percorrer desde o topo. A assinatura a procurar é a linha error=13, e a pilha de chamadas acima nomeia normalmente o método do launcher que chamou ProcessBuilder:
adb logcat -d | grep -n -i -E "cannot run program|permission denied|startOpenVPN"Se o auxiliar nunca arrancou, não verá qualquer saída de registo do OpenVPN: nem negociação de cifra, nem rotas empurradas, nem configuração de DNS. Se essas linhas aparecerem e a falha vier mais tarde, o binário correu e o problema está no perfil, no servidor ou na camada de encaminhamento. Essa única distinção poupa muito tempo que de outra forma iria para editar ficheiros .ovpn que nunca tiveram culpa. Diz-lhe também qual das duas correções abaixo precisa realmente.
Verifique também o nível de API no dispositivo que está a testar, não o da sua configuração de compilação. Uma imagem de emulador na API 28 não reproduz nada, porque a restrição só morde a partir da 29. Verifique ao mesmo tempo o SDK alvo no seu ficheiro de compilação: é o aumento dele que traz a regra.
adb shell getprop ro.build.version.sdkPorque copiar o binário para outro sítio não ajuda
A solução óbvia, escrever o auxiliar noutro diretório gravável, falha pelo mesmo motivo que o original falhou. O Android continua a permitir executar bibliotecas partilhadas instaladas no diretório de bibliotecas nativas da aplicação, porque é o instalador que as empacota e não são escritas em tempo de execução. Esse diretório é o único sítio para onde o launcher deve apontar. Tudo o que descompactar sozinho em tempo de execução, chame-lhe o que chamar, fica na categoria recusada.
O armazenamento externo é pior: está montado com noexec, e um auxiliar colocado lá não pode ser iniciado por nenhuma aplicação. Apagar os dados da aplicação também não repõe o comportamento antigo, porque o arranque seguinte recria simplesmente a mesma cópia não executável.
Aponte o launcher para uma biblioteca empacotada
Renomeie o auxiliar para um nome de biblioteca partilhada e entregue-o sob jniLibs para cada ABI que suporta, e depois faça o launcher devolver o caminho da biblioteca instalada a partir da API 28:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return new File(context.getApplicationInfo().nativeLibraryDir,
"libovpnexec.so").getPath();
}A biblioteca tem de ser extraída na instalação para que esse caminho contenha um ficheiro real, por isso defina android:extractNativeLibs="true" no elemento <application>. Se um merge de manifesto trouxer outro valor, acrescente tools:replace="android:extractNativeLibs" ao lado. A referência do elemento application documenta o que o atributo controla.
Atenção a uma variável local duplicada quando corrige o launcher à mão. Fontes antigas declaram nativeAPI mais adiante no mesmo método, e uma segunda declaração é um erro de compilação e não de execução, por isso a compilação falha depressa e o erro é fácil de ver. Mantenha a lista de ABI no bloco Gradle sincronizada com as pastas que acrescenta.
Faça o artefacto de lançamento corresponder à compilação de teste
Um APK de depuração consegue levantar o túnel enquanto um bundle de lançamento ainda rebenta, porque um bundle empacota as bibliotecas nativas de outra forma e pode servi-las comprimidas a partir do arquivo. Se apenas as compilações derivadas do bundle falharem, acrescente isto a gradle.properties e volte a compilar:
android.bundle.enableUncompressedNativeLibs=falseDepois confirme que o pacote instalado reporta a ABI que espera, e repita a verificação num dispositivo de 32 bits e num de 64 bits se distribuir ambos. O gestor de pacotes indica a ABI que escolheu, por isso um dispositivo que recuou para 32 bits aparece aqui em vez de num relatório de falha:
adb shell dumpsys package com.example.openvpn | grep -i primaryCpuAbiUma biblioteca em falta para uma ABI reproduz o erro de permissão original apenas nesse dispositivo, e é por isso que um único telefone de teste a funcionar não é prova. Entregue a flag do manifesto, o nome da biblioteca e a definição do bundle em conjunto; mudar uma e esquecer as outras volta a pô-lo na mesma falha. A correção duradoura é passar para uma versão do núcleo mantida em vez de carregar o patch para sempre.