diff --git a/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt b/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt index ca5292a93..cc874e754 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ktx/XAndroid.kt @@ -7,9 +7,11 @@ import android.content.Context import android.content.ContextWrapper import android.content.Intent import android.content.pm.ApplicationInfo -import android.content.pm.ComponentInfo import android.content.pm.PackageManager import android.content.pm.PackageManager.* +import android.content.pm.ServiceInfo +import android.content.pm.ServiceInfo.FLAG_ISOLATED_PROCESS +import android.content.pm.ServiceInfo.FLAG_USE_APP_ZYGOTE import android.content.res.Configuration import android.content.res.Resources import android.database.Cursor @@ -57,32 +59,10 @@ import java.lang.reflect.Array as JArray val packageName: String get() = get().packageName -val ApplicationInfo.processes: List @SuppressLint("InlinedApi") get() { - val pm = get() - val appProcessName = processName ?: packageName - val baseFlag = MATCH_DISABLED_COMPONENTS or MATCH_UNINSTALLED_PACKAGES - val packageInfo = try { - val request = GET_ACTIVITIES or GET_SERVICES or GET_RECEIVERS or GET_PROVIDERS - pm.getPackageInfo(packageName, baseFlag or request) - } catch (e: NameNotFoundException) { // EdXposed hooked, issue#3276 - return listOf(appProcessName) - } catch (e: Exception) { - // Exceed binder data transfer limit, fetch each component type separately - pm.getPackageInfo(packageName, baseFlag).apply { - runCatching { activities = pm.getPackageInfo(packageName, GET_ACTIVITIES).activities } - runCatching { services = pm.getPackageInfo(packageName, GET_SERVICES).services } - runCatching { receivers = pm.getPackageInfo(packageName, GET_RECEIVERS).receivers } - runCatching { providers = pm.getPackageInfo(packageName, GET_PROVIDERS).providers } - } - } - fun Array.processNames() = map { it.processName ?: appProcessName } - return with(packageInfo) { - activities?.processNames().orEmpty() + - services?.processNames().orEmpty() + - receivers?.processNames().orEmpty() + - providers?.processNames().orEmpty() - } -} +val ServiceInfo.isIsolated get() = (flags and FLAG_ISOLATED_PROCESS) != 0 + +@get:SuppressLint("InlinedApi") +val ServiceInfo.useAppZygote get() = (flags and FLAG_USE_APP_ZYGOTE) != 0 fun Context.rawResource(id: Int) = resources.openRawResource(id) diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideApp.kt b/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideApp.kt deleted file mode 100644 index e55a3db35..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideApp.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.topjohnwu.magisk.ui.hide - -import android.content.pm.ApplicationInfo -import android.content.pm.PackageManager -import android.graphics.drawable.Drawable -import com.topjohnwu.magisk.core.utils.currentLocale -import com.topjohnwu.magisk.ktx.getLabel - -class HideTarget(line: String) { - val packageName: String - val process: String - - init { - val split = line.split(Regex("\\|"), 2) - packageName = split[0] - process = split.getOrElse(1) { packageName } - } -} - -class HideAppInfo(info: ApplicationInfo, pm: PackageManager) - : ApplicationInfo(info), Comparable { - - val label = info.getLabel(pm) - val iconImage: Drawable = info.loadIcon(pm) - - override fun compareTo(other: HideAppInfo) = comparator.compare(this, other) - - companion object { - private val comparator = compareBy( - { it.label.toLowerCase(currentLocale) }, - { it.packageName } - ) - } -} - -data class HideProcessInfo( - val name: String, - val packageName: String, - val isHidden: Boolean -) - -class HideAppTarget( - val info: HideAppInfo, - val processes: List -) : Comparable { - override fun compareTo(other: HideAppTarget) = compareValuesBy(this, other) { it.info } -} diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideInfo.kt b/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideInfo.kt new file mode 100644 index 000000000..d82c1e34a --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideInfo.kt @@ -0,0 +1,102 @@ +package com.topjohnwu.magisk.ui.hide + +import android.annotation.SuppressLint +import android.content.pm.ApplicationInfo +import android.content.pm.ComponentInfo +import android.content.pm.PackageManager +import android.content.pm.PackageManager.* +import android.content.pm.ServiceInfo +import android.graphics.drawable.Drawable +import com.topjohnwu.magisk.core.utils.currentLocale +import com.topjohnwu.magisk.ktx.getLabel +import com.topjohnwu.magisk.ktx.isIsolated +import com.topjohnwu.magisk.ktx.useAppZygote + +class CmdlineHiddenItem(line: String) { + val packageName: String + val process: String + + init { + val split = line.split(Regex("\\|"), 2) + packageName = split[0] + process = split.getOrElse(1) { packageName } + } +} + +const val ISOLATED_MAGIC = "isolated" + +@SuppressLint("InlinedApi") +class HideAppInfo(info: ApplicationInfo, pm: PackageManager, hideList: List) + : ApplicationInfo(info), Comparable { + + val label = info.getLabel(pm) + val iconImage: Drawable = info.loadIcon(pm) + val processes = fetchProcesses(pm, hideList) + + override fun compareTo(other: HideAppInfo) = comparator.compare(this, other) + + private fun fetchProcesses( + pm: PackageManager, + hideList: List + ): List { + // Fetch full PackageInfo + val baseFlag = MATCH_DISABLED_COMPONENTS or MATCH_UNINSTALLED_PACKAGES + val packageInfo = try { + val request = GET_ACTIVITIES or GET_SERVICES or GET_RECEIVERS or GET_PROVIDERS + pm.getPackageInfo(packageName, baseFlag or request) + } catch (e: NameNotFoundException) { + // EdXposed hooked, issue#3276 + return emptyList() + } catch (e: Exception) { + // Exceed binder data transfer limit, fetch each component type separately + pm.getPackageInfo(packageName, baseFlag).apply { + runCatching { activities = pm.getPackageInfo(packageName, baseFlag or GET_ACTIVITIES).activities } + runCatching { services = pm.getPackageInfo(packageName, baseFlag or GET_SERVICES).services } + runCatching { receivers = pm.getPackageInfo(packageName, baseFlag or GET_RECEIVERS).receivers } + runCatching { providers = pm.getPackageInfo(packageName, baseFlag or GET_PROVIDERS).providers } + } + } + + val hidden = hideList.filter { it.packageName == packageName || it.packageName == ISOLATED_MAGIC } + fun createProcess(name: String, pkg: String = packageName): HideProcessInfo { + return HideProcessInfo(name, pkg, hidden.any { it.process == name }) + } + + var haveAppZygote = false + fun Array.processes() = map { createProcess(it.processName) } + fun Array.processes() = map { + if (it.isIsolated) { + if (it.useAppZygote) { + haveAppZygote = true + // Using app zygote, don't need to track the process + null + } else { + createProcess("${it.processName}:${it.name}", ISOLATED_MAGIC) + } + } else { + createProcess(it.processName) + } + } + + return with(packageInfo) { + activities?.processes().orEmpty() + + services?.processes().orEmpty() + + receivers?.processes().orEmpty() + + providers?.processes().orEmpty() + + listOf(if (haveAppZygote) createProcess("${processName}_zygote") else null) + }.filterNotNull().distinctBy { it.name }.sortedBy { it.name } + } + + companion object { + private val comparator = compareBy( + { it.label.toLowerCase(currentLocale) }, + { it.packageName } + ) + } +} + +data class HideProcessInfo( + val name: String, + val packageName: String, + var isHidden: Boolean +) diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideRvItems.kt b/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideRvItems.kt index dbaa840b1..4a75cabee 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideRvItems.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideRvItems.kt @@ -12,14 +12,13 @@ import com.topjohnwu.magisk.utils.set import com.topjohnwu.superuser.Shell import kotlin.math.roundToInt -class HideItem( - app: HideAppTarget -) : ObservableItem(), Comparable { +class HideRvItem( + val info: HideAppInfo +) : ObservableItem(), Comparable { - override val layoutRes = R.layout.item_hide_md2 + override val layoutRes get() = R.layout.item_hide_md2 - val info = app.info - val processes = app.processes.map { HideProcessItem(it) } + val processes = info.processes.map { HideProcessRvItem(it) } @get:Bindable var isExpanded = false @@ -73,10 +72,10 @@ class HideItem( } } - override fun compareTo(other: HideItem) = comparator.compare(this, other) + override fun compareTo(other: HideRvItem) = comparator.compare(this, other) companion object { - private val comparator = compareBy( + private val comparator = compareBy( { it.itemsChecked == 0 }, { it.info } ) @@ -84,16 +83,17 @@ class HideItem( } -class HideProcessItem( +class HideProcessRvItem( val process: HideProcessInfo -) : ObservableItem() { +) : ObservableItem() { - override val layoutRes = R.layout.item_hide_process_md2 + override val layoutRes get() = R.layout.item_hide_process_md2 @get:Bindable - var isHidden = process.isHidden - set(value) = set(value, field, { field = it }, BR.hidden) { - val arg = if (isHidden) "add" else "rm" + var isHidden + get() = process.isHidden + set(value) = set(value, process.isHidden, { process.isHidden = it }, BR.hidden) { + val arg = if (it) "add" else "rm" val (name, pkg) = process Shell.su("magiskhide --$arg $pkg $name").submit() } @@ -102,7 +102,7 @@ class HideProcessItem( isHidden = !isHidden } - override fun contentSameAs(other: HideProcessItem) = process == other.process - override fun itemSameAs(other: HideProcessItem) = process.name == other.process.name + override fun contentSameAs(other: HideProcessRvItem) = process == other.process + override fun itemSameAs(other: HideProcessRvItem) = process.name == other.process.name } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt index fa9cdccdf..45ded7020 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt @@ -3,6 +3,7 @@ package com.topjohnwu.magisk.ui.hide import android.annotation.SuppressLint import android.content.pm.ApplicationInfo import android.content.pm.PackageManager +import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES import android.os.Process import androidx.databinding.Bindable import androidx.lifecycle.viewModelScope @@ -14,7 +15,6 @@ import com.topjohnwu.magisk.arch.itemBindingOf import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.ktx.get import com.topjohnwu.magisk.ktx.packageName -import com.topjohnwu.magisk.ktx.processes import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.set import com.topjohnwu.superuser.Shell @@ -45,11 +45,11 @@ class HideViewModel : BaseViewModel(), Queryable { submitQuery() } - val items = filterableListOf() - val itemBinding = itemBindingOf { + val items = filterableListOf() + val itemBinding = itemBindingOf { it.bindExtra(BR.viewModel, this) } - val itemInternalBinding = itemBindingOf { + val itemInternalBinding = itemBindingOf { it.bindExtra(BR.viewModel, this) } @@ -62,14 +62,13 @@ class HideViewModel : BaseViewModel(), Queryable { state = State.LOADING val (apps, diff) = withContext(Dispatchers.Default) { val pm = get() - val hides = Shell.su("magiskhide --ls").exec().out.map { HideTarget(it) } - val apps = pm.getInstalledApplications(PackageManager.MATCH_UNINSTALLED_PACKAGES) + val hideList = Shell.su("magiskhide --ls").exec().out.map { CmdlineHiddenItem(it) } + val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES) .asSequence() .filter { it.enabled && !blacklist.contains(it.packageName) } - .map { HideAppInfo(it, pm) } - .map { createTarget(it, hides) } + .map { HideAppInfo(it, pm, hideList) } .filter { it.processes.isNotEmpty() } - .map { HideItem(it) } + .map { HideRvItem(it) } .toList() .sorted() apps to items.calculateDiff(apps) @@ -80,18 +79,6 @@ class HideViewModel : BaseViewModel(), Queryable { // --- - private fun createTarget(info: HideAppInfo, hideList: List): HideAppTarget { - val pkg = info.packageName - val hidden = hideList.filter { it.packageName == pkg } - val processNames = info.processes.distinct() - val processes = processNames.map { name -> - HideProcessInfo(name, pkg, hidden.any { name == it.process }) - } - return HideAppTarget(info, processes) - } - - // --- - override fun query() { items.filter { fun showHidden() = it.itemsChecked != 0 diff --git a/app/src/main/res/layout/item_hide_md2.xml b/app/src/main/res/layout/item_hide_md2.xml index 5f9c9777e..8d71158b2 100644 --- a/app/src/main/res/layout/item_hide_md2.xml +++ b/app/src/main/res/layout/item_hide_md2.xml @@ -9,7 +9,7 @@ + type="com.topjohnwu.magisk.ui.hide.HideRvItem" /> + type="com.topjohnwu.magisk.ui.hide.HideProcessRvItem" /> d_name, ".rc") == 0) { + if (str_ends(entry->d_name, ".rc")) { LOGD("Found rc script [%s]\n", entry->d_name); int rc = xopenat(dfd, entry->d_name, O_RDONLY | O_CLOEXEC); rc_list.push_back(fd_full_read(rc)); diff --git a/native/jni/magiskhide/hide_utils.cpp b/native/jni/magiskhide/hide_utils.cpp index b098d4c1c..1296168d5 100644 --- a/native/jni/magiskhide/hide_utils.cpp +++ b/native/jni/magiskhide/hide_utils.cpp @@ -48,23 +48,27 @@ void set_hide_state(bool state) { hide_state = state; } +template static bool proc_name_match(int pid, const char *name) { char buf[4019]; sprintf(buf, "/proc/%d/cmdline", pid); - if (FILE *f = fopen(buf, "re")) { - fgets(buf, sizeof(buf), f); - fclose(f); - if (strcmp(buf, name) == 0) + if (auto fp = open_file(buf, "re")) { + fgets(buf, sizeof(buf), fp.get()); + if (str_op(buf, name)) { + LOGD("hide_utils: kill PID=[%d] (%s)\n", pid, buf); return true; + } } return false; } -static void kill_process(const char *name, bool multi = false) { +static inline bool str_eql(string_view s, string_view ss) { return s == ss; } + +static void kill_process(const char *name, bool multi = false, + bool (*filter)(int, const char *) = proc_name_match<&str_eql>) { crawl_procfs([=](int pid) -> bool { - if (proc_name_match(pid, name)) { - if (kill(pid, SIGTERM) == 0) - LOGD("hide_utils: killed PID=[%d] (%s)\n", pid, name); + if (filter(pid, name)) { + kill(pid, SIGTERM); return multi; } return true; @@ -72,6 +76,8 @@ static void kill_process(const char *name, bool multi = false) { } static bool validate(const char *s) { + if (strcmp(s, ISOLATED_MAGIC) == 0) + return true; bool dot = false; for (char c; (c = *s); ++s) { if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || @@ -87,7 +93,18 @@ static bool validate(const char *s) { return dot; } -static int add_list(const char *pkg, const char *proc = "") { +static void add_hide_set(const char *pkg, const char *proc) { + LOGI("hide_list add: [%s/%s]\n", pkg, proc); + hide_set.emplace(pkg, proc); + if (strcmp(pkg, ISOLATED_MAGIC) == 0) { + // Kill all matching isolated processes + kill_process(proc, true, proc_name_match<&str_starts>); + } else { + kill_process(proc); + } +} + +static int add_list(const char *pkg, const char *proc) { if (proc[0] == '\0') proc = pkg; @@ -105,15 +122,12 @@ static int add_list(const char *pkg, const char *proc = "") { char *err = db_exec(sql); db_err_cmd(err, return DAEMON_ERROR); - LOGI("hide_list add: [%s/%s]\n", pkg, proc); - - // Critical region { + // Critical region mutex_guard lock(monitor_lock); - hide_set.emplace(pkg, proc); + add_hide_set(pkg, proc); } - kill_process(proc); return DAEMON_SUCCESS; } @@ -123,27 +137,28 @@ int add_list(int client) { int ret = add_list(pkg, proc); free(pkg); free(proc); - update_uid_map(); + if (ret == DAEMON_SUCCESS) + update_uid_map(); return ret; } -static int rm_list(const char *pkg, const char *proc = "") { +static int rm_list(const char *pkg, const char *proc) { + bool remove = false; { // Critical region mutex_guard lock(monitor_lock); - bool remove = false; for (auto it = hide_set.begin(); it != hide_set.end();) { if (it->first == pkg && (proc[0] == '\0' || it->second == proc)) { remove = true; - LOGI("hide_list rm: [%s]\n", it->second.data()); + LOGI("hide_list rm: [%s/%s]\n", it->first.data(), it->second.data()); it = hide_set.erase(it); } else { ++it; } } - if (!remove) - return HIDE_ITEM_NOT_EXIST; } + if (!remove) + return HIDE_ITEM_NOT_EXIST; char sql[4096]; if (proc[0] == '\0') @@ -167,10 +182,11 @@ int rm_list(int client) { return ret; } -static void init_list(const char *pkg, const char *proc) { - LOGI("hide_list init: [%s/%s]\n", pkg, proc); - hide_set.emplace(pkg, proc); - kill_process(proc); +static bool str_ends_safe(string_view s, string_view ss) { + // Never kill webview zygote + if (s == "webview_zygote") + return false; + return str_ends(s, ss); } #define SNET_PROC "com.google.android.gms.unstable" @@ -181,25 +197,26 @@ static bool init_list() { LOGD("hide_list: initialize\n"); char *err = db_exec("SELECT * FROM hidelist", [](db_row &row) -> bool { - init_list(row["package_name"].data(), row["process"].data()); + add_hide_set(row["package_name"].data(), row["process"].data()); return true; }); db_err_cmd(err, return false); - // If Android Q+, also kill blastula pool + // If Android Q+, also kill blastula pool and all app zygotes if (SDK_INT >= 29) { kill_process("usap32", true); kill_process("usap64", true); + kill_process("_zygote", true, proc_name_match<&str_ends_safe>); } // Add SafetyNet by default - init_list(GMS_PKG, SNET_PROC); - init_list(MICROG_PKG, SNET_PROC); + add_hide_set(GMS_PKG, SNET_PROC); + add_hide_set(MICROG_PKG, SNET_PROC); // We also need to hide the default GMS process if MAGISKTMP != /sbin // The snet process communicates with the main process and get additional info if (MAGISKTMP != "/sbin") - init_list(GMS_PKG, GMS_PKG); + add_hide_set(GMS_PKG, GMS_PKG); update_uid_map(); return true; @@ -251,7 +268,7 @@ int launch_magiskhide() { hide_late_sensitive_props(); // Start monitoring - void *(*start)(void*) = [](void*) -> void* { proc_monitor(); return nullptr; }; + void *(*start)(void*) = [](void*) -> void* { proc_monitor(); }; if (xpthread_create(&proc_monitor_thread, nullptr, start, nullptr)) return DAEMON_ERROR; diff --git a/native/jni/magiskhide/magiskhide.hpp b/native/jni/magiskhide/magiskhide.hpp index 75a07d648..df0620c73 100644 --- a/native/jni/magiskhide/magiskhide.hpp +++ b/native/jni/magiskhide/magiskhide.hpp @@ -13,6 +13,7 @@ #include #define SIGTERMTHRD SIGUSR1 +#define ISOLATED_MAGIC "isolated" // CLI entries int launch_magiskhide(); diff --git a/native/jni/magiskhide/proc_monitor.cpp b/native/jni/magiskhide/proc_monitor.cpp index ecffd6f0e..687386fe9 100644 --- a/native/jni/magiskhide/proc_monitor.cpp +++ b/native/jni/magiskhide/proc_monitor.cpp @@ -1,6 +1,3 @@ -#include -#include -#include #include #include #include @@ -61,13 +58,13 @@ static int parse_ppid(int pid) { int ppid; sprintf(path, "/proc/%d/stat", pid); - FILE *stat = fopen(path, "re"); - if (stat == nullptr) + + auto stat = open_file(path, "re"); + if (!stat) return -1; // PID COMM STATE PPID ..... - fscanf(stat, "%*d %*s %*c %d", &ppid); - fclose(stat); + fscanf(stat.get(), "%*d %*s %*c %d", &ppid); return ppid; } @@ -89,20 +86,27 @@ void update_uid_map() { string data_path(APP_DATA_DIR); size_t len = data_path.length(); auto dir = open_dir(APP_DATA_DIR); + bool firstIteration = true; for (dirent *entry; (entry = xreaddir(dir.get()));) { data_path.resize(len); data_path += '/'; - data_path += entry->d_name; + data_path += entry->d_name; // multiuser user id data_path += '/'; size_t user_len = data_path.length(); struct stat st; for (auto &hide : hide_set) { + if (hide.first == ISOLATED_MAGIC) { + if (!firstIteration) continue; + // Setup isolated processes + uid_proc_map[-1].emplace_back(hide.second); + } data_path.resize(user_len); data_path += hide.first; if (stat(data_path.data(), &st)) continue; uid_proc_map[st.st_uid].emplace_back(hide.second); } + firstIteration = false; } } @@ -224,7 +228,7 @@ static bool check_pid(int pid) { sprintf(path, "/proc/%d", pid); if (stat(path, &st)) { - // Process killed unexpectedly, ignore + // Process died unexpectedly, ignore detach_pid(pid); return true; } @@ -237,7 +241,7 @@ static bool check_pid(int pid) { if (auto f = open_file(path, "re")) { fgets(cmdline, sizeof(cmdline), f.get()); } else { - // Process killed unexpectedly, ignore + // Process died unexpectedly, ignore detach_pid(pid); return true; } @@ -247,36 +251,59 @@ static bool check_pid(int pid) { return false; int uid = st.st_uid; - auto it = uid_proc_map.find(uid); - if (it != uid_proc_map.end()) { - for (auto &s : it->second) { - if (s == cmdline) { - // Double check whether ns is separated - read_ns(pid, &st); - bool mnt_ns = true; - for (auto &zit : zygote_map) { - if (zit.second.st_ino == st.st_ino && - zit.second.st_dev == st.st_dev) { - mnt_ns = false; - break; - } - } - // For some reason ns is not separated, abort - if (!mnt_ns) - break; + auto it = uid_proc_map.end(); - // Finally this is our target! - // Detach from ptrace but should still remain stopped. - // The hide daemon will resume the process. - PTRACE_LOG("target found\n"); - LOGI("proc_monitor: [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid); - detach_pid(pid, SIGSTOP); - hide_daemon(pid); - return true; + if (uid % 100000 > 90000) { + // Isolated process + it = uid_proc_map.find(-1); + if (it == uid_proc_map.end()) + goto not_target; + for (auto &s : it->second) { + if (str_starts(cmdline, s)) { + LOGI("proc_monitor: (isolated) [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid); + goto inject_and_hide; } } } - PTRACE_LOG("[%s] not our target\n", cmdline); + + it = uid_proc_map.find(uid); + if (it == uid_proc_map.end()) + goto not_target; + for (auto &s : it->second) { + if (s != cmdline) + continue; + + if (str_ends(s, "_zygote")) { + LOGI("proc_monitor: (app zygote) [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid); + goto inject_and_hide; + } + + // Double check whether ns is separated + read_ns(pid, &st); + for (auto &zit : zygote_map) { + if (zit.second.st_ino == st.st_ino && + zit.second.st_dev == st.st_dev) { + // For some reason ns is not separated, abort + goto not_target; + } + } + + // Finally this is our target! + // Detach from ptrace but should still remain stopped. + // The hide daemon will resume the process. + LOGI("proc_monitor: [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid); + detach_pid(pid, SIGSTOP); + hide_daemon(pid); + return true; + } + +not_target: + PTRACE_LOG("[%s] is not our target\n", cmdline); + detach_pid(pid); + return true; + +inject_and_hide: + // TODO: handle isolated processes and app zygotes detach_pid(pid); return true; } @@ -324,7 +351,7 @@ static void new_zygote(int pid) { } #define WEVENT(s) (((s) & 0xffff0000) >> 16) -#define DETACH_AND_CONT { detach = true; continue; } +#define DETACH_AND_CONT { detach_pid(pid); continue; } void proc_monitor() { // Unblock some signals @@ -354,9 +381,7 @@ void proc_monitor() { setitimer(ITIMER_REAL, &interval, nullptr); } - int status; - - for (;;) { + for (int status;;) { const int pid = waitpid(-1, &status, __WALL | __WNOTHREAD); if (pid < 0) { if (errno == ECHILD) { @@ -370,38 +395,35 @@ void proc_monitor() { } continue; } - bool detach = false; - run_finally f([&] { - if (detach) - // Non of our business now - detach_pid(pid); - }); if (!WIFSTOPPED(status) /* Ignore if not ptrace-stop */) DETACH_AND_CONT; - if (WSTOPSIG(status) == SIGTRAP && WEVENT(status)) { + int event = WEVENT(status); + int signal = WSTOPSIG(status); + + if (signal == SIGTRAP && event) { unsigned long msg; xptrace(PTRACE_GETEVENTMSG, pid, nullptr, &msg); if (zygote_map.count(pid)) { // Zygote event - switch (WEVENT(status)) { + switch (event) { case PTRACE_EVENT_FORK: case PTRACE_EVENT_VFORK: - PTRACE_LOG("zygote forked: [%d]\n", msg); + PTRACE_LOG("zygote forked: [%lu]\n", msg); attaches[msg] = true; break; case PTRACE_EVENT_EXIT: - PTRACE_LOG("zygote exited with status: [%d]\n", msg); + PTRACE_LOG("zygote exited with status: [%lu]\n", msg); [[fallthrough]]; default: zygote_map.erase(pid); DETACH_AND_CONT; } } else { - switch (WEVENT(status)) { + switch (event) { case PTRACE_EVENT_CLONE: - PTRACE_LOG("create new threads: [%d]\n", msg); + PTRACE_LOG("create new threads: [%lu]\n", msg); if (attaches[pid] && check_pid(pid)) continue; break; @@ -414,7 +436,7 @@ void proc_monitor() { } } xptrace(PTRACE_CONT, pid); - } else if (WSTOPSIG(status) == SIGSTOP) { + } else if (signal == SIGSTOP) { if (!attaches[pid]) { // Double check if this is actually a process attaches[pid] = is_process(pid); @@ -432,8 +454,8 @@ void proc_monitor() { } } else { // Not caused by us, resend signal - xptrace(PTRACE_CONT, pid, nullptr, WSTOPSIG(status)); - PTRACE_LOG("signal [%d]\n", WSTOPSIG(status)); + xptrace(PTRACE_CONT, pid, nullptr, signal); + PTRACE_LOG("signal [%d]\n", signal); } } } diff --git a/native/jni/utils/misc.cpp b/native/jni/utils/misc.cpp index f4f6c7fa9..aed1ff63d 100644 --- a/native/jni/utils/misc.cpp +++ b/native/jni/utils/misc.cpp @@ -58,12 +58,6 @@ int gen_rand_str(char *buf, int len, bool varlen) { return len - 1; } -int strend(const char *s1, const char *s2) { - size_t l1 = strlen(s1); - size_t l2 = strlen(s2); - return strcmp(s1 + l1 - l2, s2); -} - int exec_command(exec_t &exec) { int pipefd[] = {-1, -1}; int outfd = -1; @@ -151,12 +145,6 @@ void set_nice_name(const char *name) { prctl(PR_SET_NAME, name); } -bool ends_with(const std::string_view &s1, const std::string_view &s2) { - unsigned l1 = s1.length(); - unsigned l2 = s2.length(); - return l1 < l2 ? false : s1.compare(l1 - l2, l2, s2) == 0; -} - /* * Bionic's atoi runs through strtol(). * Use our own implementation for faster conversion. diff --git a/native/jni/utils/misc.hpp b/native/jni/utils/misc.hpp index a9665fa8a..995c78f80 100644 --- a/native/jni/utils/misc.hpp +++ b/native/jni/utils/misc.hpp @@ -8,9 +8,6 @@ #define UID_ROOT 0 #define UID_SHELL 2000 -#define str_contains(s, ss) ((ss) != nullptr && (s).find(ss) != std::string::npos) -#define str_starts(s, ss) ((ss) != nullptr && (s).compare(0, strlen(ss), ss) == 0) - class mutex_guard { public: explicit mutex_guard(pthread_mutex_t &m): mutex(&m) { @@ -65,10 +62,18 @@ using thread_entry = void *(*)(void *); int new_daemon_thread(thread_entry entry, void *arg = nullptr, const pthread_attr_t *attr = nullptr); int new_daemon_thread(std::function &&entry); -bool ends_with(const std::string_view &s1, const std::string_view &s2); +static inline bool str_contains(std::string_view s, std::string_view ss) { + return s.find(ss) != std::string::npos; +} +static inline bool str_starts(std::string_view s, std::string_view ss) { + return s.rfind(ss, 0) == 0; +} +static inline bool str_ends(std::string_view s, std::string_view ss) { + return s.size() >= ss.size() && s.compare(s.size() - ss.size(), std::string::npos, ss) == 0; +} + int fork_dont_care(); int fork_no_orphan(); -int strend(const char *s1, const char *s2); void init_argv0(int argc, char **argv); void set_nice_name(const char *name); uint32_t binary_gcd(uint32_t u, uint32_t v);