From 99e9b1deedad8b76d7a9f08d7954265d8e2f84eb Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Fri, 25 Feb 2022 05:03:20 -0800 Subject: [PATCH] Multiuser daemon service support --- .../topjohnwu/superuser/internal/Utils.java | 22 ++ .../topjohnwu/libsuexample/ITestService.aidl | 2 +- example/src/main/cpp/test.cpp | 14 -- .../topjohnwu/libsuexample/AIDLService.java | 21 +- .../topjohnwu/libsuexample/MSGService.java | 26 +- .../topjohnwu/libsuexample/MainActivity.java | 204 ++++++++------- example/src/main/res/layout/activity_main.xml | 12 +- .../internal/IRootServiceManager.aidl | 4 +- service/src/main/assets/main.jar | Bin 2720 -> 2819 bytes .../superuser/internal/BinderHolder.java | 38 +++ .../superuser/internal/RootServerMain.java | 61 +++-- .../internal/RootServiceManager.java | 235 ++++++++++-------- .../superuser/internal/RootServiceServer.java | 229 ++++++++++------- .../topjohnwu/superuser/ipc/RootService.java | 10 + 14 files changed, 519 insertions(+), 359 deletions(-) create mode 100644 service/src/main/java/com/topjohnwu/superuser/internal/BinderHolder.java 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 0355557..4f5e12a 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,8 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.ContextWrapper; import android.os.Build; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import androidx.annotation.RestrictTo; @@ -34,6 +36,10 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; @RestrictTo(RestrictTo.Scope.LIBRARY) public final class Utils { @@ -117,4 +123,20 @@ public final class Utils { } return total; } + + static Map newArrayMap() { + if (Build.VERSION.SDK_INT >= 19) { + return new ArrayMap<>(); + } else { + return new HashMap<>(); + } + } + + static Set newArraySet() { + if (Build.VERSION.SDK_INT >= 23) { + return new ArraySet<>(); + } else { + return new HashSet<>(); + } + } } diff --git a/example/src/main/aidl/com/topjohnwu/libsuexample/ITestService.aidl b/example/src/main/aidl/com/topjohnwu/libsuexample/ITestService.aidl index ac95757..cf36fcf 100644 --- a/example/src/main/aidl/com/topjohnwu/libsuexample/ITestService.aidl +++ b/example/src/main/aidl/com/topjohnwu/libsuexample/ITestService.aidl @@ -6,5 +6,5 @@ package com.topjohnwu.libsuexample; interface ITestService { int getPid(); int getUid(); - String readCmdline(); + String getUUID(); } diff --git a/example/src/main/cpp/test.cpp b/example/src/main/cpp/test.cpp index 4ce95c1..3ba3845 100644 --- a/example/src/main/cpp/test.cpp +++ b/example/src/main/cpp/test.cpp @@ -16,23 +16,9 @@ #include #include -#include -#include -#include extern "C" JNIEXPORT JNICALL jint Java_com_topjohnwu_libsuexample_AIDLService_nativeGetUid( JNIEnv *env, jobject instance) { return getuid(); } - -extern "C" JNIEXPORT JNICALL -jstring Java_com_topjohnwu_libsuexample_AIDLService_nativeReadFile( - JNIEnv *env, jobject instance, jstring name) { - const char *path = env->GetStringUTFChars(name, nullptr); - int fd = open(path, O_RDONLY); - env->ReleaseStringUTFChars(name, path); - char buf[4096]; - buf[read(fd, buf, sizeof(buf) - 1)] = 0; - return env->NewStringUTF(buf); -} diff --git a/example/src/main/java/com/topjohnwu/libsuexample/AIDLService.java b/example/src/main/java/com/topjohnwu/libsuexample/AIDLService.java index 2568d2d..b40c8bf 100644 --- a/example/src/main/java/com/topjohnwu/libsuexample/AIDLService.java +++ b/example/src/main/java/com/topjohnwu/libsuexample/AIDLService.java @@ -16,6 +16,8 @@ package com.topjohnwu.libsuexample; +import static com.topjohnwu.libsuexample.MainActivity.TAG; + import android.content.Intent; import android.os.IBinder; import android.os.Process; @@ -25,10 +27,11 @@ import androidx.annotation.NonNull; import com.topjohnwu.superuser.ipc.RootService; -import static com.topjohnwu.libsuexample.MainActivity.TAG; +import java.util.UUID; // Demonstrate RootService using AIDL (daemon mode) class AIDLService extends RootService { + static { // Only load the library when this class is loaded in a root process. // The classloader will load this class (and call this static block) in the non-root @@ -40,7 +43,6 @@ class AIDLService extends RootService { // Demonstrate we can also run native code via JNI with RootServices native int nativeGetUid(); - native String nativeReadFile(String file); class TestIPC extends ITestService.Stub { @Override @@ -54,12 +56,19 @@ class AIDLService extends RootService { } @Override - public String readCmdline() { - // Normally we cannot read /proc/cmdline without root - return nativeReadFile("/proc/cmdline"); + public String getUUID() { + return uuid; } } + private String uuid; + + @Override + public void onCreate() { + uuid = UUID.randomUUID().toString(); + Log.d(TAG, "AIDLService: onCreate, " + uuid); + } + @Override public void onRebind(@NonNull Intent intent) { // This callback will be called when we are reusing a previously started root process @@ -75,7 +84,7 @@ class AIDLService extends RootService { @Override public boolean onUnbind(@NonNull Intent intent) { Log.d(TAG, "AIDLService: onUnbind, client process unbound"); - // We return true here to tell libsu that we want this service to run as a daemon + // Return true here so onRebindg will be called return true; } } diff --git a/example/src/main/java/com/topjohnwu/libsuexample/MSGService.java b/example/src/main/java/com/topjohnwu/libsuexample/MSGService.java index 6d3c94d..a9c6251 100644 --- a/example/src/main/java/com/topjohnwu/libsuexample/MSGService.java +++ b/example/src/main/java/com/topjohnwu/libsuexample/MSGService.java @@ -31,19 +31,24 @@ import android.util.Log; import androidx.annotation.NonNull; -import com.topjohnwu.superuser.internal.Utils; import com.topjohnwu.superuser.ipc.RootService; -import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; -import java.io.IOException; +import java.util.UUID; // Demonstrate root service using Messengers class MSGService extends RootService implements Handler.Callback { static final int MSG_GETINFO = 1; static final int MSG_STOP = 2; - static final String CMDLINE_KEY = "cmdline"; + static final String UUID_KEY = "uuid"; + + private String uuid; + + @Override + public void onCreate() { + uuid = UUID.randomUUID().toString(); + Log.d(TAG, "MSGService: onCreate, " + uuid); + } @Override public IBinder onBind(@NonNull Intent intent) { @@ -65,15 +70,8 @@ class MSGService extends RootService implements Handler.Callback { reply.what = msg.what; reply.arg1 = Process.myPid(); reply.arg2 = Process.myUid(); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try (FileInputStream in = new FileInputStream("/proc/cmdline")) { - // libsu internal util method, pumps input to output - Utils.pump(in, out); - } catch (IOException e) { - Log.e(TAG, "IO error", e); - } Bundle data = new Bundle(); - data.putString(CMDLINE_KEY, out.toString()); + data.putString(UUID_KEY, uuid); reply.setData(data); try { msg.replyTo.send(reply); @@ -86,7 +84,7 @@ class MSGService extends RootService implements Handler.Callback { @Override public boolean onUnbind(@NonNull Intent intent) { Log.d(TAG, "MSGService: onUnbind, client process unbound"); - // Default returns false, which means NOT daemon mode + // Default returns false, which means onRebind will not be called return false; } } diff --git a/example/src/main/java/com/topjohnwu/libsuexample/MainActivity.java b/example/src/main/java/com/topjohnwu/libsuexample/MainActivity.java index b7e8057..3a31782 100644 --- a/example/src/main/java/com/topjohnwu/libsuexample/MainActivity.java +++ b/example/src/main/java/com/topjohnwu/libsuexample/MainActivity.java @@ -41,7 +41,6 @@ import com.topjohnwu.superuser.ipc.RootService; import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; import java.util.List; public class MainActivity extends Activity implements Handler.Callback { @@ -57,15 +56,10 @@ public class MainActivity extends Activity implements Handler.Callback { ); } - private ITestService testIPC; - private Messenger remoteMessenger; - private Messenger myMessenger = new Messenger(new Handler(Looper.getMainLooper(), this)); - private MSGConnection conn = new MSGConnection(); - private boolean daemonTestQueued = false; - private boolean serviceTestQueued = false; + private final Messenger me = new Messenger(new Handler(Looper.getMainLooper(), this)); + private final List consoleList = new AppendCallbackList(); private ActivityMainBinding binding; - private List consoleList = new AppendCallbackList(); // Demonstrate Shell.Initializer static class ExampleInitializer extends Shell.Initializer { @@ -79,135 +73,157 @@ public class MainActivity extends Activity implements Handler.Callback { } } + private AIDLConnection aidlConn; + private AIDLConnection daemonConn; + class AIDLConnection implements ServiceConnection { + + private final boolean isDaemon; + + AIDLConnection(boolean b) { + isDaemon = b; + } + @Override public void onServiceConnected(ComponentName name, IBinder service) { - Log.d(TAG, "daemon onServiceConnected"); - testIPC = ITestService.Stub.asInterface(service); - if (daemonTestQueued) { - daemonTestQueued = false; - testDaemon(); + Log.d(TAG, "AIDL onServiceConnected"); + if (isDaemon) daemonConn = this; + else aidlConn = this; + refreshUI(); + + ITestService ipc = ITestService.Stub.asInterface(service); + try { + consoleList.add("AIDL PID : " + ipc.getPid()); + consoleList.add("AIDL UID : " + ipc.getUid()); + consoleList.add("AIDL UUID: " + ipc.getUUID()); + } catch (RemoteException e) { + Log.e(TAG, "Remote error", e); } } @Override public void onServiceDisconnected(ComponentName name) { - Log.d(TAG, "daemon onServiceDisconnected"); - testIPC = null; + Log.d(TAG, "AIDL onServiceDisconnected"); + if (isDaemon) daemonConn = null; + else aidlConn = null; + refreshUI(); } } + private MSGConnection msgConn; + class MSGConnection implements ServiceConnection { + private Messenger m; + @Override public void onServiceConnected(ComponentName name, IBinder service) { - Log.d(TAG, "service onServiceConnected"); - remoteMessenger = new Messenger(service); - if (serviceTestQueued) { - serviceTestQueued = false; - testService(); + Log.d(TAG, "MSG onServiceConnected"); + m = new Messenger(service); + msgConn = this; + refreshUI(); + + Message msg = Message.obtain(null, MSGService.MSG_GETINFO); + msg.replyTo = me; + try { + m.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "Remote error", e); + } finally { + msg.recycle(); } } @Override public void onServiceDisconnected(ComponentName name) { - Log.d(TAG, "service onServiceDisconnected"); - remoteMessenger = null; + Log.d(TAG, "MSG onServiceDisconnected"); + msgConn = null; + refreshUI(); } - } - private void testDaemon() { - try { - consoleList.add("Daemon PID: " + testIPC.getPid()); - consoleList.add("Daemon UID: " + testIPC.getUid()); - String[] cmds = testIPC.readCmdline().split(" "); - if (cmds.length > 5) { - cmds = Arrays.copyOf(cmds, 6); - cmds[5] = "..."; + void stop() { + if (m == null) + return; + Message msg = Message.obtain(null, MSGService.MSG_STOP); + try { + m.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "Remote error", e); + } finally { + msg.recycle(); } - consoleList.add("/proc/cmdline:"); - consoleList.addAll(Arrays.asList(cmds)); - } catch (RemoteException e) { - Log.e(TAG, "Remote error", e); - } - } - - private void testService() { - Message message = Message.obtain(null, MSGService.MSG_GETINFO); - message.replyTo = myMessenger; - try { - remoteMessenger.send(message); - } catch (RemoteException e) { - Log.e(TAG, "Remote error", e); } } @Override public boolean handleMessage(@NonNull Message msg) { - consoleList.add("Remote PID: " + msg.arg1); - consoleList.add("Remote UID: " + msg.arg2); - String cmdline = msg.getData().getString(MSGService.CMDLINE_KEY); - String[] cmds = cmdline.split(" "); - if (cmds.length > 5) { - cmds = Arrays.copyOf(cmds, 6); - cmds[5] = "..."; - } - consoleList.add("/proc/cmdline:"); - consoleList.addAll(Arrays.asList(cmds)); + consoleList.add("MSG PID : " + msg.arg1); + consoleList.add("MSG UID : " + msg.arg2); + String uuid = msg.getData().getString(MSGService.UUID_KEY); + consoleList.add("MSG UUID : " + uuid); return false; } + private void refreshUI() { + binding.aidlSvc.setText(aidlConn == null ? "Bind AIDL" : "Unbind AIDL"); + binding.msgSvc.setText(msgConn == null ? "Bind MSG" : "Unbind MSG"); + binding.testDaemon.setText(daemonConn == null ? "Bind Daemon" : "Unbind Daemon"); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); - // Bind to a root service; IPC via Messages - binding.testSvc.setOnClickListener(v -> { - if (remoteMessenger == null) { - serviceTestQueued = true; - Intent intent = new Intent(this, MSGService.class); - RootService.bind(intent, conn); - return; - } - testService(); - }); - - // Unbind service through RootService API - binding.unbindSvc.setOnClickListener(v -> RootService.unbind(conn)); - - // Send a message to service and ask it to stop itself to demonstrate stopSelf() - binding.stopSvc.setOnClickListener(v -> { - if (remoteMessenger != null) { - Message message = Message.obtain(null, MSGService.MSG_STOP); - try { - remoteMessenger.send(message); - } catch (RemoteException e) { - Log.e(TAG, "Remote error", e); - } - } - }); - - // Bind to a daemon root service; IPC via AIDL - binding.testDaemon.setOnClickListener(v -> { - if (testIPC == null) { - daemonTestQueued = true; + // Bind to a root service; IPC via AIDL + binding.aidlSvc.setOnClickListener(v -> { + if (aidlConn == null) { Intent intent = new Intent(this, AIDLService.class); - RootService.bind(intent, new AIDLConnection()); - return; + RootService.bind(intent, new AIDLConnection(false)); + } else { + RootService.unbind(aidlConn); } - testDaemon(); }); + // Bind to a root service; IPC via Messages + binding.msgSvc.setOnClickListener(v -> { + if (msgConn == null) { + Intent intent = new Intent(this, MSGService.class); + RootService.bind(intent, new MSGConnection()); + } else { + RootService.unbind(msgConn); + } + }); + + // Send a message to service and ask it to stop itself to test stopSelf() + binding.selfStop.setOnClickListener(v -> { + if (msgConn != null) { + msgConn.stop(); + } + }); + + // To verify whether the daemon actually works, kill the app and try testing the + // daemon again. You should get the same PID as last time (as it was re-using the + // previous daemon process), and in AIDLService, onRebind should be called. + // Note: re-running the app in Android Studio is not the same as kill + relaunch. + // The root process will kill itself when it detects the client APK has updated. + + // Bind to a daemon root service + binding.testDaemon.setOnClickListener(v -> { + if (daemonConn == null) { + Intent intent = new Intent(this, AIDLService.class); + intent.addCategory(RootService.CATEGORY_DAEMON_MODE); + RootService.bind(intent, new AIDLConnection(true)); + } else { + RootService.unbind(daemonConn); + } + }); + + // Test the stop API binding.stopDaemon.setOnClickListener(v -> { Intent intent = new Intent(this, AIDLService.class); - // Use stop here instead of unbind because AIDLService is running as a daemon. - // To verify whether the daemon actually works, kill the app and try testing the - // daemon again. You should get the same PID as last time (as it was re-using the - // previous daemon process), and in AIDLService, onRebind should be called. - // Note: re-running the app in Android Studio is not the same as kill + relaunch. - // The root process will kill itself when it detects the client APK has updated. + intent.addCategory(RootService.CATEGORY_DAEMON_MODE); RootService.stop(intent); }); diff --git a/example/src/main/res/layout/activity_main.xml b/example/src/main/res/layout/activity_main.xml index 690b12d..d893a50 100644 --- a/example/src/main/res/layout/activity_main.xml +++ b/example/src/main/res/layout/activity_main.xml @@ -72,23 +72,23 @@ android:orientation="horizontal">