summaryrefslogtreecommitdiffhomepage
path: root/app/src/main/java
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2017-12-16 06:17:27 +0100
committerJason A. Donenfeld <Jason@zx2c4.com>2017-12-16 07:07:05 +0100
commitb7a6b44ec1df88ac83955d2ae7a01a7b705b9550 (patch)
tree64ab58469228267e0b5d9f6b67f2f6a2dce71a54 /app/src/main/java
parent6d1117a94c80f93689f5f42cc239820a024240c5 (diff)
RootShell: rewrite
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/com/wireguard/android/backends/RootShell.java88
-rw-r--r--app/src/main/java/com/wireguard/android/backends/VpnService.java6
2 files changed, 53 insertions, 41 deletions
diff --git a/app/src/main/java/com/wireguard/android/backends/RootShell.java b/app/src/main/java/com/wireguard/android/backends/RootShell.java
index 31b10f98..44b9dd8c 100644
--- a/app/src/main/java/com/wireguard/android/backends/RootShell.java
+++ b/app/src/main/java/com/wireguard/android/backends/RootShell.java
@@ -1,13 +1,14 @@
package com.wireguard.android.backends;
import android.content.Context;
+import android.system.OsConstants;
import android.util.Log;
import java.io.BufferedReader;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.regex.Pattern;
@@ -22,67 +23,76 @@ class RootShell {
* Setup commands that are run at the beginning of each root shell. The trap command ensures
* access to the return value of the last command, since su itself always exits with 0.
*/
- private static final String SETUP_TEMPLATE = "export PATH=\"%s/bin:$PATH\"; export TMPDIR=\"%s/temp\"; trap 'echo $?' EXIT; mkdir -p \"%s/bin\" \"%s/temp\"; ln -fs \"%s/libwg.so\" \"%s/bin/wg\" || exit 99; ln -fs \"%s/libwg-quick.so\" \"%s/bin/wg-quick\" || exit 99;";
- private static final String TAG = "RootShell";
+ private static final String TAG = "WireGuard/RootShell";
private static final Pattern ERRNO_EXTRACTOR = Pattern.compile("error=(\\d+)");
- private final byte[] setupCommands;
- private final String shell;
+ private static final String[][] libraryNamedExecutables = {
+ { "libwg.so", "wg" },
+ { "libwg-quick.so", "wg-quick" }
+ };
+
+ private final String preamble;
RootShell(final Context context) {
- this(context, "su");
- }
+ final String binDir = context.getCacheDir().getPath() + "/bin";
+ final String tmpDir = context.getCacheDir().getPath() + "/tmp";
+
+ new File(binDir).mkdirs();
+ new File(tmpDir).mkdirs();
+
+ preamble = String.format("export PATH=\"%s:$PATH\" TMPDIR=\"%s\";", binDir, tmpDir);
- RootShell(final Context context, final String shell) {
- final String tmpdir = context.getCacheDir().getPath();
- final String fakelibdir = context.getApplicationInfo().nativeLibraryDir;
- setupCommands = String.format(SETUP_TEMPLATE, tmpdir, tmpdir, tmpdir, tmpdir, fakelibdir, tmpdir, fakelibdir, tmpdir).getBytes(StandardCharsets.UTF_8);
- this.shell = shell;
+ final String libDir = context.getApplicationInfo().nativeLibraryDir;
+ String symlinkCommand = "set -ex;";
+ for (final String[] libraryNamedExecutable : libraryNamedExecutables) {
+ final String args = "'" + libDir + "/" + libraryNamedExecutable[0] + "' '" + binDir + "/" + libraryNamedExecutable[1] + "'";
+ symlinkCommand += "ln -f " + args + " || ln -sf " + args + ";";
+ }
+ if (run(null, symlinkCommand) != 0)
+ Log.e(TAG, "Unable to establish symlinks for important executables.");
}
/**
- * Run a series of commands in a root shell. These commands are all sent to the same shell
- * process, so they can be considered a shell script.
+ * Run a command in a root shell.
*
- * @param output Lines read from stdout and stderr are appended to this list. Pass null if the
+ * @param output Lines read from stdout are appended to this list. Pass null if the
* output from the shell is not important.
- * @param commands One or more commands to run as root (each element is a separate line).
+ * @param command Command to run as root.
* @return The exit value of the last command run, or -1 if there was an internal error.
*/
- int run(final List<String> output, final String... commands) {
- if (commands.length < 1)
- throw new IndexOutOfBoundsException("At least one command must be supplied");
+ int run(final List<String> output, final String command) {
int exitValue = -1;
try {
- final ProcessBuilder builder = new ProcessBuilder().redirectErrorStream(true);
- final Process process = builder.command(shell).start();
- final OutputStream stdin = process.getOutputStream();
- stdin.write(setupCommands);
- for (final String command : commands)
- stdin.write(command.concat("\n").getBytes(StandardCharsets.UTF_8));
- stdin.close();
- Log.d(TAG, "Sent " + commands.length + " command(s), now reading output");
+ final ProcessBuilder builder = new ProcessBuilder();
+ builder.environment().put("LANG", "C");
+ builder.command("su", "-c", preamble + command);
+ final Process process = builder.start();
+ Log.d(TAG, "Running: " + command);
final InputStream stdout = process.getInputStream();
+ final InputStream stderr = process.getErrorStream();
final BufferedReader stdoutReader =
new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8));
+ final BufferedReader stderrReader =
+ new BufferedReader(new InputStreamReader(stderr, StandardCharsets.UTF_8));
String line;
- String lastLine = null;
while ((line = stdoutReader.readLine()) != null) {
- Log.v(TAG, line);
- lastLine = line;
if (output != null)
output.add(line);
+ Log.v(TAG, "stdout: " + line);
}
- process.waitFor();
- process.destroy();
- if (lastLine != null) {
- // Remove the exit value line from the output
- if (output != null)
- output.remove(output.size() - 1);
- exitValue = Integer.parseInt(lastLine);
+ int linesOfStderr = 0;
+ String stderrLast = null;
+ while ((line = stderrReader.readLine()) != null) {
+ ++linesOfStderr;
+ stderrLast = line;
+ Log.v(TAG, "stderr: " + line);
}
- Log.d(TAG, "Session completed with exit value " + exitValue);
- } catch (IOException | InterruptedException | NumberFormatException e) {
+ exitValue = process.waitFor();
+ process.destroy();
+ if (exitValue == 1 && linesOfStderr == 1 && stderrLast.equals("Permission denied"))
+ exitValue = OsConstants.EACCES;
+ Log.d(TAG, "Exit status: " + exitValue);
+ } catch (IOException | InterruptedException e) {
Log.w(TAG, "Session failed with exception", e);
final Matcher match = ERRNO_EXTRACTOR.matcher(e.toString());
if (match.find())
diff --git a/app/src/main/java/com/wireguard/android/backends/VpnService.java b/app/src/main/java/com/wireguard/android/backends/VpnService.java
index 5e254d2e..ad16fb36 100644
--- a/app/src/main/java/com/wireguard/android/backends/VpnService.java
+++ b/app/src/main/java/com/wireguard/android/backends/VpnService.java
@@ -14,6 +14,8 @@ import android.os.IBinder;
import android.preference.PreferenceManager;
import android.provider.OpenableColumns;
import android.service.quicksettings.TileService;
+import android.system.ErrnoException;
+import android.system.OsConstants;
import android.util.Log;
import android.widget.Toast;
@@ -48,7 +50,7 @@ public class VpnService extends Service
public static final String KEY_ENABLED_CONFIGS = "enabled_configs";
public static final String KEY_PRIMARY_CONFIG = "primary_config";
public static final String KEY_RESTORE_ON_BOOT = "restore_on_boot";
- private static final String TAG = "VpnService";
+ private static final String TAG = "WireGuard/VpnService";
private static VpnService instance;
private final IBinder binder = new Binder();
@@ -275,7 +277,7 @@ public class VpnService extends Service
Log.i(TAG, "Running wg-quick up for " + config.getName());
final File configFile = new File(getFilesDir(), config.getName() + ".conf");
final int ret = rootShell.run(null, "wg-quick up '" + configFile.getPath() + "'");
- if (ret == 13 /* EPERM */)
+ if (ret == OsConstants.EACCES)
return -0xfff0002;
return ret;
}