From 25c557248cab19f9761bbeb5a8ba110318d79d85 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Mon, 4 Nov 2019 14:32:28 -0500 Subject: [PATCH] Use ContentProvider call method for communication Previously, we use either BroadcastReceivers or Activities to receive messages from our native daemon, but both have their own downsides. Some OEMs blocks broadcasts if the app is not running in the background, regardless of who the caller is. Activities on the other hand, despite working 100% of the time, will steal the focus of the current foreground app, even though we are just doing some logging and showing a toast. In addition, since stubs for hiding Magisk Manager is introduced, our only communication method is left with the broadcast option, as only broadcasting allows targeting a specific package name, not a component name (which will be obfuscated in the case of stubs). To make sure root requests will work on all devices, Magisk had to do some experiments every boot to test whether broadcast is deliverable or not. This makes the whole thing even more complicated then ever. So lets take a look at another kind of component in Android apps: ContentProviders. It is a vital part of Android's ecosystem, and as far as I know no OEMs will block requests to ContentProviders (or else tons of functionality will break catastrophically). Starting at API 11, the system supports calling a specific method in ContentProviders, optionally sending extra data along with the method call. This is perfect for the native daemon to start a communication with Magisk Manager. Another cool thing is that we no longer need to know the component name of the reciever, as ContentProviders identify themselves with an "authority" name, which in Magisk Manager's case is tied to the package name. We already have a mechanism to keep track of our current manager package name, so this works out of the box. So yay! No more flaky broadcast tests, no more stupid OEMs blocking broadcasts for some bizzare reasons. This method should in theory work on almost all devices and situations. --- app/src/main/java/com/topjohnwu/magisk/App.kt | 2 + .../main/java/com/topjohnwu/magisk/Const.kt | 1 + .../main/java/com/topjohnwu/magisk/Hacks.kt | 5 - .../main/java/com/topjohnwu/magisk/Info.kt | 8 +- .../magisk/model/entity/MagiskPolicy.kt | 7 +- .../magisk/model/receiver/GeneralReceiver.kt | 57 +----- .../com/topjohnwu/magisk/ui/SplashActivity.kt | 5 +- .../magisk/ui/surequest/SuRequestActivity.kt | 21 +- .../com/topjohnwu/magisk/utils/PatchAPK.kt | 5 +- .../com/topjohnwu/magisk/utils/SuHandler.kt | 137 +++++++++++++ .../com/topjohnwu/magisk/utils/SuLogger.kt | 92 --------- native/jni/core/bootstages.cpp | 3 - native/jni/core/daemon.cpp | 8 - native/jni/core/magisk.cpp | 15 -- native/jni/include/daemon.h | 4 - native/jni/su/connect.cpp | 191 +++++------------- scripts/emulator.sh | 1 - .../com/topjohnwu/magisk/FileProvider.java | 10 + .../topjohnwu/magisk/ProviderCallHandler.java | 8 + 19 files changed, 237 insertions(+), 343 deletions(-) create mode 100644 app/src/main/java/com/topjohnwu/magisk/utils/SuHandler.kt delete mode 100644 app/src/main/java/com/topjohnwu/magisk/utils/SuLogger.kt create mode 100644 shared/src/main/java/com/topjohnwu/magisk/ProviderCallHandler.java diff --git a/app/src/main/java/com/topjohnwu/magisk/App.kt b/app/src/main/java/com/topjohnwu/magisk/App.kt index 391c2563f..c5d4ed0dc 100644 --- a/app/src/main/java/com/topjohnwu/magisk/App.kt +++ b/app/src/main/java/com/topjohnwu/magisk/App.kt @@ -16,6 +16,7 @@ import com.topjohnwu.magisk.di.koinModules import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.unwrap import com.topjohnwu.magisk.utils.RootInit +import com.topjohnwu.magisk.utils.SuHandler import com.topjohnwu.magisk.utils.updateConfig import com.topjohnwu.superuser.Shell import org.koin.android.ext.koin.androidContext @@ -34,6 +35,7 @@ open class App() : Application() { Shell.Config.verboseLogging(BuildConfig.DEBUG) Shell.Config.addInitializers(RootInit::class.java) Shell.Config.setTimeout(2) + FileProvider.callHandler = SuHandler Room.setFactory { when (it) { WorkDatabase::class.java -> WorkDatabase_Impl() diff --git a/app/src/main/java/com/topjohnwu/magisk/Const.kt b/app/src/main/java/com/topjohnwu/magisk/Const.kt index d4f1d7081..4ae5299fc 100644 --- a/app/src/main/java/com/topjohnwu/magisk/Const.kt +++ b/app/src/main/java/com/topjohnwu/magisk/Const.kt @@ -26,6 +26,7 @@ object Const { const val MIN_VERSION = "v18.0" const val MIN_VERCODE = 18000 const val CONNECT_MODE = 20100 + const val PROVIDER_CONNECT = 20102 } object ID { diff --git a/app/src/main/java/com/topjohnwu/magisk/Hacks.kt b/app/src/main/java/com/topjohnwu/magisk/Hacks.kt index 4f7abfe80..6703d2e6b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/Hacks.kt +++ b/app/src/main/java/com/topjohnwu/magisk/Hacks.kt @@ -14,8 +14,6 @@ import android.content.res.AssetManager import android.content.res.Configuration import android.content.res.Resources import androidx.annotation.RequiresApi -import androidx.annotation.StringRes -import com.topjohnwu.magisk.extensions.langTagToLocale import com.topjohnwu.magisk.model.download.DownloadService import com.topjohnwu.magisk.model.receiver.GeneralReceiver import com.topjohnwu.magisk.model.update.UpdateCheckService @@ -23,11 +21,8 @@ import com.topjohnwu.magisk.ui.MainActivity import com.topjohnwu.magisk.ui.SplashActivity import com.topjohnwu.magisk.ui.flash.FlashActivity import com.topjohnwu.magisk.ui.surequest.SuRequestActivity -import com.topjohnwu.magisk.utils.currentLocale -import com.topjohnwu.magisk.utils.defaultLocale import com.topjohnwu.magisk.utils.refreshLocale import com.topjohnwu.magisk.utils.updateConfig -import java.util.* fun AssetManager.addAssetPath(path: String) { DynAPK.addAssetPath(this, path) diff --git a/app/src/main/java/com/topjohnwu/magisk/Info.kt b/app/src/main/java/com/topjohnwu/magisk/Info.kt index 66c601a91..317a6357c 100644 --- a/app/src/main/java/com/topjohnwu/magisk/Info.kt +++ b/app/src/main/java/com/topjohnwu/magisk/Info.kt @@ -36,17 +36,13 @@ object Info { val str = ShellUtils.fastCmd("magisk -v").split(":".toRegex())[0] val code = ShellUtils.fastCmd("magisk -V").toInt() val hide = Shell.su("magiskhide --status").exec().isSuccess - var mode = -1 - if (code >= Const.Version.CONNECT_MODE) - mode = Shell.su("magisk --connect-mode").exec().code - Env(code, str, hide, mode) + Env(code, str, hide) }.getOrElse { Env() } class Env( code: Int = -1, val magiskVersionString: String = "", - hide: Boolean = false, - var connectionMode: Int = -1 + hide: Boolean = false ) { val magiskHide get() = Config.magiskHide val magiskVersionCode = when (code) { diff --git a/app/src/main/java/com/topjohnwu/magisk/model/entity/MagiskPolicy.kt b/app/src/main/java/com/topjohnwu/magisk/model/entity/MagiskPolicy.kt index 2a843b9b3..cf803c7b7 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/entity/MagiskPolicy.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/entity/MagiskPolicy.kt @@ -56,14 +56,15 @@ fun Map.toPolicy(pm: PackageManager): MagiskPolicy { } @Throws(PackageManager.NameNotFoundException::class) -fun Int.toPolicy(pm: PackageManager): MagiskPolicy { +fun Int.toPolicy(pm: PackageManager, policy: Int = INTERACTIVE): MagiskPolicy { val pkg = pm.getPackagesForUid(this)?.firstOrNull() ?: throw PackageManager.NameNotFoundException() val info = pm.getApplicationInfo(pkg, 0) return MagiskPolicy( uid = this, packageName = pkg, + policy = policy, applicationInfo = info, - appName = info.loadLabel(pm).toString() + appName = info.getLabel(pm) ) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/topjohnwu/magisk/model/receiver/GeneralReceiver.kt b/app/src/main/java/com/topjohnwu/magisk/model/receiver/GeneralReceiver.kt index 4c72c992c..90134a8eb 100644 --- a/app/src/main/java/com/topjohnwu/magisk/model/receiver/GeneralReceiver.kt +++ b/app/src/main/java/com/topjohnwu/magisk/model/receiver/GeneralReceiver.kt @@ -2,37 +2,25 @@ package com.topjohnwu.magisk.model.receiver import android.content.ContextWrapper import android.content.Intent -import android.os.Build.VERSION.SDK_INT -import com.topjohnwu.magisk.* +import com.topjohnwu.magisk.Config +import com.topjohnwu.magisk.Const +import com.topjohnwu.magisk.Info import com.topjohnwu.magisk.base.BaseReceiver import com.topjohnwu.magisk.data.database.PolicyDao -import com.topjohnwu.magisk.data.database.base.su import com.topjohnwu.magisk.extensions.reboot -import com.topjohnwu.magisk.extensions.startActivity -import com.topjohnwu.magisk.extensions.startActivityWithRoot import com.topjohnwu.magisk.model.download.DownloadService import com.topjohnwu.magisk.model.entity.ManagerJson import com.topjohnwu.magisk.model.entity.internal.Configuration import com.topjohnwu.magisk.model.entity.internal.DownloadSubject -import com.topjohnwu.magisk.ui.surequest.SuRequestActivity -import com.topjohnwu.magisk.utils.SuLogger -import com.topjohnwu.magisk.view.Notifications +import com.topjohnwu.magisk.utils.SuHandler import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.superuser.Shell import org.koin.core.inject -import timber.log.Timber open class GeneralReceiver : BaseReceiver() { private val policyDB: PolicyDao by inject() - companion object { - const val REQUEST = "request" - const val LOG = "log" - const val NOTIFY = "notify" - const val TEST = "test" - } - private fun getPkg(intent: Intent): String { return intent.data?.encodedSchemeSpecificPart.orEmpty() } @@ -40,46 +28,15 @@ open class GeneralReceiver : BaseReceiver() { override fun onReceive(context: ContextWrapper, intent: Intent?) { intent ?: return - // Debug messages - if (BuildConfig.DEBUG) { - Timber.d(intent.action) - intent.extras?.let { bundle -> - bundle.keySet().forEach { - Timber.d("[%s]=[%s]", it, bundle[it]) - } - } - } - when (intent.action ?: return) { Intent.ACTION_REBOOT -> { - when (val action = intent.getStringExtra("action") ?: return) { - REQUEST -> { - val i = context.intent() - .setAction(action) - .putExtra("socket", intent.getStringExtra("socket")) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - .addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) - if (SDK_INT >= 29) { - // Android Q does not allow starting activity from background - i.startActivityWithRoot() - } else { - i.startActivity(context) - } - } - LOG -> SuLogger.handleLogs(context, intent) - NOTIFY -> SuLogger.handleNotify(context, intent) - TEST -> { - val mode = intent.getIntExtra("mode", 1 shl 1) - if (mode > Info.env.connectionMode) - Info.env.connectionMode = mode - Shell.su("magisk --connect-mode $mode").submit() - } - } + SuHandler(context, intent.getStringExtra("action"), intent.extras) } - Intent.ACTION_PACKAGE_REPLACED -> + Intent.ACTION_PACKAGE_REPLACED -> { // This will only work pre-O if (Config.suReAuth) policyDB.delete(getPkg(intent)).blockingGet() + } Intent.ACTION_PACKAGE_FULLY_REMOVED -> { val pkg = getPkg(intent) policyDB.delete(pkg).blockingGet() diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt b/app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt index 1a0ddf02b..eabbd9c00 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt @@ -3,10 +3,13 @@ package com.topjohnwu.magisk.ui import android.app.Activity import android.content.Context import android.os.Bundle -import com.topjohnwu.magisk.* +import com.topjohnwu.magisk.BuildConfig +import com.topjohnwu.magisk.Config +import com.topjohnwu.magisk.intent import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Shortcuts +import com.topjohnwu.magisk.wrap import com.topjohnwu.superuser.Shell open class SplashActivity : Activity() { diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt b/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt index 4ce2a60be..75b395cac 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt @@ -10,8 +10,7 @@ import com.topjohnwu.magisk.databinding.ActivityRequestBinding import com.topjohnwu.magisk.model.entity.MagiskPolicy import com.topjohnwu.magisk.model.events.DieEvent import com.topjohnwu.magisk.model.events.ViewEvent -import com.topjohnwu.magisk.model.receiver.GeneralReceiver -import com.topjohnwu.magisk.utils.SuLogger +import com.topjohnwu.magisk.utils.SuHandler import org.koin.androidx.viewmodel.ext.android.viewModel open class SuRequestActivity : BaseActivity() { @@ -29,19 +28,13 @@ open class SuRequestActivity : BaseActivity { - if (!viewModel.handleRequest(intent)) - finish() - return - } - GeneralReceiver.LOG -> SuLogger.handleLogs(this, intent) - GeneralReceiver.NOTIFY -> SuLogger.handleNotify(this, intent) + if (intent?.action == SuHandler.REQUEST) { + if (!viewModel.handleRequest(intent)) + finish() + } else { + SuHandler(this, intent.action, intent.extras) + finish() } - - finish() } override fun onEventDispatched(event: ViewEvent) { diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt b/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt index ebc4a8c94..511409a7d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt @@ -77,8 +77,9 @@ object PatchAPK { } private fun patchAndHide(context: Context, label: String): Boolean { - // If not running as stub, and we are compatible with stub, use stub - val src = if (!isRunningAsStub && SDK_INT >= 28 && Info.env.connectionMode == 3) { + val src = if (!isRunningAsStub && SDK_INT >= 28 && + Info.env.magiskVersionCode >= Const.Version.PROVIDER_CONNECT) { + // If not running as stub, and we are compatible with stub, use stub val stub = File(context.cacheDir, "stub.apk") val svc = get() runCatching { diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/SuHandler.kt b/app/src/main/java/com/topjohnwu/magisk/utils/SuHandler.kt new file mode 100644 index 000000000..ba6521c32 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/utils/SuHandler.kt @@ -0,0 +1,137 @@ +package com.topjohnwu.magisk.utils + +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.os.Process +import android.widget.Toast +import com.topjohnwu.magisk.* +import com.topjohnwu.magisk.data.repository.LogRepository +import com.topjohnwu.magisk.extensions.get +import com.topjohnwu.magisk.extensions.startActivity +import com.topjohnwu.magisk.extensions.startActivityWithRoot +import com.topjohnwu.magisk.extensions.subscribeK +import com.topjohnwu.magisk.model.entity.MagiskPolicy +import com.topjohnwu.magisk.model.entity.toLog +import com.topjohnwu.magisk.model.entity.toPolicy +import com.topjohnwu.magisk.ui.surequest.SuRequestActivity +import com.topjohnwu.superuser.Shell +import timber.log.Timber +import java.util.* + +object SuHandler : ProviderCallHandler { + + const val REQUEST = "request" + const val LOG = "log" + const val NOTIFY = "notify" + const val TEST = "test" + + override fun call(context: Context, method: String, arg: String?, extras: Bundle?): Bundle? { + invoke(context.wrap(), method, extras) + return null + } + + operator fun invoke(context: Context, action: String?, data: Bundle?) { + data ?: return + + // Debug messages + if (BuildConfig.DEBUG) { + Timber.d(action) + data.let { bundle -> + bundle.keySet().forEach { + Timber.d("[%s]=[%s]", it, bundle[it]) + } + } + } + + when (action) { + REQUEST -> { + val intent = context.intent() + .setAction(action) + .putExtras(data) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) + if (Build.VERSION.SDK_INT >= 29) { + // Android Q does not allow starting activity from background + intent.startActivityWithRoot() + } else { + intent.startActivity(context) + } + } + LOG -> handleLogs(context, data) + NOTIFY -> handleNotify(context, data) + TEST -> { + val mode = data.getInt("mode", 2) + Shell.su( + "magisk --connect-mode $mode", + "magisk --use-broadcast" + ).submit() + } + } + } + + private fun Any?.toInt(): Int? { + return when (this) { + is Int -> this + is Long -> this.toInt() + else -> null + } + } + + private fun handleLogs(context: Context, data: Bundle) { + val fromUid = data["from.uid"].toInt() ?: return + if (fromUid == Process.myUid()) + return + + val pm = context.packageManager + + val notify = data.getBoolean("notify", true) + val allow = data["policy"].toInt() ?: return + + val policy = runCatching { fromUid.toPolicy(pm, allow) }.getOrElse { return } + + if (notify) + notify(context, policy) + + val toUid = data["to.uid"].toInt() ?: return + val pid = data["pid"].toInt() ?: return + + val command = data.getString("command") ?: return + val log = policy.toLog( + toUid = toUid, + fromPid = pid, + command = command, + date = Date() + ) + + val logRepo = get() + logRepo.put(log).subscribeK(onError = { Timber.e(it) }) + } + + private fun handleNotify(context: Context, data: Bundle) { + val fromUid = data["from.uid"].toInt() ?: return + if (fromUid == Process.myUid()) + return + + val pm = context.packageManager + val allow = data["policy"].toInt() ?: return + + runCatching { + val policy = fromUid.toPolicy(pm, allow) + if (policy.policy >= 0) + notify(context, policy) + } + } + + private fun notify(context: Context, policy: MagiskPolicy) { + if (policy.notification && Config.suNotification == Config.Value.NOTIFICATION_TOAST) { + val resId = if (policy.policy == MagiskPolicy.ALLOW) + R.string.su_allow_toast + else + R.string.su_deny_toast + + Utils.toast(context.getString(resId, policy.appName), Toast.LENGTH_SHORT) + } + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/SuLogger.kt b/app/src/main/java/com/topjohnwu/magisk/utils/SuLogger.kt deleted file mode 100644 index 98b201235..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/utils/SuLogger.kt +++ /dev/null @@ -1,92 +0,0 @@ -package com.topjohnwu.magisk.utils - -import android.content.Context -import android.content.Intent -import android.os.Process -import android.widget.Toast -import com.topjohnwu.magisk.Config -import com.topjohnwu.magisk.R -import com.topjohnwu.magisk.data.database.PolicyDao -import com.topjohnwu.magisk.data.repository.LogRepository -import com.topjohnwu.magisk.extensions.get -import com.topjohnwu.magisk.model.entity.MagiskPolicy -import com.topjohnwu.magisk.model.entity.toLog -import com.topjohnwu.magisk.model.entity.toPolicy -import java.util.* - -object SuLogger { - - fun handleLogs(context: Context, intent: Intent) { - - val fromUid = intent.getIntExtra("from.uid", -1) - if (fromUid < 0) return - if (fromUid == Process.myUid()) return - - val pm = context.packageManager - - val notify: Boolean - val data = intent.extras - val policy: MagiskPolicy = if (data!!.containsKey("notify")) { - notify = data.getBoolean("notify") - runCatching { - fromUid.toPolicy(pm) - }.getOrElse { return } - } else { - // Doesn't report whether notify or not, check database ourselves - val policyDB = get() - val policy = policyDB.fetch(fromUid).blockingGet() ?: return - notify = policy.notification - policy - }.copy(policy = data.getInt("policy", -1)) - - if (policy.policy < 0) - return - - if (notify) - handleNotify(context, policy) - - val toUid = intent.getIntExtra("to.uid", -1) - if (toUid < 0) return - - val pid = intent.getIntExtra("pid", -1) - if (pid < 0) return - - val command = intent.getStringExtra("command") ?: return - val log = policy.toLog( - toUid = toUid, - fromPid = pid, - command = command, - date = Date() - ) - - val logRepo = get() - logRepo.put(log).blockingGet()?.printStackTrace() - } - - private fun handleNotify(context: Context, policy: MagiskPolicy) { - if (policy.notification && Config.suNotification == Config.Value.NOTIFICATION_TOAST) { - Utils.toast( - context.getString( - if (policy.policy == MagiskPolicy.ALLOW) - R.string.su_allow_toast - else - R.string.su_deny_toast, policy.appName - ), - Toast.LENGTH_SHORT - ) - } - } - - fun handleNotify(context: Context, intent: Intent) { - val fromUid = intent.getIntExtra("from.uid", -1) - if (fromUid < 0) return - if (fromUid == Process.myUid()) return - runCatching { - val pm = context.packageManager - val policy = fromUid.toPolicy(pm) - .copy(policy = intent.getIntExtra("policy", -1)) - if (policy.policy >= 0) - handleNotify(context, policy) - } - } -} diff --git a/native/jni/core/bootstages.cpp b/native/jni/core/bootstages.cpp index 9c57c776e..9e6f10c97 100644 --- a/native/jni/core/bootstages.cpp +++ b/native/jni/core/bootstages.cpp @@ -800,7 +800,4 @@ void boot_complete(int client) { install_apk("/data/magisk.apk"); } } - - // Test whether broadcast can be used or not - broadcast_test(); } diff --git a/native/jni/core/daemon.cpp b/native/jni/core/daemon.cpp index 2948b1497..49a22a419 100644 --- a/native/jni/core/daemon.cpp +++ b/native/jni/core/daemon.cpp @@ -46,8 +46,6 @@ static void *request_handler(void *args) { case LATE_START: case BOOT_COMPLETE: case SQLITE_CMD: - case BROADCAST_ACK: - case BROADCAST_TEST: if (credential.uid != 0) { write_int(client, ROOT_REQUIRED); close(client); @@ -84,12 +82,6 @@ static void *request_handler(void *args) { case SQLITE_CMD: exec_sql(client); break; - case BROADCAST_ACK: - broadcast_ack(client); - break; - case BROADCAST_TEST: - broadcast_test(client); - break; case REMOVE_MODULES: if (credential.uid == UID_SHELL || credential.uid == UID_ROOT) { remove_modules(); diff --git a/native/jni/core/magisk.cpp b/native/jni/core/magisk.cpp index 821e53be1..79b5a65c5 100644 --- a/native/jni/core/magisk.cpp +++ b/native/jni/core/magisk.cpp @@ -35,8 +35,6 @@ Advanced Options (Internal APIs): --clone-attr SRC DEST clone permission, owner, and selinux context --clone SRC DEST clone SRC to DEST --sqlite SQL exec SQL commands to Magisk database - --connect-mode [MODE] get/set connect mode for su request and notify - --broadcast-test manually trigger broadcast tests Supported init triggers: post-fs-data, service, boot-complete @@ -113,23 +111,10 @@ int magisk_main(int argc, char *argv[]) { printf("%s\n", res); free(res); } - } else if (argv[1] == "--connect-mode"sv) { - int fd = connect_daemon(); - write_int(fd, BROADCAST_ACK); - if (argc >= 3) { - write_int(fd, parse_int(argv[2])); - } else { - write_int(fd, -1); - } - return read_int(fd); } else if (argv[1] == "--remove-modules"sv) { int fd = connect_daemon(); write_int(fd, REMOVE_MODULES); return read_int(fd); - } else if (argv[1] == "--broadcast-test"sv) { - int fd = connect_daemon(); - write_int(fd, BROADCAST_TEST); - return read_int(fd); } #if 0 /* Entry point for testing stuffs */ diff --git a/native/jni/include/daemon.h b/native/jni/include/daemon.h index 6af462f43..71ac92b9b 100644 --- a/native/jni/include/daemon.h +++ b/native/jni/include/daemon.h @@ -17,9 +17,7 @@ enum { BOOT_COMPLETE, MAGISKHIDE, SQLITE_CMD, - BROADCAST_ACK, REMOVE_MODULES, - BROADCAST_TEST, }; // Return codes for daemon @@ -84,8 +82,6 @@ void magiskhide_handler(int client); *************/ void su_daemon_handler(int client, struct ucred *credential); -void broadcast_test(int client = -1); -void broadcast_ack(int client); /********************* * Daemon Global Vars diff --git a/native/jni/su/connect.cpp b/native/jni/su/connect.cpp index f83cf2c9c..a5789f45d 100644 --- a/native/jni/su/connect.cpp +++ b/native/jni/su/connect.cpp @@ -13,46 +13,28 @@ using namespace std; -enum connect_mode { - UNINITIALIZED = 0, - MODE_ACTIVITY, - MODE_BROADCAST_COMPONENT, - MODE_BROADCAST_PACKAGE -}; +#define CALL_PROVIDER \ +"/system/bin/app_process", "/system/bin", "com.android.commands.content.Content", \ +"call", "--uri", nullptr, "--user", nullptr, "--method" -static connect_mode current_mode = UNINITIALIZED; - -#define START_ACTIVITY \ -"/system/bin/app_process", "/system/bin", "com.android.commands.am.Am", \ -"start", "-n", nullptr, "--user", nullptr, "-f", "0x18000020", "-a" - -// 0x18000020 = FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_MULTIPLE_TASK|FLAG_INCLUDE_STOPPED_PACKAGES - -#define START_BROADCAST \ -"/system/bin/app_process", "/system/bin", "com.android.commands.am.Am", \ -"broadcast", "-n", nullptr, "--user", nullptr, "-f", "0x00000020", \ -"-a", "android.intent.action.REBOOT", "--es", "action" - -#define START_BROADCAST_PKG \ -"/system/bin/app_process", "/system/bin", "com.android.commands.am.Am", \ -"broadcast", "-p", nullptr, "--user", nullptr, "-f", "0x00000020", \ -"-a", "android.intent.action.REBOOT", "--es", "action" - -// 0x00000020 = FLAG_INCLUDE_STOPPED_PACKAGES - -#define am_app_info(info, ...) \ -if (current_mode == MODE_BROADCAST_PACKAGE) { \ - const char *cmd[] = { START_BROADCAST_PKG, __VA_ARGS__, nullptr }; \ - exec_am_cmd(cmd, info); \ -} else if (current_mode == MODE_BROADCAST_COMPONENT) { \ - const char *cmd[] = { START_BROADCAST, __VA_ARGS__, nullptr }; \ - exec_am_cmd(cmd, info); \ -} else { \ - const char *cmd[] = { START_ACTIVITY, __VA_ARGS__, nullptr }; \ - exec_am_cmd(cmd, info); \ +#define content_exec_info(info, ...) {\ + const char *cmd[] = { CALL_PROVIDER, __VA_ARGS__, nullptr }; \ + exec_content_cmd(cmd, info); \ } -#define am_app(...) am_app_info(ctx.info.get(), __VA_ARGS__) +#define content_exec(...) content_exec_info(ctx.info.get(), __VA_ARGS__) + +#define ex(s) "--extra", s + +#define get_user(info) \ +(info->cfg[SU_MULTIUSER_MODE] == MULTIUSER_MODE_USER \ +? info->uid / 100000 \ +: 0) + +#define get_uid(info) \ +(info->cfg[SU_MULTIUSER_MODE] == MULTIUSER_MODE_OWNER_MANAGED \ +? info->uid % 100000 \ +: info->uid) static const char *get_command(const su_request *to) { if (to->command[0]) @@ -62,48 +44,22 @@ static const char *get_command(const su_request *to) { return DEFAULT_SHELL; } -static void get_user(char *user, const su_info *info) { - sprintf(user, "%d", - info->cfg[SU_MULTIUSER_MODE] == MULTIUSER_MODE_USER - ? info->uid / 100000 - : 0); -} - -static void get_uid(char *uid, const su_info *info) { - sprintf(uid, "%d", - info->cfg[SU_MULTIUSER_MODE] == MULTIUSER_MODE_OWNER_MANAGED - ? info->uid % 100000 - : info->uid); -} - -static void exec_am_cmd(const char **args, const su_info *info) { +static void exec_content_cmd(const char **args, const su_info *info) { char target[128]; - if (args[3][0] == 'b') { - // Broadcast - if (args[4][1] == 'p') { - // Broadcast to package (receiver can be obfuscated) - strcpy(target, info->str[SU_MANAGER].data()); - } else { - // a.h is the broadcast receiver - sprintf(target, "%s/a.h", info->str[SU_MANAGER].data()); - } - } else { - // a.m is the activity - sprintf(target, "%s/a.m", info->str[SU_MANAGER].data()); - } - char user[8]; - get_user(user, info); + sprintf(target, "content://%s.provider", info->str[SU_MANAGER].data()); + char user[4]; + sprintf(user, "%d", get_user(info)); // Fill in non static arguments args[5] = target; args[7] = user; exec_t exec { - .pre_exec = []() -> void { + .pre_exec = [] { int null = xopen("/dev/null", O_WRONLY | O_CLOEXEC); dup2(null, STDOUT_FILENO); dup2(null, STDERR_FILENO); - setenv("CLASSPATH", "/system/framework/am.jar", 1); + setenv("CLASSPATH", "/system/framework/content.jar", 1); }, .fork = fork_dont_care, .argv = args @@ -113,94 +69,51 @@ static void exec_am_cmd(const char **args, const su_info *info) { #define LOG_BODY \ "log", \ -"--ei", "from.uid", fromUid, \ -"--ei", "to.uid", toUid, \ -"--ei", "pid", pid, \ -"--ei", "policy", policy, \ -"--es", "command", get_command(&ctx.req), \ -"--ez", "notify", ctx.info->access.notify ? "true" : "false" +ex(fromUid), ex(toUid), ex(pid), ex(policy), \ +ex(command.data()), ex(notify) void app_log(const su_context &ctx) { - char fromUid[8]; - get_uid(fromUid, ctx.info.get()); + char fromUid[16]; + sprintf(fromUid, "from.uid:i:%d", get_uid(ctx.info)); - char toUid[8]; - sprintf(toUid, "%d", ctx.req.uid); + char toUid[16]; + sprintf(toUid, "to.uid:i:%d", ctx.req.uid); - char pid[8]; - sprintf(pid, "%d", ctx.pid); + char pid[16]; + sprintf(pid, "pid:i:%d", ctx.pid); - char policy[2]; - sprintf(policy, "%d", ctx.info->access.policy); + char policy[16]; + sprintf(policy, "policy:i:%d", ctx.info->access.policy); - am_app(LOG_BODY) + string command("command:s:"); + command += get_command(&ctx.req); + + char notify[16]; + sprintf(notify, "notify:b:%s", ctx.info->access.notify ? "true" : "false"); + + content_exec(LOG_BODY) } #define NOTIFY_BODY \ -"notify", \ -"--ei", "from.uid", fromUid, \ -"--ei", "policy", policy +"notify", ex(fromUid), ex(policy) void app_notify(const su_context &ctx) { - char fromUid[8]; - get_uid(fromUid, ctx.info.get()); + char fromUid[16]; + sprintf(fromUid, "from.uid:i:%d", get_uid(ctx.info)); - char policy[2]; - sprintf(policy, "%d", ctx.info->access.policy); + char policy[16]; + sprintf(policy, "policy:i:%d", ctx.info->access.policy); - am_app(NOTIFY_BODY) + content_exec(NOTIFY_BODY) } #define SOCKET_BODY \ -"request", \ -"--es", "socket", socket +"request", ex(sock) void app_socket(const char *socket, const shared_ptr &info) { - am_app_info(info.get(), SOCKET_BODY) -} - -#define TEST_BODY \ -"test", "--ei", "mode", mode, nullptr - -void broadcast_test(int client) { - if (client >= 0) { - // Make it not uninitialized - current_mode = MODE_ACTIVITY; - write_int(client, 0); - close(client); - } - - su_info info; - get_db_settings(info.cfg); - get_db_strings(info.str); - validate_manager(info.str[SU_MANAGER], 0, &info.mgr_st); - - char mode[2]; - { - sprintf(mode, "%d", MODE_BROADCAST_PACKAGE); - const char *cmd[] = { START_BROADCAST_PKG, TEST_BODY }; - exec_am_cmd(cmd, &info); - } - { - sprintf(mode, "%d", MODE_BROADCAST_COMPONENT); - const char *cmd[] = { START_BROADCAST, TEST_BODY }; - exec_am_cmd(cmd, &info); - } -} - -void broadcast_ack(int client) { - int mode = read_int(client); - if (mode < 0) { - // Return connection mode to client - write_int(client, current_mode); - } else { - if (mode > current_mode) { - LOGD("* Use connect mode [%d] for su request and notify\n", mode); - current_mode = static_cast(mode); - } - write_int(client, 0); - } - close(client); + char sock[128]; + sprintf(sock, "socket:s:%s", socket); + content_exec_info(info.get(), SOCKET_BODY) } void socket_send_request(int fd, const shared_ptr &info) { diff --git a/scripts/emulator.sh b/scripts/emulator.sh index a01e0f040..ceb3e8388 100755 --- a/scripts/emulator.sh +++ b/scripts/emulator.sh @@ -118,4 +118,3 @@ mkdir -p /data/adb/modules 2>/dev/null mkdir /data/adb/post-fs-data.d 2>/dev/null mkdir /data/adb/services.d 2>/dev/null /sbin/magisk --daemon -/sbin/magisk --broadcast-test diff --git a/shared/src/main/java/com/topjohnwu/magisk/FileProvider.java b/shared/src/main/java/com/topjohnwu/magisk/FileProvider.java index 9b7ea0a73..8ce8d2de0 100644 --- a/shared/src/main/java/com/topjohnwu/magisk/FileProvider.java +++ b/shared/src/main/java/com/topjohnwu/magisk/FileProvider.java @@ -10,6 +10,7 @@ import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.os.Build; +import android.os.Bundle; import android.os.Environment; import android.os.ParcelFileDescriptor; import android.provider.OpenableColumns; @@ -54,6 +55,7 @@ public class FileProvider extends ContentProvider { private PathStrategy mStrategy; + public static ProviderCallHandler callHandler; @Override public boolean onCreate() { @@ -156,6 +158,14 @@ public class FileProvider extends ContentProvider { return file.delete() ? 1 : 0; } + + @Override + public Bundle call(String method, String arg, Bundle extras) { + if (callHandler != null) + return callHandler.call(getContext(), method, arg, extras); + return null; + } + @Override public ParcelFileDescriptor openFile(Uri uri, String mode) diff --git a/shared/src/main/java/com/topjohnwu/magisk/ProviderCallHandler.java b/shared/src/main/java/com/topjohnwu/magisk/ProviderCallHandler.java new file mode 100644 index 000000000..c3ff5c447 --- /dev/null +++ b/shared/src/main/java/com/topjohnwu/magisk/ProviderCallHandler.java @@ -0,0 +1,8 @@ +package com.topjohnwu.magisk; + +import android.content.Context; +import android.os.Bundle; + +public interface ProviderCallHandler { + Bundle call(Context context, String method, String arg, Bundle extras); +}