From 9c7025130ed9483269f74ef8500a98cb809efd20 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Sun, 27 Feb 2022 22:09:23 -0800 Subject: [PATCH] Update Shell.rootAccess() implementation --- .../java/com/topjohnwu/superuser/Shell.java | 17 ++++++------ .../superuser/internal/BuilderImpl.java | 6 ++++- .../superuser/internal/ShellImpl.java | 6 ++++- .../topjohnwu/superuser/internal/Utils.java | 26 +++++++++++++++++++ 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/com/topjohnwu/superuser/Shell.java b/core/src/main/java/com/topjohnwu/superuser/Shell.java index 436a7e1..fbdd644 100644 --- a/core/src/main/java/com/topjohnwu/superuser/Shell.java +++ b/core/src/main/java/com/topjohnwu/superuser/Shell.java @@ -28,6 +28,7 @@ import androidx.annotation.Nullable; import com.topjohnwu.superuser.internal.BuilderImpl; import com.topjohnwu.superuser.internal.MainShell; import com.topjohnwu.superuser.internal.UiThreadHandler; +import com.topjohnwu.superuser.internal.Utils; import java.io.Closeable; import java.io.IOException; @@ -206,17 +207,17 @@ public abstract class Shell implements Closeable { } /** - * {@code Shell.getShell().isRoot()} + * Whether the application has access to root. *

- * Please refer to {@link #getShell()} for info about concerns on blocking. + * This method would NEVER produce false negatives, but false positives can be returned before + * actually constructing a root shell. A {@code false} returned is guaranteed to be + * 100% accurate, while {@code true} may be returned if the device is rooted, but the user + * did not grant root access to your application. However, after any root shell is constructed, + * this method will accurately return {@code true}. + * @return whether the application has access to root. */ public static boolean rootAccess() { - try { - return getShell().isRoot(); - } catch (NoShellException e) { - return false; - } - + return Utils.isAppGrantedRoot(); } /* ************ diff --git a/core/src/main/java/com/topjohnwu/superuser/internal/BuilderImpl.java b/core/src/main/java/com/topjohnwu/superuser/internal/BuilderImpl.java index e9594bd..8984498 100644 --- a/core/src/main/java/com/topjohnwu/superuser/internal/BuilderImpl.java +++ b/core/src/main/java/com/topjohnwu/superuser/internal/BuilderImpl.java @@ -89,8 +89,12 @@ public final class BuilderImpl extends Shell.Builder { if (shell == null && !hasFlags(FLAG_NON_ROOT_SHELL)) { try { shell = build("su"); - if (shell.getStatus() != ROOT_SHELL) + if (shell.getStatus() != ROOT_SHELL) { shell = null; + synchronized (Utils.class) { + Utils.confirmedRootState = false; + } + } } catch (NoShellException ignore) {} } diff --git a/core/src/main/java/com/topjohnwu/superuser/internal/ShellImpl.java b/core/src/main/java/com/topjohnwu/superuser/internal/ShellImpl.java index 17b3da9..f2c7de7 100644 --- a/core/src/main/java/com/topjohnwu/superuser/internal/ShellImpl.java +++ b/core/src/main/java/com/topjohnwu/superuser/internal/ShellImpl.java @@ -141,8 +141,12 @@ class ShellImpl extends Shell { STDIN.write(("id\n").getBytes(UTF_8)); STDIN.flush(); s = br.readLine(); - if (!TextUtils.isEmpty(s) && s.contains("uid=0")) + if (!TextUtils.isEmpty(s) && s.contains("uid=0")) { status = ROOT_SHELL; + synchronized (Utils.class) { + Utils.confirmedRootState = true; + } + } if (status == ROOT_SHELL) { STDIN.write(("readlink /proc/self/ns/mnt\n").getBytes(UTF_8)); diff --git a/core/src/main/java/com/topjohnwu/superuser/internal/Utils.java b/core/src/main/java/com/topjohnwu/superuser/internal/Utils.java index 6b1da78..11c10e6 100644 --- a/core/src/main/java/com/topjohnwu/superuser/internal/Utils.java +++ b/core/src/main/java/com/topjohnwu/superuser/internal/Utils.java @@ -20,6 +20,7 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.ContextWrapper; import android.os.Build; +import android.os.Process; import android.util.ArraySet; import android.util.Log; @@ -42,6 +43,7 @@ public final class Utils { public static Context context; private static Class synchronizedCollectionClass; private static final String TAG = "LIBSU"; + static Boolean confirmedRootState = null; public static void log(Object log) { log(TAG, log); @@ -123,4 +125,28 @@ public final class Utils { return new HashSet<>(); } } + + public synchronized static boolean isAppGrantedRoot() { + if (confirmedRootState != null) { + // This confirmed root state will also be set in BuilderImpl + // and ShellImpl when new shells are getting constructed. + return confirmedRootState; + } + if (Process.myUid() == 0) { + // The current process is a root service + confirmedRootState = true; + return true; + } + try { + Runtime.getRuntime().exec("su --version"); + // Even if the execution worked, we don't actually know whether the app has + // been granted root access. As a heuristic, let's return true here, + // but do NOT set the value as a confirmed state. + return true; + } catch (IOException e) { + // Cannot run program "su": error=2, No such file or directory + confirmedRootState = false; + return false; + } + } }