diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d3863ebb9..9ba7c0f60 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -76,6 +76,7 @@ android:name="a.h" android:directBootAware="true"> + diff --git a/app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt b/app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt index 4a893ab8b..29120d6f8 100644 --- a/app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt +++ b/app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt @@ -20,13 +20,15 @@ import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.core.content.ContextCompat import androidx.core.net.toUri +import com.topjohnwu.magisk.Const import com.topjohnwu.magisk.utils.DynamicClassLoader import com.topjohnwu.magisk.utils.FileProvider import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.currentLocale +import com.topjohnwu.superuser.Shell import java.io.File import java.io.FileNotFoundException -import java.util.* +import java.lang.reflect.Array as JArray val packageName: String get() = get().packageName @@ -97,33 +99,38 @@ fun Context.readUri(uri: Uri) = fun Intent.startActivity(context: Context) = context.startActivity(this) -fun Intent.toCommand(args: MutableList) { - if (action != null) { +fun Intent.startActivityWithRoot() { + val args = mutableListOf("am", "start", "--user", Const.USER_ID.toString()) + val cmd = toCommand(args).joinToString(" ") + Shell.su(cmd).submit() +} + +fun Intent.toCommand(args: MutableList = mutableListOf()): MutableList { + action?.also { args.add("-a") - args.add(action!!) + args.add(it) } - if (component != null) { + component?.also { args.add("-n") - args.add(component!!.flattenToString()) + args.add(it.flattenToString()) } - if (data != null) { + data?.also { args.add("-d") - args.add(dataString!!) + args.add(it.toString()) } - if (categories != null) { - for (cat in categories) { + categories?.also { + for (cat in it) { args.add("-c") args.add(cat) } } - if (type != null) { + type?.also { args.add("-t") - args.add(type!!) + args.add(it) } - val extras = extras - if (extras != null) { - loop@ for (key in extras.keySet()) { - val v = extras.get(key) ?: continue + extras?.also { + loop@ for (key in it.keySet()) { + val v = it[key] ?: continue var value: Any = v val arg: String when { @@ -137,9 +144,8 @@ fun Intent.toCommand(args: MutableList) { arg = "--ecn" value = v.flattenToString() } - v is ArrayList<*> -> { - if (v.size <= 0) - /* Impossible to know the type due to type erasure */ + v is List<*> -> { + if (v.isEmpty()) continue@loop arg = if (v[0] is Int) @@ -175,9 +181,9 @@ fun Intent.toCommand(args: MutableList) { continue@loop /* Unsupported */ val sb = StringBuilder() - val len = java.lang.reflect.Array.getLength(v) + val len = JArray.getLength(v) for (i in 0 until len) { - sb.append(java.lang.reflect.Array.get(v, i)!!.toString().replace(",", "\\,")) + sb.append(JArray.get(v, i)!!.toString().replace(",", "\\,")) sb.append(',') } // Remove trailing comma @@ -194,6 +200,7 @@ fun Intent.toCommand(args: MutableList) { } args.add("-f") args.add(flags.toString()) + return args } fun File.provide(context: Context = get()): Uri { 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 f9c591304..6a0ca9a4f 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,14 +2,14 @@ package com.topjohnwu.magisk.model.receiver import android.content.ContextWrapper import android.content.Intent -import com.topjohnwu.magisk.Config -import com.topjohnwu.magisk.Const -import com.topjohnwu.magisk.Info +import android.os.Build.VERSION.SDK_INT +import com.topjohnwu.magisk.* 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.intent +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 @@ -20,6 +20,7 @@ import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Shortcuts import com.topjohnwu.superuser.Shell import org.koin.core.inject +import timber.log.Timber open class GeneralReceiver : BaseReceiver() { @@ -38,6 +39,17 @@ 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, Intent.ACTION_BOOT_COMPLETED -> { val action = intent.getStringExtra("action") @@ -56,11 +68,19 @@ open class GeneralReceiver : BaseReceiver() { .putExtra("socket", intent.getStringExtra("socket")) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) - context.startActivity(i) + 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) + Shell.su("magisk --connect-mode $mode").submit() } - LOG -> SuLogger.handleLogs(intent) - NOTIFY -> SuLogger.handleNotify(intent) - TEST -> Shell.su("magisk --use-broadcast").submit() } } Intent.ACTION_PACKAGE_REPLACED -> 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 b55e108c2..4ce2a60be 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 @@ -3,7 +3,6 @@ package com.topjohnwu.magisk.ui.surequest import android.content.pm.ActivityInfo import android.os.Build import android.os.Bundle -import android.text.TextUtils import android.view.Window import com.topjohnwu.magisk.R import com.topjohnwu.magisk.base.BaseActivity @@ -31,19 +30,17 @@ open class SuRequestActivity : BaseActivity { + if (!viewModel.handleRequest(intent)) + finish() + return + } + GeneralReceiver.LOG -> SuLogger.handleLogs(this, intent) + GeneralReceiver.NOTIFY -> SuLogger.handleNotify(this, intent) } - if (TextUtils.equals(action, GeneralReceiver.LOG)) - SuLogger.handleLogs(intent) - else if (TextUtils.equals(action, GeneralReceiver.NOTIFY)) - SuLogger.handleNotify(intent) - finish() } diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/SuLogger.kt b/app/src/main/java/com/topjohnwu/magisk/utils/SuLogger.kt index b6134d584..98b201235 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/SuLogger.kt +++ b/app/src/main/java/com/topjohnwu/magisk/utils/SuLogger.kt @@ -2,14 +2,13 @@ package com.topjohnwu.magisk.utils import android.content.Context import android.content.Intent -import android.content.pm.PackageManager 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.inject +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 @@ -17,15 +16,13 @@ import java.util.* object SuLogger { - private val context: Context by inject() - - fun handleLogs(intent: Intent) { + fun handleLogs(context: Context, intent: Intent) { val fromUid = intent.getIntExtra("from.uid", -1) if (fromUid < 0) return if (fromUid == Process.myUid()) return - val pm: PackageManager by inject() + val pm = context.packageManager val notify: Boolean val data = intent.extras @@ -36,7 +33,7 @@ object SuLogger { }.getOrElse { return } } else { // Doesn't report whether notify or not, check database ourselves - val policyDB: PolicyDao by inject() + val policyDB = get() val policy = policyDB.fetch(fromUid).blockingGet() ?: return notify = policy.notification policy @@ -46,7 +43,7 @@ object SuLogger { return if (notify) - handleNotify(policy) + handleNotify(context, policy) val toUid = intent.getIntExtra("to.uid", -1) if (toUid < 0) return @@ -62,11 +59,11 @@ object SuLogger { date = Date() ) - val logRepo: LogRepository by inject() + val logRepo = get() logRepo.put(log).blockingGet()?.printStackTrace() } - private fun handleNotify(policy: MagiskPolicy) { + private fun handleNotify(context: Context, policy: MagiskPolicy) { if (policy.notification && Config.suNotification == Config.Value.NOTIFICATION_TOAST) { Utils.toast( context.getString( @@ -80,16 +77,16 @@ object SuLogger { } } - fun handleNotify(intent: Intent) { + fun handleNotify(context: Context, intent: Intent) { val fromUid = intent.getIntExtra("from.uid", -1) if (fromUid < 0) return if (fromUid == Process.myUid()) return runCatching { - val packageManager: PackageManager by inject() - val policy = fromUid.toPolicy(packageManager) + val pm = context.packageManager + val policy = fromUid.toPolicy(pm) .copy(policy = intent.getIntExtra("policy", -1)) if (policy.policy >= 0) - handleNotify(policy) + handleNotify(context, policy) } } } diff --git a/native/jni/core/daemon.cpp b/native/jni/core/daemon.cpp index 41dc7d97c..f13f05d3e 100644 --- a/native/jni/core/daemon.cpp +++ b/native/jni/core/daemon.cpp @@ -54,6 +54,7 @@ static void *request_handler(void *args) { case BOOT_COMPLETE: case SQLITE_CMD: case BROADCAST_ACK: + case BROADCAST_TEST: if (credential.uid != 0) { write_int(client, ROOT_REQUIRED); close(client); @@ -91,9 +92,10 @@ static void *request_handler(void *args) { exec_sql(client); break; case BROADCAST_ACK: - LOGD("* Use broadcasts for su logging and notify\n"); - CONNECT_BROADCAST = true; - close(client); + broadcast_ack(client); + break; + case BROADCAST_TEST: + broadcast_test(client); break; case REMOVE_MODULES: if (credential.uid == UID_SHELL || credential.uid == UID_ROOT) { diff --git a/native/jni/core/db.cpp b/native/jni/core/db.cpp index 701f62c41..bd451b64d 100644 --- a/native/jni/core/db.cpp +++ b/native/jni/core/db.cpp @@ -219,7 +219,6 @@ int get_db_strings(db_strings &str, int key) { char *err; auto string_cb = [&](db_row &row) -> bool { str[row["key"]] = row["value"]; - LOGD("magiskdb: query %s=[%s]\n", row["key"].data(), row["value"].data()); return true; }; if (key >= 0) { @@ -273,6 +272,7 @@ int validate_manager(string &alt_pkg, int userid, struct stat *st) { } void exec_sql(int client) { + run_finally f([=]{ close(client); }); char *sql = read_string(client); char *err = db_exec(sql, [&](db_row &row) -> bool { string out; @@ -289,9 +289,6 @@ void exec_sql(int client) { return true; }); free(sql); - db_err_cmd(err, - write_int(client, 0); - return; - ); - close(client); + write_int(client, 0); + db_err_cmd(err, return; ); } diff --git a/native/jni/core/magisk.cpp b/native/jni/core/magisk.cpp index a93ea1eaa..821e53be1 100644 --- a/native/jni/core/magisk.cpp +++ b/native/jni/core/magisk.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include using namespace std::literals; @@ -36,7 +35,8 @@ 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 - --use-broadcast use broadcast for su logging and notify + --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 @@ -79,12 +79,10 @@ int magisk_main(int argc, char *argv[]) { restore_rootcon(); restorecon(); return 0; - } else if (argv[1] == "--clone-attr"sv) { - if (argc < 4) usage(); + } else if (argc >= 4 && argv[1] == "--clone-attr"sv) {; clone_attr(argv[2], argv[3]); return 0; - } else if (argv[1] == "--clone"sv) { - if (argc < 4) usage(); + } else if (argc >= 4 && argv[1] == "--clone"sv) { cp_afc(argv[2], argv[3]); return 0; } else if (argv[1] == "--daemon"sv) { @@ -103,7 +101,7 @@ int magisk_main(int argc, char *argv[]) { int fd = connect_daemon(true); write_int(fd, BOOT_COMPLETE); return read_int(fd); - } else if (argv[1] == "--sqlite"sv) { + } else if (argc >= 3 && argv[1] == "--sqlite"sv) { int fd = connect_daemon(); write_int(fd, SQLITE_CMD); write_string(fd, argv[2]); @@ -115,14 +113,23 @@ int magisk_main(int argc, char *argv[]) { printf("%s\n", res); free(res); } - } else if (argv[1] == "--use-broadcast"sv) { + } else if (argv[1] == "--connect-mode"sv) { int fd = connect_daemon(); write_int(fd, BROADCAST_ACK); - return 0; + 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 92e1b5b62..0f6956b0e 100644 --- a/native/jni/include/daemon.h +++ b/native/jni/include/daemon.h @@ -19,6 +19,7 @@ enum { SQLITE_CMD, BROADCAST_ACK, REMOVE_MODULES, + BROADCAST_TEST, }; // Return codes for daemon @@ -84,10 +85,13 @@ void magiskhide_handler(int client); *************/ void su_daemon_handler(int client, struct ucred *credential); -void broadcast_test(); +void broadcast_test(int client = -1); +void broadcast_ack(int client); + +/********************* + * Daemon Global Vars + *********************/ extern int SDK_INT; extern bool RECOVERY_MODE; -extern bool CONNECT_BROADCAST; - #define APP_DATA_DIR (SDK_INT >= 24 ? "/data/user_de" : "/data/user") diff --git a/native/jni/su/connect.cpp b/native/jni/su/connect.cpp index f59155863..f83cf2c9c 100644 --- a/native/jni/su/connect.cpp +++ b/native/jni/su/connect.cpp @@ -7,12 +7,20 @@ #include #include +#include #include "su.h" using namespace std; -bool CONNECT_BROADCAST; +enum connect_mode { + UNINITIALIZED = 0, + MODE_ACTIVITY, + MODE_BROADCAST_COMPONENT, + MODE_BROADCAST_PACKAGE +}; + +static connect_mode current_mode = UNINITIALIZED; #define START_ACTIVITY \ "/system/bin/app_process", "/system/bin", "com.android.commands.am.Am", \ @@ -25,9 +33,28 @@ bool CONNECT_BROADCAST; "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 -static inline const char *get_command(const su_request *to) { +#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__) + +static const char *get_command(const su_request *to) { if (to->command[0]) return to->command; if (to->shell[0]) @@ -35,14 +62,14 @@ static inline const char *get_command(const su_request *to) { return DEFAULT_SHELL; } -static inline void get_user(char *user, const su_info *info) { +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 inline void get_uid(char *uid, const su_info *info) { +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 @@ -50,13 +77,25 @@ static inline void get_uid(char *uid, const su_info *info) { } static void exec_am_cmd(const char **args, const su_info *info) { - char component[128]; - sprintf(component, "%s/%s", info->str[SU_MANAGER].data(), args[3][0] == 'b' ? "a.h" : "a.m"); + 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); - /* Fill in dynamic arguments */ - args[5] = component; + // Fill in non static arguments + args[5] = target; args[7] = user; exec_t exec { @@ -79,8 +118,7 @@ static void exec_am_cmd(const char **args, const su_info *info) { "--ei", "pid", pid, \ "--ei", "policy", policy, \ "--es", "command", get_command(&ctx.req), \ -"--ez", "notify", ctx.info->access.notify ? "true" : "false", \ -nullptr +"--ez", "notify", ctx.info->access.notify ? "true" : "false" void app_log(const su_context &ctx) { char fromUid[8]; @@ -95,20 +133,13 @@ void app_log(const su_context &ctx) { char policy[2]; sprintf(policy, "%d", ctx.info->access.policy); - if (CONNECT_BROADCAST) { - const char *cmd[] = { START_BROADCAST, LOG_BODY }; - exec_am_cmd(cmd, ctx.info.get()); - } else { - const char *cmd[] = { START_ACTIVITY, LOG_BODY }; - exec_am_cmd(cmd, ctx.info.get()); - } + am_app(LOG_BODY) } #define NOTIFY_BODY \ "notify", \ "--ei", "from.uid", fromUid, \ -"--ei", "policy", policy, \ -nullptr +"--ei", "policy", policy void app_notify(const su_context &ctx) { char fromUid[8]; @@ -117,33 +148,59 @@ void app_notify(const su_context &ctx) { char policy[2]; sprintf(policy, "%d", ctx.info->access.policy); - if (CONNECT_BROADCAST) { - const char *cmd[] = { START_BROADCAST, NOTIFY_BODY }; - exec_am_cmd(cmd, ctx.info.get()); - } else { - const char *cmd[] = { START_ACTIVITY, NOTIFY_BODY }; - exec_am_cmd(cmd, ctx.info.get()); + am_app(NOTIFY_BODY) +} + +#define SOCKET_BODY \ +"request", \ +"--es", "socket", socket + +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); } -} - -void app_connect(const char *socket, const shared_ptr &info) { - const char *cmd[] = { - START_ACTIVITY, "request", - "--es", "socket", socket, - nullptr - }; - exec_am_cmd(cmd, info.get()); -} - -void broadcast_test() { su_info info; get_db_settings(info.cfg); get_db_strings(info.str); validate_manager(info.str[SU_MANAGER], 0, &info.mgr_st); - const char *cmd[] = { START_BROADCAST, "test", nullptr }; - exec_am_cmd(cmd, &info); + 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); } void socket_send_request(int fd, const shared_ptr &info) { diff --git a/native/jni/su/su.h b/native/jni/su/su.h index c496e5bbf..2c18fc5c8 100644 --- a/native/jni/su/su.h +++ b/native/jni/su/su.h @@ -68,5 +68,5 @@ struct su_context { void app_log(const su_context &ctx); void app_notify(const su_context &ctx); -void app_connect(const char *socket, const std::shared_ptr &info); +void app_socket(const char *socket, const std::shared_ptr &info); void socket_send_request(int fd, const std::shared_ptr &info); diff --git a/native/jni/su/su_daemon.cpp b/native/jni/su/su_daemon.cpp index 5debb5cd5..660ca1175 100644 --- a/native/jni/su/su_daemon.cpp +++ b/native/jni/su/su_daemon.cpp @@ -144,7 +144,7 @@ static shared_ptr get_su_info(unsigned uid) { int sockfd = create_rand_socket(&addr); // Connect manager - app_connect(addr.sun_path + 1, info); + app_socket(addr.sun_path + 1, info); int fd = socket_accept(sockfd, 60); if (fd < 0) { info->access.policy = DENY;