From 3f8be11f9a74fee3a308648b658b47e5a427623f Mon Sep 17 00:00:00 2001 From: Jorrit Jongma Date: Sat, 18 Jan 2014 16:46:31 +0100 Subject: [PATCH] Add utility function create shell command from uid and SELinux context The shell command is normally just "sh" or "su", but sometimes you need to switch to a specific uid or SELinux context. The Shell.SU.shell(...) method will construct a string in this format: "su[ --context ][ ]". Currently only SuperSU v1.90 supports the context parameter, so the code auto-detects if that is the su binary currently being used. Update the command format for other su binaries if/when that feature becomes available. To reduce the chance of issues, the uid is only passed if not 0 (root, this is the default case), and the SELinux context is only passed if SELinux is detected to be in enforcing mode (otherwise it may be useful for development/logging purposes only, but not end-user) --- .../src/eu/chainfire/libsuperuser/Shell.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java b/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java index 8c780c6..63fa5de 100644 --- a/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java +++ b/libsuperuser/src/eu/chainfire/libsuperuser/Shell.java @@ -17,7 +17,10 @@ package eu.chainfire.libsuperuser; import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -337,6 +340,72 @@ public static boolean isSU(String shell) { return shell.equals("su"); } + + /** + * Constructs a shell command to start a su shell using the supplied + * uid and SELinux context. This is can be an expensive operation, + * consider caching the result. + * + * @param uid Uid to use (0 == root) + * @param context (SELinux) context name to use or null + * @return Shell command + */ + public static String shell(int uid, String context) { + // su[ --context ][ ] + String shell = "su"; + + // First known firmware with SELinux built-in was a 4.2 (17) leak + if ((context != null) && (android.os.Build.VERSION.SDK_INT >= 17)) { + Boolean enforcing = null; + + // Detect enforcing through sysfs, not always present + if (enforcing == null) { + File f = new File("/sys/fs/selinux/enforce"); + if (f.exists()) { + try { + InputStream is = new FileInputStream("/sys/fs/selinux/enforce"); + try { + enforcing = (is.read() == '1'); + } finally { + is.close(); + } + } catch (Exception e) { + } + } + } + + // 4.4+ builds are enforcing by default, take the gamble + if (enforcing == null) { + enforcing = (android.os.Build.VERSION.SDK_INT >= 19); + } + + // Switching to a context in permissive mode is not generally + // useful aside from audit testing, but we want to avoid + // switching the context due to the increased chance of issues. + if (enforcing) { + String display = version(false); + String internal = version(true); + + // We only know the format for SuperSU v1.90+ right now + if ( + (display != null) && + (internal != null) && + (display.endsWith("SUPERSU")) && + (Integer.valueOf(internal) >= 190) + ) { + shell = String.format(Locale.ENGLISH, "%s --context %s", shell, context); + } + } + } + + // Most su binaries support the "su " format, but in case + // they don't, lets skip it for the default 0 (root) case + if (uid > 0) { + shell = String.format(Locale.ENGLISH, "%s %d", shell, uid); + } + + return shell; + } } /**