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
2 changes: 1 addition & 1 deletion daemon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ src/main/
├── utils/ # Context forgery, signature verification, and JNI bridges
├── Cli.kt # Command-line interface definitions
├── VectorDaemon.kt # Main entry point and looper initialization
└── VectorService.kt # Primary IDaemonService implementation
└── VectorService.kt # Primary IVectorDaemon implementation
```

## Concurrency and State Management
Expand Down
3 changes: 3 additions & 0 deletions daemon/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,8 @@
public static *** v(...);
public static *** d(...);
}
# The libxposed annotations are compile-only metadata and are not packaged
-dontwarn io.github.libxposed.annotation.**

-repackageclasses
-allowaccessmodification
20 changes: 12 additions & 8 deletions daemon/src/main/kotlin/org/matrix/vector/daemon/VectorService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import hidden.HiddenApiBridge
import io.github.libxposed.service.IXposedScopeCallback
import kotlinx.coroutines.launch
import org.lsposed.lspd.models.Application
import org.lsposed.lspd.service.IDaemonService
import org.lsposed.lspd.service.ILSPApplicationService
import org.matrix.vector.ipc.IVectorDaemon
import org.matrix.vector.ipc.IFrameworkService
import org.matrix.vector.daemon.data.ConfigCache
import org.matrix.vector.daemon.data.ModuleDatabase
import org.matrix.vector.daemon.data.PreferenceStore
Expand All @@ -29,7 +29,7 @@ import org.matrix.vector.daemon.system.*

private const val TAG = "VectorService"

object VectorService : IDaemonService.Stub() {
object VectorService : IVectorDaemon.Stub() {

private var bootCompleted = false

Expand Down Expand Up @@ -65,14 +65,14 @@ object VectorService : IDaemonService.Stub() {
}
}

override fun requestApplicationService(
override fun attachProcess(
uid: Int,
pid: Int,
processName: String,
heartBeat: IBinder
): ILSPApplicationService? {
): IFrameworkService? {
if (Binder.getCallingUid() != 1000) {
Log.w(TAG, "Unauthorized requestApplicationService call")
Log.w(TAG, "Unauthorized attachProcess call")
return null
}
if (ApplicationService.hasRegister(uid, pid)) return null
Expand Down Expand Up @@ -449,11 +449,15 @@ object VectorService : IDaemonService.Stub() {
when (action) {
"approve" -> {
val scopes = ModuleDatabase.getModuleScope(packageName) ?: mutableListOf()
if (scopes.none { it.packageName == scopePackageName && it.userId == userId }) {
// Compared against where the row will land, not against the user who asked: the
// framework is stored under user 0 whoever requested it, so for "system" this test
// never matched and every approval appended a duplicate and rewrote the whole table.
val storedUserId = if (scopePackageName == "system") 0 else userId
if (scopes.none { it.packageName == scopePackageName && it.userId == storedUserId }) {
scopes.add(
Application().apply {
this.packageName = scopePackageName
this.userId = userId
this.userId = storedUserId
})
ModuleDatabase.setModuleScope(packageName, scopes)
}
Expand Down
168 changes: 140 additions & 28 deletions daemon/src/main/kotlin/org/matrix/vector/daemon/data/ConfigCache.kt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.matrix.vector.daemon.data

import java.nio.file.Path
import org.lsposed.lspd.models.Module
import org.matrix.vector.ipc.LoadedModule
import org.matrix.vector.daemon.BuildConfig

data class ProcessScope(val processName: String, val uid: Int)
Expand All @@ -17,8 +17,8 @@ data class DaemonState(
val isCacheReady: Boolean = false,
val managerUid: Int = -1,
val miscPath: Path? = null,
val modules: Map<String, Module> = emptyMap(),
val scopes: Map<ProcessScope, List<Module>> = emptyMap(),
val modules: Map<String, LoadedModule> = emptyMap(),
val scopes: Map<ProcessScope, List<LoadedModule>> = emptyMap(),
/**
* Modules the user enabled that the framework could not load, and why.
*
Expand Down
24 changes: 17 additions & 7 deletions daemon/src/main/kotlin/org/matrix/vector/daemon/data/FileSystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
import kotlin.io.path.exists
import kotlin.io.path.isDirectory
import org.lsposed.lspd.models.PreLoadedApk
import org.matrix.vector.ipc.ModuleCode
import org.matrix.vector.daemon.BuildConfig
import org.matrix.vector.daemon.utils.ObfuscationManager

Expand All @@ -51,7 +51,7 @@ private const val TAG = "VectorFileSystem"
*/
sealed interface ModuleLoad {
/** Parsed, and ready to hand to a forking process. */
data class Loaded(val apk: PreLoadedApk) : ModuleLoad
data class Loaded(val apk: ModuleCode) : ModuleLoad

/** Declares libxposed API 100, and carries nothing else this framework can load. */
data object UnsupportedApi : ModuleLoad
Expand All @@ -61,7 +61,7 @@ sealed interface ModuleLoad {
}

/** The APK when it loaded and null when it did not, for callers with nothing to say about why. */
val ModuleLoad.apkOrNull: PreLoadedApk?
val ModuleLoad.apkOrNull: ModuleCode?
get() = (this as? ModuleLoad.Loaded)?.apk

object FileSystem {
Expand Down Expand Up @@ -234,12 +234,14 @@ object FileSystem {
val file = File(apkPath)
if (!file.exists()) return ModuleLoad.Unusable

val preLoadedApk = PreLoadedApk()
val preLoadedApk = ModuleCode()
val preLoadedDexes = mutableListOf<SharedMemory>()
val moduleClassNames = mutableListOf<String>()
val moduleLibraryNames = mutableListOf<String>()
var isLegacy = false
var exceptionPassthrough = false
var targetApiVersion = 0
var autoHotReload = false

runCatching {
ZipFile(file).use { zip ->
Expand All @@ -258,7 +260,9 @@ object FileSystem {
}
}

val targetApi = props.getProperty("targetApiVersion")?.trim()?.toIntOrNull() ?: 0
val targetApi = leadingInt(props.getProperty("targetApiVersion"))
targetApiVersion = targetApi
autoHotReload = props.getProperty("autoHotReload")?.trim().toBoolean()
// The module-wide mode ExceptionMode.DEFAULT resolves to. Anything that is not
// "passthrough" - absent, misspelled, or an explicit "protective" - keeps the
// protective default the API specifies.
Expand Down Expand Up @@ -342,6 +346,8 @@ object FileSystem {
this.moduleLibraryNames = moduleLibraryNames
this.legacy = isLegacy
this.exceptionPassthrough = exceptionPassthrough
this.targetApiVersion = targetApiVersion
this.autoHotReload = autoHotReload
}

return ModuleLoad.Loaded(preLoadedApk)
Expand Down Expand Up @@ -602,10 +608,10 @@ object FileSystem {
os.write("${scope.processName}/${scope.uid}\n".toByteArray())
modules.forEach { mod ->
os.write("\t${mod.packageName}\n".toByteArray())
mod.file?.moduleClassNames?.forEach { cn ->
mod.code?.moduleClassNames?.forEach { cn ->
os.write("\t\t$cn\n".toByteArray())
}
mod.file?.moduleLibraryNames?.forEach { ln ->
mod.code?.moduleLibraryNames?.forEach { ln ->
os.write("\t\t$ln\n".toByteArray())
}
}
Expand Down Expand Up @@ -669,4 +675,8 @@ object FileSystem {
createLogDirPath()
return logDirPath.resolve(getNewLogFileName("modules")).toFile()
}

// Matches the manager's leading-integer parsing, including values such as "101.0".
private fun leadingInt(value: String?): Int =
value?.trim()?.takeWhile { it.isDigit() }?.toIntOrNull() ?: 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,16 @@ object ModuleDatabase {

val values = ContentValues().apply { put("mid", mid) }
for (app in scope) {
// The system server is one process for the whole device, so it is stored against user 0
// whoever asked for it — a module in a work profile hooking the framework is hooking the
// same system_server as everyone else.
// A module is one package, one APK and one scope set for the whole device — Android cannot
// hold two different builds under one package name, so there is nothing here to key by
// user. What [Application.userId] names is the *target*: which installed instance of
// [Application.packageName] this row points at. `ConfigCache` refuses to expand a row whose
// user does not hold the module, which is what keeps a module installed for one user out of
// another user's processes.
//
// The system server is the exception and is stored against user 0 whoever asked for it: it
// is one process for the whole device belonging to no user, so a module in a work profile
// hooking the framework is hooking the same system_server as everyone else.
//
// Normalised rather than dropped, which is what this used to do. Dropping meant restoring
// a backup written by an older manager, which recorded the framework under the module's
Expand Down Expand Up @@ -321,7 +328,13 @@ object ModuleDatabase {
}

fun removeModuleScope(packageName: String, scopePackageName: String, userId: Int): Boolean {
if (packageName == "lspd" || (scopePackageName == "system" && userId != 0)) return false
if (packageName == "lspd") return false
// Normalised the same way [setModuleScope] normalises it, rather than refused. The framework is
// stored against user 0 whoever asked for it, so a removal keyed on the caller's own user
// matches no row at all - which meant a module outside user 0 could take system scope and then
// never give it back. It reached that state through the very same call: removeScope returns
// nothing, so the module was told its request had been honoured.
val storedUserId = if (scopePackageName == "system") 0 else userId
val db = dbHelper.writableDatabase
val mid =
db.compileStatement("SELECT mid FROM modules WHERE module_pkg_name = ?")
Expand All @@ -330,7 +343,7 @@ object ModuleDatabase {
db.delete(
"scope",
"mid = ? AND app_pkg_name = ? AND user_id = ?",
arrayOf(mid.toString(), scopePackageName, userId.toString()))
arrayOf(mid.toString(), scopePackageName, storedUserId.toString()))
ConfigCache.requestCacheUpdate()
return true
}
Expand Down
Loading
Loading