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
---
app/src/main/c/exec.c | 60 ----------------------
app/src/main/c/main.c | 1 +
.../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 +++++++++---
.../main/play/listings/en-US/full-description.txt | 2 +-
7 files changed, 48 insertions(+), 108 deletions(-)
delete mode 100644 app/src/main/c/exec.c
create mode 100644 app/src/main/c/main.c
(limited to 'app/src')
diff --git a/app/src/main/c/exec.c b/app/src/main/c/exec.c
deleted file mode 100644
index c335b20..0000000
--- a/app/src/main/c/exec.c
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Tinc App, an Android binding and user interface for the tinc mesh VPN daemon
- * Copyright (C) 2017-2018 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
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#include
-#include
-#include
-#include
-
-static inline const char **to_string_array(JNIEnv *env, jobjectArray ja) {
- const int len = (*env)->GetArrayLength(env, ja);
- const char **ca = calloc((size_t) len + 1, sizeof(char *));
-
- for (int i = 0; i < len; ++i) {
- jstring jstr = (jstring) (*env)->GetObjectArrayElement(env, ja, i);
- ca[i] = (*env)->GetStringUTFChars(env, jstr, NULL);
- }
-
- ca[len] = NULL;
- return ca;
-}
-
-static inline void exec(const char **argcv) {
- execv(argcv[0], (char *const *) argcv);
- exit(1);
-}
-
-JNIEXPORT jint JNICALL
-Java_org_pacien_tincapp_commands_Executor_forkExec(JNIEnv *env, __attribute__((unused)) jclass class, jobjectArray args) {
- pid_t pid = fork();
- switch (pid) {
- case 0:
- exec(to_string_array(env, args));
- return 0;
-
- default:
- return pid;
- }
-}
-
-JNIEXPORT jint JNICALL
-Java_org_pacien_tincapp_commands_Executor_wait(__attribute__((unused))JNIEnv *env, __attribute__((unused)) jclass class, jint pid) {
- int status;
- waitpid(pid, &status, 0);
- return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
-}
diff --git a/app/src/main/c/main.c b/app/src/main/c/main.c
new file mode 100644
index 0000000..68007d8
--- /dev/null
+++ b/app/src/main/c/main.c
@@ -0,0 +1 @@
+// This file intentionally left blank.
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"
diff --git a/app/src/main/play/listings/en-US/full-description.txt b/app/src/main/play/listings/en-US/full-description.txt
index 45fee19..b25756d 100644
--- a/app/src/main/play/listings/en-US/full-description.txt
+++ b/app/src/main/play/listings/en-US/full-description.txt
@@ -22,7 +22,7 @@ Please see the project's website for more information (setup guide, documentatio
---
-Copyright © 2017-2019 Pacien TRAN-GIRARD and contributors.
+Copyright © 2017-2020 Pacien TRAN-GIRARD and contributors.
This program is licensed under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License or any later version.
It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details: https://www.gnu.org/licenses/
--
cgit v1.2.3