diff --git a/daemon/src/main/kotlin/org/matrix/vector/daemon/data/ConfigCache.kt b/daemon/src/main/kotlin/org/matrix/vector/daemon/data/ConfigCache.kt index b79aa11817..6d522364b2 100644 --- a/daemon/src/main/kotlin/org/matrix/vector/daemon/data/ConfigCache.kt +++ b/daemon/src/main/kotlin/org/matrix/vector/daemon/data/ConfigCache.kt @@ -39,6 +39,34 @@ object ConfigCache { // Absent means the module places no restriction on its scope. @Volatile private var staticScopes: Map> = emptyMap() + private const val PER_USER_RANGE = 100000 + private const val FIRST_APP_ZYGOTE_ISOLATED_UID = 90000 + private const val LAST_ISOLATED_UID = 99999 + + /** + * Resolve the modules for [scope]. Isolated services have a transient UID and a generated + * process suffix, so they cannot have a stable database row of their own. In that case inherit + * the base package's explicit scope for the same Android user. + */ + private fun modulesForScope(scope: ProcessScope): List? { + state.scopes[scope]?.let { return it } + + val appId = scope.uid % PER_USER_RANGE + if (appId !in FIRST_APP_ZYGOTE_ISOLATED_UID..LAST_ISOLATED_UID) return null + + val basePackage = scope.processName.substringBefore(':') + if (basePackage == scope.processName) return null + val userId = scope.uid / PER_USER_RANGE + val inherited = + state.scopes.entries.firstOrNull { (candidate, _) -> + candidate.processName == basePackage && candidate.uid / PER_USER_RANGE == userId + } + if (inherited != null) { + Log.i(TAG, "Inherited $basePackage scope for isolated process ${scope.processName}/${scope.uid}") + } + return inherited?.value + } + /** The packages [modulePackage] claims, or null when it does not fix its scope. */ fun staticScopeOf(modulePackage: String): Set? = staticScopes[modulePackage] @@ -462,7 +490,7 @@ object ConfigCache { Log.w(TAG, "Skip unexpected module queries for $processName") return emptyList() } - return state.scopes[ProcessScope(processName, uid)] ?: emptyList() + return modulesForScope(ProcessScope(processName, uid)) ?: emptyList() } fun getModuleByUid(uid: Int): LoadedModule? = @@ -603,7 +631,7 @@ object ConfigCache { fun shouldSkipProcess(scope: ProcessScope): Boolean { ensureCacheReady() - return !state.scopes.containsKey(scope) + return modulesForScope(scope) == null } fun getPrefsPath(packageName: String, uid: Int): String { diff --git a/zygisk/src/main/cpp/module.cpp b/zygisk/src/main/cpp/module.cpp index 84ff88312d..4e71e59fae 100644 --- a/zygisk/src/main/cpp/module.cpp +++ b/zygisk/src/main/cpp/module.cpp @@ -297,13 +297,13 @@ void VectorModule::preAppSpecialize(zygisk::AppSpecializeArgs *args) { return; } - // Skip isolated processes, which are heavily sandboxed. + // Isolated processes may host explicitly scoped application services (for example, + // Android's on-device ML runtimes). Let the daemon make the final scope decision so a + // module scoped to the owning package can opt in to those services. const uid_t app_id = args->uid % PER_USER_RANGE; - if ((app_id >= FIRST_ISOLATED_UID && app_id <= LAST_ISOLATED_UID) || - (app_id >= FIRST_APP_ZYGOTE_ISOLATED_UID && app_id <= LAST_APP_ZYGOTE_ISOLATED_UID) || - app_id == SHARED_RELRO_UID) { - LOGV("Skipping injection for '{}': is an isolated process (UID: {}).", nice_name_str.get(), - app_id); + if (app_id == SHARED_RELRO_UID) { + LOGV("Skipping injection for '{}': is the shared RELRO process (UID: {}).", + nice_name_str.get(), app_id); return; }