From 940ff855d59b38a1334b33f676165b0cfb5dcdd5 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Fri, 31 Jul 2026 08:04:09 +0200 Subject: [PATCH 1/2] Stop calling an out-of-scope process an injection failure --- zygisk/src/main/cpp/ipc_bridge.cpp | 4 ++-- zygisk/src/main/cpp/module.cpp | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/zygisk/src/main/cpp/ipc_bridge.cpp b/zygisk/src/main/cpp/ipc_bridge.cpp index 71217c6d4..bc19adfd8 100644 --- a/zygisk/src/main/cpp/ipc_bridge.cpp +++ b/zygisk/src/main/cpp/ipc_bridge.cpp @@ -290,9 +290,9 @@ lsplant::ScopedLocalRef IPCBridge::RequestAppBinder(JNIEnv *env, jstrin env->NewGlobalRef(heartbeat_binder.get()); } } - } else { - LOGD("Transact call to request app binder failed."); } + // No else: a false transaction is the daemon declining, which is the ordinary answer for a + // process no module has in scope. The caller logs that outcome, with the process name. return result_binder; } diff --git a/zygisk/src/main/cpp/module.cpp b/zygisk/src/main/cpp/module.cpp index 7753b3d1b..0dbb21472 100644 --- a/zygisk/src/main/cpp/module.cpp +++ b/zygisk/src/main/cpp/module.cpp @@ -324,12 +324,14 @@ void VectorModule::postAppSpecialize(const zygisk::AppSpecializeArgs *args) { // --- Framework Injection --- lsplant::JUTFString nice_name_str(env_, args->nice_name); - LOGD("Attempting injection into '{}'.", nice_name_str.get()); + LOGD("Asking the daemon about '{}'.", nice_name_str.get()); auto &ipc_bridge = IPCBridge::GetInstance(); auto binder = ipc_bridge.RequestAppBinder(env_, args->nice_name); if (!binder) { - LOGD("No IPC binder obtained for '{}'. Skipping injection.", nice_name_str.get()); + // Usually because nothing has it in scope, but the daemon may also not be up yet or have + // registered this process already. Only it knows which, and it logs the reason itself. + LOGD("Not injecting '{}': the daemon has no binder for it.", nice_name_str.get()); SetAllowUnload(true); return; } From b964eb7da7f16e65e856b7fcb44ff482611632ba Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Fri, 31 Jul 2026 09:12:00 +0200 Subject: [PATCH 2/2] Read the verbose-log preference before we drop root `sendToBridge` leaves the main thread at euid 1000, and the statement after it opened the config database, which sits under a directory only root can enter. It normally works because a binder thread opens and caches the handle during specialization first, but that only happens when the injection succeeded; when it failed the daemon died on the preference read instead of carrying on. --- .../main/kotlin/org/matrix/vector/daemon/VectorDaemon.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 d1d398c3a..a53862c91 100644 --- a/daemon/src/main/kotlin/org/matrix/vector/daemon/VectorDaemon.kt +++ b/daemon/src/main/kotlin/org/matrix/vector/daemon/VectorDaemon.kt @@ -102,10 +102,17 @@ object VectorDaemon { applyNotificationWorkaround() + // Read this before `sendToBridge`, which leaves the main thread at euid 1000: the config + // database lives under a directory only root can enter, so the first process to open it has + // to do so while we still have root. On a successful injection a binder thread opens it for + // us during specialization, but when the injection fails nothing else has, and the daemon + // used to die here on an unreadable preference. + val isVerboseLog = ManagerService.isVerboseLog() + // Setup IPC channel for applications by injecting DaemonService binder sendToBridge(VectorService.asBinder(), false, systemServerMaxRetry) - if (!ManagerService.isVerboseLog()) { + if (!isVerboseLog) { LogcatMonitor.stopVerbose() }