Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 41 additions & 22 deletions daemon/src/main/kotlin/org/matrix/vector/daemon/VectorDaemon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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!"
Expand All @@ -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)
}

Expand All @@ -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)
}
}
}
Expand All @@ -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)
}

Expand Down Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -14,44 +13,41 @@ 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) }
}
}

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,
Expand All @@ -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)
}

Expand Down Expand Up @@ -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)
}
}
}
18 changes: 9 additions & 9 deletions zygisk/src/main/cpp/ipc_bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -307,23 +307,23 @@ lsplant::ScopedLocalRef<jobject> IPCBridge::RequestSystemServerBinder(
auto service_name = lsplant::ScopedLocalRef(env, env->NewStringUTF(bridgeServiceName.data()));
lsplant::ScopedLocalRef<jobject> 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};
}

Expand Down