diff --git a/daemon/src/main/kotlin/org/matrix/vector/daemon/VectorDaemon.kt b/daemon/src/main/kotlin/org/matrix/vector/daemon/VectorDaemon.kt index 38f225c0e..9c5224d82 100644 --- a/daemon/src/main/kotlin/org/matrix/vector/daemon/VectorDaemon.kt +++ b/daemon/src/main/kotlin/org/matrix/vector/daemon/VectorDaemon.kt @@ -11,6 +11,7 @@ import android.os.Looper import android.os.Parcel import android.os.Process import android.os.ServiceManager +import android.os.SystemProperties import android.system.Os import android.util.Log import kotlinx.coroutines.CoroutineExceptionHandler @@ -41,6 +42,7 @@ object VectorDaemon { // Dispatchers.IO: Uses the shared background thread pool. // SupervisorJob(): Ensures one failing task doesn't kill the whole daemon. val scope = CoroutineScope(Dispatchers.IO + SupervisorJob() + exceptionHandler) + val bridgeServiceName = "activity" var isLateInject = false var proxyServiceName = "serial" @@ -67,6 +69,14 @@ object VectorDaemon { kotlin.system.exitProcess(1) } + // Setup Main Looper + Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND) + @Suppress("DEPRECATION") Looper.prepareMainLooper() + + // Squat on the proxy service name immediately, which creates the early IPC channel of + // ApplicationService for our Zygisk module during system_server specialization. + SystemServerService.registerProxyService(proxyServiceName) + // Start Environmental Daemons LogcatMonitor.start() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Dex2OatServer.start() @@ -75,27 +85,20 @@ object VectorDaemon { // Preload Framework DEX in the background scope.launch { FileSystem.getPreloadDex(ConfigCache.state.isDexObfuscateEnabled) } - // Setup Main Looper & System Services - Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND) - @Suppress("DEPRECATION") Looper.prepareMainLooper() - - val systemServerService = SystemServerService(systemServerMaxRetry, proxyServiceName) - systemServerService.putBinderForSystemServer() - // Initializes system frameworks inside the daemon process ActivityThread.systemMain() DdmHandleAppName.setAppName("org.matrix.vector.daemon", 0) - // Wait for Android Core Services + // Wait for Android core services waitForSystemService("package") - waitForSystemService("activity") + waitForSystemService("activity") // current bridgeServiceName waitForSystemService(Context.USER_SERVICE) waitForSystemService(Context.APP_OPS_SERVICE) applyNotificationWorkaround() - // Inject Vector into system_server - sendToBridge(VectorService.asBinder(), isRestart = false, systemServerService) + // Setup IPC channel for applications by injecting DaemonService binder + sendToBridge(VectorService.asBinder(), false, systemServerMaxRetry) if (!ManagerService.isVerboseLog()) { LogcatMonitor.stopVerbose() @@ -112,11 +115,12 @@ object VectorDaemon { } } + // The bridge is setup in `system_server` via Zygisk API @Suppress("DEPRECATION") private fun sendToBridge( binder: IBinder, isRestart: Boolean, - systemServerService: SystemServerService + restartRetry: Int, ) { check(Looper.myLooper() == Looper.getMainLooper()) { "sendToBridge MUST run on the main thread!" @@ -126,12 +130,12 @@ object VectorDaemon { runCatching { var bridgeService: IBinder? - if (isRestart) Log.w(TAG, "System Server restarted...") + if (isRestart) Log.w(TAG, "system_server restarted...") while (true) { - bridgeService = ServiceManager.getService("activity") + bridgeService = ServiceManager.getService(bridgeServiceName) if (bridgeService?.pingBinder() == true) break - Log.i(TAG, "activity service not ready, waiting 1s...") + Log.i(TAG, "`$bridgeServiceName` service not ready, waiting 1s...") Thread.sleep(1000) } @@ -142,10 +146,13 @@ object VectorDaemon { Log.w(TAG, "System Server died! Clearing caches and re-injecting...") bridgeService.unlinkToDeath(this, 0) clearSystemCaches() - systemServerService.putBinderForSystemServer() + SystemServerService.binderDied() // Cleanup old references + // Re-claim the service name immediately to ensure that when system_server + // restarts, our proxy is already there for the Zygisk module to find. + ServiceManager.addService(proxyServiceName, SystemServerService) ManagerService.guard = null // Remove dead guard Handler(Looper.getMainLooper()).post { - sendToBridge(binder, isRestart = true, systemServerService) + sendToBridge(binder, true, restartRetry - 1) } } } @@ -170,13 +177,14 @@ object VectorDaemon { Thread.sleep(1000) } - if (success) Log.i(TAG, "Successfully injected Vector into system_server") - else { - Log.e(TAG, "Failed to inject Vector into system_server") - systemServerService.maybeRetryInject() + if (success) { + Log.i(TAG, "Successfully injected Vector IPC binder for applications.") + } else { + Log.e(TAG, "Failed to inject VectorService into system_server") + if (restartRetry > 0) restartSystemServer() } } - .onFailure { Log.e(TAG, "Error during System Server bridging", it) } + .onFailure { Log.e(TAG, "Error during injecting DaemonService", it) } Os.seteuid(1000) } @@ -209,4 +217,15 @@ object VectorDaemon { } .onFailure { Log.w(TAG, "Failed to clear system caches via reflection", it) } } + + fun restartSystemServer() { + Log.w(TAG, "Restarting system_server...") + val restartTarget = + if (Build.SUPPORTED_64_BIT_ABIS.isNotEmpty() && Build.SUPPORTED_32_BIT_ABIS.isNotEmpty()) { + "zygote_secondary" + } else { + "zygote" + } + SystemProperties.set("ctl.restart", restartTarget) + } } diff --git a/daemon/src/main/kotlin/org/matrix/vector/daemon/ipc/SystemServerService.kt b/daemon/src/main/kotlin/org/matrix/vector/daemon/ipc/SystemServerService.kt index 74c94a353..dc229ad22 100644 --- a/daemon/src/main/kotlin/org/matrix/vector/daemon/ipc/SystemServerService.kt +++ b/daemon/src/main/kotlin/org/matrix/vector/daemon/ipc/SystemServerService.kt @@ -5,7 +5,6 @@ import android.os.IBinder import android.os.IServiceCallback import android.os.Parcel import android.os.ServiceManager -import android.os.SystemProperties import android.util.Log import org.lsposed.lspd.service.ILSPApplicationService import org.lsposed.lspd.service.ILSPSystemServerService @@ -14,27 +13,25 @@ import org.matrix.vector.daemon.system.getSystemServiceManager private const val TAG = "VectorSystemServer" -class SystemServerService(private val maxRetry: Int, private val proxyServiceName: String) : - ILSPSystemServerService.Stub(), IBinder.DeathRecipient { +object SystemServerService : ILSPSystemServerService.Stub(), IBinder.DeathRecipient { + private var proxyServiceName: String? = null private var originService: IBinder? = null - private var requestedRetryCount = -maxRetry - companion object { - var systemServerRequested = false - } + var systemServerRequested = false - init { - Log.d(TAG, "registering via proxy $proxyServiceName") + fun registerProxyService(serviceName: String) { + // Register as the service name early to setup an IPC for `system_server`. + Log.d(TAG, "Registering bridge service for `system_server` with name `$serviceName`.") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val callback = object : IServiceCallback.Stub() { + // The IServiceCallback will tell us when the real Android service is ready, + // allowing us to capture it and then naturally stop intercepting traffic. override fun onRegistration(name: String, binder: IBinder?) { - if (name == proxyServiceName && - binder != null && - binder !== this@SystemServerService) { - Log.d(TAG, "Intercepted system service registration: $name") + if (name == serviceName && binder != null && binder !== this@SystemServerService) { + Log.d(TAG, "Intercepted system service registration with name `$name`") originService = binder runCatching { binder.linkToDeath(this@SystemServerService, 0) } } @@ -42,16 +39,15 @@ class SystemServerService(private val maxRetry: Int, private val proxyServiceNam override fun asBinder(): IBinder = this } - runCatching { getSystemServiceManager().registerForNotifications(proxyServiceName, callback) } + runCatching { + getSystemServiceManager().registerForNotifications(serviceName, callback) + ServiceManager.addService(serviceName, this) + proxyServiceName = serviceName + } .onFailure { Log.e(TAG, "Failed to register IServiceCallback", it) } } } - fun putBinderForSystemServer() { - ServiceManager.addService(proxyServiceName, this) - binderDied() - } - override fun requestApplicationService( uid: Int, pid: Int, @@ -69,8 +65,9 @@ class SystemServerService(private val maxRetry: Int, private val proxyServiceNam override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { originService?.let { - // This should however never happen, as service registration enforces later replacements - Log.i(TAG, "Original service $proxyServiceName alive, transmitting requests") + // This is unlikely to happen unless system_server restarts / crashes, since we intentionally + // discard our proxy upon later replacements in registerProxyService. + Log.d(TAG, "Forwarding request to real `$proxyServiceName` service.") return it.transact(code, data, reply, flags) } @@ -103,19 +100,4 @@ class SystemServerService(private val maxRetry: Int, private val proxyServiceNam originService?.unlinkToDeath(this, 0) originService = null } - - fun maybeRetryInject() { - if (requestedRetryCount < 0) { - Log.w(TAG, "System server injection fails, triggering restart...") - requestedRetryCount++ - val restartTarget = - if (Build.SUPPORTED_64_BIT_ABIS.isNotEmpty() && - Build.SUPPORTED_32_BIT_ABIS.isNotEmpty()) { - "zygote_secondary" - } else { - "zygote" - } - SystemProperties.set("ctl.restart", restartTarget) - } - } } diff --git a/zygisk/src/main/cpp/ipc_bridge.cpp b/zygisk/src/main/cpp/ipc_bridge.cpp index 1cf79917a..b4e228068 100644 --- a/zygisk/src/main/cpp/ipc_bridge.cpp +++ b/zygisk/src/main/cpp/ipc_bridge.cpp @@ -307,23 +307,23 @@ lsplant::ScopedLocalRef IPCBridge::RequestSystemServerBinder( auto service_name = lsplant::ScopedLocalRef(env, env->NewStringUTF(bridgeServiceName.data())); lsplant::ScopedLocalRef binder = {env, nullptr}; - // The system_server might start its services slightly after Zygisk injects us. - // We retry a few times to give it a chance to register. - for (int i = 0; i < 3; ++i) { + // The daemon process and system_server specialization run in parallel. + // On slower devices, the daemon may take several seconds to call addService. + // We poll for up to 10 seconds to ensure the early IPC channel for system_server is available. + const int max_retry = 10; + for (int i = 0; i < max_retry; ++i) { binder = lsplant::JNI_CallStaticObjectMethod(env, service_manager_class_, get_service_method_, service_name.get()); if (binder) { LOGI("Got system server binder via {} on attempt {}.", bridgeServiceName.data(), i + 1); return binder; } - if (i < 2) { - LOGW("Failed to get system server binder via {}, will retry in 1 second...", - bridgeServiceName.data()); - std::this_thread::sleep_for(std::chrono::seconds(1)); - } + LOGW("Failed to get system server binder via {}, will retry in 1 second...", + bridgeServiceName.data()); + std::this_thread::sleep_for(std::chrono::seconds(1)); } - LOGE("Failed to get system server binder after 3 attempts. Aborting."); + LOGE("Failed to get system server binder after {} attempts. Aborting.", max_retry); return {env, nullptr}; }