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.
This commit is contained in:
topjohnwu 2019-11-04 14:32:28 -05:00
parent 472cde29b8
commit 25c557248c
19 changed files with 237 additions and 343 deletions

View File

@ -16,6 +16,7 @@ import com.topjohnwu.magisk.di.koinModules
import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.unwrap import com.topjohnwu.magisk.extensions.unwrap
import com.topjohnwu.magisk.utils.RootInit import com.topjohnwu.magisk.utils.RootInit
import com.topjohnwu.magisk.utils.SuHandler
import com.topjohnwu.magisk.utils.updateConfig import com.topjohnwu.magisk.utils.updateConfig
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
@ -34,6 +35,7 @@ open class App() : Application() {
Shell.Config.verboseLogging(BuildConfig.DEBUG) Shell.Config.verboseLogging(BuildConfig.DEBUG)
Shell.Config.addInitializers(RootInit::class.java) Shell.Config.addInitializers(RootInit::class.java)
Shell.Config.setTimeout(2) Shell.Config.setTimeout(2)
FileProvider.callHandler = SuHandler
Room.setFactory { Room.setFactory {
when (it) { when (it) {
WorkDatabase::class.java -> WorkDatabase_Impl() WorkDatabase::class.java -> WorkDatabase_Impl()

View File

@ -26,6 +26,7 @@ object Const {
const val MIN_VERSION = "v18.0" const val MIN_VERSION = "v18.0"
const val MIN_VERCODE = 18000 const val MIN_VERCODE = 18000
const val CONNECT_MODE = 20100 const val CONNECT_MODE = 20100
const val PROVIDER_CONNECT = 20102
} }
object ID { object ID {

View File

@ -14,8 +14,6 @@ import android.content.res.AssetManager
import android.content.res.Configuration import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import androidx.annotation.RequiresApi 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.download.DownloadService
import com.topjohnwu.magisk.model.receiver.GeneralReceiver import com.topjohnwu.magisk.model.receiver.GeneralReceiver
import com.topjohnwu.magisk.model.update.UpdateCheckService 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.SplashActivity
import com.topjohnwu.magisk.ui.flash.FlashActivity import com.topjohnwu.magisk.ui.flash.FlashActivity
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity 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.refreshLocale
import com.topjohnwu.magisk.utils.updateConfig import com.topjohnwu.magisk.utils.updateConfig
import java.util.*
fun AssetManager.addAssetPath(path: String) { fun AssetManager.addAssetPath(path: String) {
DynAPK.addAssetPath(this, path) DynAPK.addAssetPath(this, path)

View File

@ -36,17 +36,13 @@ object Info {
val str = ShellUtils.fastCmd("magisk -v").split(":".toRegex())[0] val str = ShellUtils.fastCmd("magisk -v").split(":".toRegex())[0]
val code = ShellUtils.fastCmd("magisk -V").toInt() val code = ShellUtils.fastCmd("magisk -V").toInt()
val hide = Shell.su("magiskhide --status").exec().isSuccess val hide = Shell.su("magiskhide --status").exec().isSuccess
var mode = -1 Env(code, str, hide)
if (code >= Const.Version.CONNECT_MODE)
mode = Shell.su("magisk --connect-mode").exec().code
Env(code, str, hide, mode)
}.getOrElse { Env() } }.getOrElse { Env() }
class Env( class Env(
code: Int = -1, code: Int = -1,
val magiskVersionString: String = "", val magiskVersionString: String = "",
hide: Boolean = false, hide: Boolean = false
var connectionMode: Int = -1
) { ) {
val magiskHide get() = Config.magiskHide val magiskHide get() = Config.magiskHide
val magiskVersionCode = when (code) { val magiskVersionCode = when (code) {

View File

@ -56,14 +56,15 @@ fun Map<String, String>.toPolicy(pm: PackageManager): MagiskPolicy {
} }
@Throws(PackageManager.NameNotFoundException::class) @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() val pkg = pm.getPackagesForUid(this)?.firstOrNull()
?: throw PackageManager.NameNotFoundException() ?: throw PackageManager.NameNotFoundException()
val info = pm.getApplicationInfo(pkg, 0) val info = pm.getApplicationInfo(pkg, 0)
return MagiskPolicy( return MagiskPolicy(
uid = this, uid = this,
packageName = pkg, packageName = pkg,
policy = policy,
applicationInfo = info, applicationInfo = info,
appName = info.loadLabel(pm).toString() appName = info.getLabel(pm)
) )
} }

View File

@ -2,37 +2,25 @@ package com.topjohnwu.magisk.model.receiver
import android.content.ContextWrapper import android.content.ContextWrapper
import android.content.Intent import android.content.Intent
import android.os.Build.VERSION.SDK_INT import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.* import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.base.BaseReceiver import com.topjohnwu.magisk.base.BaseReceiver
import com.topjohnwu.magisk.data.database.PolicyDao 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.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.download.DownloadService
import com.topjohnwu.magisk.model.entity.ManagerJson import com.topjohnwu.magisk.model.entity.ManagerJson
import com.topjohnwu.magisk.model.entity.internal.Configuration import com.topjohnwu.magisk.model.entity.internal.Configuration
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity import com.topjohnwu.magisk.utils.SuHandler
import com.topjohnwu.magisk.utils.SuLogger
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import org.koin.core.inject import org.koin.core.inject
import timber.log.Timber
open class GeneralReceiver : BaseReceiver() { open class GeneralReceiver : BaseReceiver() {
private val policyDB: PolicyDao by inject() 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 { private fun getPkg(intent: Intent): String {
return intent.data?.encodedSchemeSpecificPart.orEmpty() return intent.data?.encodedSchemeSpecificPart.orEmpty()
} }
@ -40,46 +28,15 @@ open class GeneralReceiver : BaseReceiver() {
override fun onReceive(context: ContextWrapper, intent: Intent?) { override fun onReceive(context: ContextWrapper, intent: Intent?) {
intent ?: return 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) { when (intent.action ?: return) {
Intent.ACTION_REBOOT -> { Intent.ACTION_REBOOT -> {
when (val action = intent.getStringExtra("action") ?: return) { SuHandler(context, intent.getStringExtra("action"), intent.extras)
REQUEST -> {
val i = context.intent<SuRequestActivity>()
.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()
}
}
} }
Intent.ACTION_PACKAGE_REPLACED -> Intent.ACTION_PACKAGE_REPLACED -> {
// This will only work pre-O // This will only work pre-O
if (Config.suReAuth) if (Config.suReAuth)
policyDB.delete(getPkg(intent)).blockingGet() policyDB.delete(getPkg(intent)).blockingGet()
}
Intent.ACTION_PACKAGE_FULLY_REMOVED -> { Intent.ACTION_PACKAGE_FULLY_REMOVED -> {
val pkg = getPkg(intent) val pkg = getPkg(intent)
policyDB.delete(pkg).blockingGet() policyDB.delete(pkg).blockingGet()

View File

@ -3,10 +3,13 @@ package com.topjohnwu.magisk.ui
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.os.Bundle 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.utils.Utils
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.magisk.wrap
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
open class SplashActivity : Activity() { open class SplashActivity : Activity() {

View File

@ -10,8 +10,7 @@ import com.topjohnwu.magisk.databinding.ActivityRequestBinding
import com.topjohnwu.magisk.model.entity.MagiskPolicy import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.events.DieEvent import com.topjohnwu.magisk.model.events.DieEvent
import com.topjohnwu.magisk.model.events.ViewEvent import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.model.receiver.GeneralReceiver import com.topjohnwu.magisk.utils.SuHandler
import com.topjohnwu.magisk.utils.SuLogger
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestBinding>() { open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestBinding>() {
@ -29,19 +28,13 @@ open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestB
lockOrientation() lockOrientation()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val intent = intent if (intent?.action == SuHandler.REQUEST) {
if (!viewModel.handleRequest(intent))
when (intent?.action) { finish()
GeneralReceiver.REQUEST -> { } else {
if (!viewModel.handleRequest(intent)) SuHandler(this, intent.action, intent.extras)
finish() finish()
return
}
GeneralReceiver.LOG -> SuLogger.handleLogs(this, intent)
GeneralReceiver.NOTIFY -> SuLogger.handleNotify(this, intent)
} }
finish()
} }
override fun onEventDispatched(event: ViewEvent) { override fun onEventDispatched(event: ViewEvent) {

View File

@ -77,8 +77,9 @@ object PatchAPK {
} }
private fun patchAndHide(context: Context, label: String): Boolean { 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 &&
val src = if (!isRunningAsStub && SDK_INT >= 28 && Info.env.connectionMode == 3) { 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 stub = File(context.cacheDir, "stub.apk")
val svc = get<GithubRawServices>() val svc = get<GithubRawServices>()
runCatching { runCatching {

View File

@ -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<SuRequestActivity>()
.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<LogRepository>()
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)
}
}
}

View File

@ -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<PolicyDao>()
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<LogRepository>()
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)
}
}
}

View File

@ -800,7 +800,4 @@ void boot_complete(int client) {
install_apk("/data/magisk.apk"); install_apk("/data/magisk.apk");
} }
} }
// Test whether broadcast can be used or not
broadcast_test();
} }

View File

@ -46,8 +46,6 @@ static void *request_handler(void *args) {
case LATE_START: case LATE_START:
case BOOT_COMPLETE: case BOOT_COMPLETE:
case SQLITE_CMD: case SQLITE_CMD:
case BROADCAST_ACK:
case BROADCAST_TEST:
if (credential.uid != 0) { if (credential.uid != 0) {
write_int(client, ROOT_REQUIRED); write_int(client, ROOT_REQUIRED);
close(client); close(client);
@ -84,12 +82,6 @@ static void *request_handler(void *args) {
case SQLITE_CMD: case SQLITE_CMD:
exec_sql(client); exec_sql(client);
break; break;
case BROADCAST_ACK:
broadcast_ack(client);
break;
case BROADCAST_TEST:
broadcast_test(client);
break;
case REMOVE_MODULES: case REMOVE_MODULES:
if (credential.uid == UID_SHELL || credential.uid == UID_ROOT) { if (credential.uid == UID_SHELL || credential.uid == UID_ROOT) {
remove_modules(); remove_modules();

View File

@ -35,8 +35,6 @@ Advanced Options (Internal APIs):
--clone-attr SRC DEST clone permission, owner, and selinux context --clone-attr SRC DEST clone permission, owner, and selinux context
--clone SRC DEST clone SRC to DEST --clone SRC DEST clone SRC to DEST
--sqlite SQL exec SQL commands to Magisk database --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: Supported init triggers:
post-fs-data, service, boot-complete post-fs-data, service, boot-complete
@ -113,23 +111,10 @@ int magisk_main(int argc, char *argv[]) {
printf("%s\n", res); printf("%s\n", res);
free(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) { } else if (argv[1] == "--remove-modules"sv) {
int fd = connect_daemon(); int fd = connect_daemon();
write_int(fd, REMOVE_MODULES); write_int(fd, REMOVE_MODULES);
return read_int(fd); 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 #if 0
/* Entry point for testing stuffs */ /* Entry point for testing stuffs */

View File

@ -17,9 +17,7 @@ enum {
BOOT_COMPLETE, BOOT_COMPLETE,
MAGISKHIDE, MAGISKHIDE,
SQLITE_CMD, SQLITE_CMD,
BROADCAST_ACK,
REMOVE_MODULES, REMOVE_MODULES,
BROADCAST_TEST,
}; };
// Return codes for daemon // Return codes for daemon
@ -84,8 +82,6 @@ void magiskhide_handler(int client);
*************/ *************/
void su_daemon_handler(int client, struct ucred *credential); void su_daemon_handler(int client, struct ucred *credential);
void broadcast_test(int client = -1);
void broadcast_ack(int client);
/********************* /*********************
* Daemon Global Vars * Daemon Global Vars

View File

@ -13,46 +13,28 @@
using namespace std; using namespace std;
enum connect_mode { #define CALL_PROVIDER \
UNINITIALIZED = 0, "/system/bin/app_process", "/system/bin", "com.android.commands.content.Content", \
MODE_ACTIVITY, "call", "--uri", nullptr, "--user", nullptr, "--method"
MODE_BROADCAST_COMPONENT,
MODE_BROADCAST_PACKAGE
};
static connect_mode current_mode = UNINITIALIZED; #define content_exec_info(info, ...) {\
const char *cmd[] = { CALL_PROVIDER, __VA_ARGS__, nullptr }; \
#define START_ACTIVITY \ exec_content_cmd(cmd, info); \
"/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 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) { static const char *get_command(const su_request *to) {
if (to->command[0]) if (to->command[0])
@ -62,48 +44,22 @@ static const char *get_command(const su_request *to) {
return DEFAULT_SHELL; return DEFAULT_SHELL;
} }
static void get_user(char *user, const su_info *info) { static void exec_content_cmd(const char **args, 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) {
char target[128]; char target[128];
if (args[3][0] == 'b') { sprintf(target, "content://%s.provider", info->str[SU_MANAGER].data());
// Broadcast char user[4];
if (args[4][1] == 'p') { sprintf(user, "%d", get_user(info));
// 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);
// Fill in non static arguments // Fill in non static arguments
args[5] = target; args[5] = target;
args[7] = user; args[7] = user;
exec_t exec { exec_t exec {
.pre_exec = []() -> void { .pre_exec = [] {
int null = xopen("/dev/null", O_WRONLY | O_CLOEXEC); int null = xopen("/dev/null", O_WRONLY | O_CLOEXEC);
dup2(null, STDOUT_FILENO); dup2(null, STDOUT_FILENO);
dup2(null, STDERR_FILENO); dup2(null, STDERR_FILENO);
setenv("CLASSPATH", "/system/framework/am.jar", 1); setenv("CLASSPATH", "/system/framework/content.jar", 1);
}, },
.fork = fork_dont_care, .fork = fork_dont_care,
.argv = args .argv = args
@ -113,94 +69,51 @@ static void exec_am_cmd(const char **args, const su_info *info) {
#define LOG_BODY \ #define LOG_BODY \
"log", \ "log", \
"--ei", "from.uid", fromUid, \ ex(fromUid), ex(toUid), ex(pid), ex(policy), \
"--ei", "to.uid", toUid, \ ex(command.data()), ex(notify)
"--ei", "pid", pid, \
"--ei", "policy", policy, \
"--es", "command", get_command(&ctx.req), \
"--ez", "notify", ctx.info->access.notify ? "true" : "false"
void app_log(const su_context &ctx) { void app_log(const su_context &ctx) {
char fromUid[8]; char fromUid[16];
get_uid(fromUid, ctx.info.get()); sprintf(fromUid, "from.uid:i:%d", get_uid(ctx.info));
char toUid[8]; char toUid[16];
sprintf(toUid, "%d", ctx.req.uid); sprintf(toUid, "to.uid:i:%d", ctx.req.uid);
char pid[8]; char pid[16];
sprintf(pid, "%d", ctx.pid); sprintf(pid, "pid:i:%d", ctx.pid);
char policy[2]; char policy[16];
sprintf(policy, "%d", ctx.info->access.policy); 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 \ #define NOTIFY_BODY \
"notify", \ "notify", ex(fromUid), ex(policy)
"--ei", "from.uid", fromUid, \
"--ei", "policy", policy
void app_notify(const su_context &ctx) { void app_notify(const su_context &ctx) {
char fromUid[8]; char fromUid[16];
get_uid(fromUid, ctx.info.get()); sprintf(fromUid, "from.uid:i:%d", get_uid(ctx.info));
char policy[2]; char policy[16];
sprintf(policy, "%d", ctx.info->access.policy); sprintf(policy, "policy:i:%d", ctx.info->access.policy);
am_app(NOTIFY_BODY) content_exec(NOTIFY_BODY)
} }
#define SOCKET_BODY \ #define SOCKET_BODY \
"request", \ "request", ex(sock)
"--es", "socket", socket
void app_socket(const char *socket, const shared_ptr<su_info> &info) { void app_socket(const char *socket, const shared_ptr<su_info> &info) {
am_app_info(info.get(), SOCKET_BODY) char sock[128];
} sprintf(sock, "socket:s:%s", socket);
content_exec_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<connect_mode>(mode);
}
write_int(client, 0);
}
close(client);
} }
void socket_send_request(int fd, const shared_ptr<su_info> &info) { void socket_send_request(int fd, const shared_ptr<su_info> &info) {

View File

@ -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/post-fs-data.d 2>/dev/null
mkdir /data/adb/services.d 2>/dev/null mkdir /data/adb/services.d 2>/dev/null
/sbin/magisk --daemon /sbin/magisk --daemon
/sbin/magisk --broadcast-test

View File

@ -10,6 +10,7 @@ import android.database.Cursor;
import android.database.MatrixCursor; import android.database.MatrixCursor;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns; import android.provider.OpenableColumns;
@ -54,6 +55,7 @@ public class FileProvider extends ContentProvider {
private PathStrategy mStrategy; private PathStrategy mStrategy;
public static ProviderCallHandler callHandler;
@Override @Override
public boolean onCreate() { public boolean onCreate() {
@ -157,6 +159,14 @@ public class FileProvider extends ContentProvider {
} }
@Override
public Bundle call(String method, String arg, Bundle extras) {
if (callHandler != null)
return callHandler.call(getContext(), method, arg, extras);
return null;
}
@Override @Override
public ParcelFileDescriptor openFile(Uri uri, String mode) public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException { throws FileNotFoundException {

View File

@ -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);
}