From 883b5abc7b2a770146683e7e27bf275bd4064511 Mon Sep 17 00:00:00 2001 From: pacien Date: Mon, 20 Jan 2020 17:07:12 +0100 Subject: pass network device fd via unix socket instead of inheritance Workaround for new shared memory restrictions added in Android 10 preventing file descriptor leakage to sub-processes. This change set BREAKS ENCRYPTED PRIVATE KEYS SUPPORT. GitHub: https://github.com/pacien/tincapp/issues/92 --- .../java/org/pacien/tincapp/commands/Executor.kt | 34 +--------------------- .../main/java/org/pacien/tincapp/commands/Tincd.kt | 15 ++++++---- .../main/java/org/pacien/tincapp/context/App.kt | 10 +++++-- .../org/pacien/tincapp/service/TincVpnService.kt | 34 ++++++++++++++++++---- 4 files changed, 46 insertions(+), 47 deletions(-) (limited to 'app/src/main/java') diff --git a/app/src/main/java/org/pacien/tincapp/commands/Executor.kt b/app/src/main/java/org/pacien/tincapp/commands/Executor.kt index 29e011f..0a8a774 100644 --- a/app/src/main/java/org/pacien/tincapp/commands/Executor.kt +++ b/app/src/main/java/org/pacien/tincapp/commands/Executor.kt @@ -1,6 +1,6 @@ /* * Tinc App, an Android binding and user interface for the tinc mesh VPN daemon - * Copyright (C) 2017-2018 Pacien TRAN-GIRARD + * Copyright (C) 2017-2020 Pacien TRAN-GIRARD * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,42 +30,10 @@ import java.io.InputStreamReader * @author pacien */ internal object Executor { - private const val FAILED = -1 - private const val SUCCESS = 0 - class CommandExecutionException(msg: String) : Exception(msg) - init { - System.loadLibrary("exec") - } - - /** - * @return FAILED (-1) on error, forked child PID otherwise - */ - private external fun forkExec(args: Array): Int - - /** - * @return FAILED (-1) on error, the exit status of the process otherwise - */ - private external fun wait(pid: Int): Int - private fun read(stream: InputStream) = BufferedReader(InputStreamReader(stream)).readLines() - fun forkExec(cmd: Command): CompletableFuture { - val pid = forkExec(cmd.asArray()).also { - if (it == FAILED) throw CommandExecutionException("Could not fork child process.") - } - - return runAsyncTask { - val exitCode = wait(pid) - when (exitCode) { - SUCCESS -> Unit - FAILED -> throw CommandExecutionException("Process terminated abnormally.") - else -> throw CommandExecutionException("Non-zero exit status code ($exitCode).") - } - } - } - fun run(cmd: Command): Process = try { ProcessBuilder(cmd.asList()).start() } catch (e: IOException) { diff --git a/app/src/main/java/org/pacien/tincapp/commands/Tincd.kt b/app/src/main/java/org/pacien/tincapp/commands/Tincd.kt index 92be0f5..c0b0048 100644 --- a/app/src/main/java/org/pacien/tincapp/commands/Tincd.kt +++ b/app/src/main/java/org/pacien/tincapp/commands/Tincd.kt @@ -1,6 +1,6 @@ /* * Tinc App, an Android binding and user interface for the tinc mesh VPN daemon - * Copyright (C) 2017-2018 Pacien TRAN-GIRARD + * Copyright (C) 2017-2020 Pacien TRAN-GIRARD * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,20 +18,23 @@ package org.pacien.tincapp.commands +import java8.util.concurrent.CompletableFuture import org.pacien.tincapp.context.AppPaths +import java.io.File /** * @author pacien */ object Tincd { - fun start(netName: String, deviceFd: Int, ed25519PrivateKeyFd: Int? = null, rsaPrivateKeyFd: Int? = null) = - Executor.forkExec(Command(AppPaths.tincd().absolutePath) + fun start(netName: String, device: String, ed25519PrivateKey: File? = null, rsaPrivateKey: File? = null): CompletableFuture = + Executor.call(Command(AppPaths.tincd().absolutePath) .withOption("no-detach") .withOption("config", AppPaths.confDir(netName).absolutePath) .withOption("pidfile", AppPaths.pidFile(netName).absolutePath) .withOption("logfile", AppPaths.logFile(netName).absolutePath) .withOption("option", "DeviceType=fd") - .withOption("option", "Device=$deviceFd") - .apply { if (ed25519PrivateKeyFd != null) withOption("option", "Ed25519PrivateKeyFile=/proc/self/fd/$ed25519PrivateKeyFd") } - .apply { if (rsaPrivateKeyFd != null) withOption("option", "PrivateKeyFile=/proc/self/fd/$rsaPrivateKeyFd") }) + .withOption("option", "Device=@$device") + .apply { if (ed25519PrivateKey != null) withOption("option", "Ed25519PrivateKeyFile=${ed25519PrivateKey.absolutePath}") } + .apply { if (rsaPrivateKey != null) withOption("option", "PrivateKeyFile=${rsaPrivateKey.absolutePath}") } + ).thenApply { } } diff --git a/app/src/main/java/org/pacien/tincapp/context/App.kt b/app/src/main/java/org/pacien/tincapp/context/App.kt index a877929..4d8d5d0 100644 --- a/app/src/main/java/org/pacien/tincapp/context/App.kt +++ b/app/src/main/java/org/pacien/tincapp/context/App.kt @@ -1,6 +1,6 @@ /* * Tinc App, an Android binding and user interface for the tinc mesh VPN daemon - * Copyright (C) 2017-2019 Pacien TRAN-GIRARD + * Copyright (C) 2017-2020 Pacien TRAN-GIRARD * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +21,7 @@ package org.pacien.tincapp.context import android.app.Application import android.content.Context import android.content.Intent +import android.content.pm.ApplicationInfo import android.net.Uri import android.os.Build import android.os.Handler @@ -47,7 +48,7 @@ class App : Application() { private fun setupCrashHandler() { val logger = LoggerFactory.getLogger(this.javaClass) - val systemCrashHandler = Thread.getDefaultUncaughtExceptionHandler() + val systemCrashHandler = Thread.getDefaultUncaughtExceptionHandler()!! val crashRecorder = CrashRecorder(logger, systemCrashHandler) Thread.setDefaultUncaughtExceptionHandler(crashRecorder) } @@ -61,6 +62,11 @@ class App : Application() { fun getContext() = appContext!! fun getResources() = getContext().resources!! + fun getApplicationInfo(): ApplicationInfo = + getContext() + .packageManager + .getApplicationInfo(BuildConfig.APPLICATION_ID, 0) + fun alert(@StringRes title: Int, msg: String, manualLink: String? = null) = notificationManager.notifyError(appContext!!.getString(title), msg, manualLink) diff --git a/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt b/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt index 40e9004..48cb1df 100644 --- a/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt +++ b/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt @@ -1,6 +1,6 @@ /* * Tinc App, an Android binding and user interface for the tinc mesh VPN daemon - * Copyright (C) 2017-2019 Pacien TRAN-GIRARD + * Copyright (C) 2017-2020 Pacien TRAN-GIRARD * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +21,7 @@ package org.pacien.tincapp.service import android.app.Service import android.content.Context import android.content.Intent +import android.net.LocalServerSocket import android.net.VpnService import android.os.ParcelFileDescriptor import androidx.localbroadcastmanager.content.LocalBroadcastManager @@ -43,6 +44,7 @@ import org.pacien.tincapp.intent.Actions import org.pacien.tincapp.utils.TincKeyring import org.slf4j.LoggerFactory import java.io.FileNotFoundException +import java.security.AccessControlException /** * @author pacien @@ -100,6 +102,7 @@ class TincVpnService : VpnService() { log.info("Starting tinc daemon for network \"$netName\".") if (isConnected() || getCurrentNetName() != null) stopVpn().join() + // FIXME: pass decrypted private keys via temp file val privateKeys = try { TincConfiguration.fromTincConfiguration(AppPaths.existing(AppPaths.tincConfFile(netName))).let { tincCfg -> Pair( @@ -125,13 +128,10 @@ class TincVpnService : VpnService() { } val deviceFd = try { - val appContextFd = Builder().setSession(netName) + Builder().setSession(netName) .applyCfg(interfaceCfg) .also { applyIgnoringException(it::addDisallowedApplication, BuildConfig.APPLICATION_ID) } .establish()!! - val daemonContextFd = appContextFd.dup() // necessary since Android 10 - appContextFd.close() - daemonContextFd } catch (e: IllegalArgumentException) { return reportError(resources.getString(R.string.notification_error_message_network_config_invalid_format, e.defaultMessage()), e, "network-interface") } catch (e: NullPointerException) { @@ -140,10 +140,15 @@ class TincVpnService : VpnService() { return reportError(resources.getString(R.string.notification_error_message_could_not_configure_iface, e.defaultMessage()), e) } - val daemon = Tincd.start(netName, deviceFd.fd, privateKeys.first?.fd, privateKeys.second?.fd) + val serverSocket = LocalServerSocket(DEVICE_FD_ABSTRACT_SOCKET) + Executor.runAsyncTask { serveDeviceFd(serverSocket, deviceFd) } + + // FIXME: pass decrypted private keys via temp file + val daemon = Tincd.start(netName, DEVICE_FD_ABSTRACT_SOCKET, null, null) setState(netName, passphrase, interfaceCfg, deviceFd, daemon) waitForDaemonStartup().whenComplete { _, exception -> + serverSocket.close() deviceFd.close() privateKeys.first?.close() privateKeys.second?.close() @@ -189,6 +194,22 @@ class TincVpnService : VpnService() { LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(event)) } + private fun serveDeviceFd(serverSocket: LocalServerSocket, deviceFd: ParcelFileDescriptor) = + serverSocket.accept().let { socket -> + try { + if (socket.peerCredentials.uid != App.getApplicationInfo().uid) + throw AccessControlException("Peer UID mismatch.") + + socket.setFileDescriptorsForSend(arrayOf(deviceFd.fileDescriptor)) + socket.outputStream.write(0) // dummy write + socket.outputStream.flush() + } catch (e: Exception) { + log.error("Error while serving device fd", e) + } finally { + socket.close() + } + } + private fun waitForDaemonStartup() = Executor .runAsyncTask { Thread.sleep(SETUP_DELAY) } @@ -196,6 +217,7 @@ class TincVpnService : VpnService() { companion object { private const val SETUP_DELAY = 500L // ms + private const val DEVICE_FD_ABSTRACT_SOCKET = "${BuildConfig.APPLICATION_ID}.daemon.socket" private val STORE_NAME = this::class.java.`package`!!.name private const val STORE_KEY_NETNAME = "netname" -- cgit v1.2.3