Preparation for hiding isolated processes

This commit is contained in:
topjohnwu 2020-12-30 15:55:53 -08:00
parent 3f9a64417b
commit 8e61080a4a
13 changed files with 271 additions and 216 deletions

View File

@ -7,9 +7,11 @@ import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.content.Intent import android.content.Intent
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.ComponentInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
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.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.database.Cursor import android.database.Cursor
@ -57,32 +59,10 @@ import java.lang.reflect.Array as JArray
val packageName: String get() = get<Context>().packageName val packageName: String get() = get<Context>().packageName
val ApplicationInfo.processes: List<String> @SuppressLint("InlinedApi") get() { val ServiceInfo.isIsolated get() = (flags and FLAG_ISOLATED_PROCESS) != 0
val pm = get<PackageManager>()
val appProcessName = processName ?: packageName @get:SuppressLint("InlinedApi")
val baseFlag = MATCH_DISABLED_COMPONENTS or MATCH_UNINSTALLED_PACKAGES val ServiceInfo.useAppZygote get() = (flags and FLAG_USE_APP_ZYGOTE) != 0
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<out ComponentInfo>.processNames() = map { it.processName ?: appProcessName }
return with(packageInfo) {
activities?.processNames().orEmpty() +
services?.processNames().orEmpty() +
receivers?.processNames().orEmpty() +
providers?.processNames().orEmpty()
}
}
fun Context.rawResource(id: Int) = resources.openRawResource(id) fun Context.rawResource(id: Int) = resources.openRawResource(id)

View File

@ -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<HideAppInfo> {
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<HideAppInfo>(
{ 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<HideProcessInfo>
) : Comparable<HideAppTarget> {
override fun compareTo(other: HideAppTarget) = compareValuesBy(this, other) { it.info }
}

View File

@ -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<CmdlineHiddenItem>)
: ApplicationInfo(info), Comparable<HideAppInfo> {
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<CmdlineHiddenItem>
): List<HideProcessInfo> {
// 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<out ComponentInfo>.processes() = map { createProcess(it.processName) }
fun Array<ServiceInfo>.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<HideAppInfo>(
{ it.label.toLowerCase(currentLocale) },
{ it.packageName }
)
}
}
data class HideProcessInfo(
val name: String,
val packageName: String,
var isHidden: Boolean
)

View File

@ -12,14 +12,13 @@ import com.topjohnwu.magisk.utils.set
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import kotlin.math.roundToInt import kotlin.math.roundToInt
class HideItem( class HideRvItem(
app: HideAppTarget val info: HideAppInfo
) : ObservableItem<HideItem>(), Comparable<HideItem> { ) : ObservableItem<HideRvItem>(), Comparable<HideRvItem> {
override val layoutRes = R.layout.item_hide_md2 override val layoutRes get() = R.layout.item_hide_md2
val info = app.info val processes = info.processes.map { HideProcessRvItem(it) }
val processes = app.processes.map { HideProcessItem(it) }
@get:Bindable @get:Bindable
var isExpanded = false 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 { companion object {
private val comparator = compareBy<HideItem>( private val comparator = compareBy<HideRvItem>(
{ it.itemsChecked == 0 }, { it.itemsChecked == 0 },
{ it.info } { it.info }
) )
@ -84,16 +83,17 @@ class HideItem(
} }
class HideProcessItem( class HideProcessRvItem(
val process: HideProcessInfo val process: HideProcessInfo
) : ObservableItem<HideProcessItem>() { ) : ObservableItem<HideProcessRvItem>() {
override val layoutRes = R.layout.item_hide_process_md2 override val layoutRes get() = R.layout.item_hide_process_md2
@get:Bindable @get:Bindable
var isHidden = process.isHidden var isHidden
set(value) = set(value, field, { field = it }, BR.hidden) { get() = process.isHidden
val arg = if (isHidden) "add" else "rm" set(value) = set(value, process.isHidden, { process.isHidden = it }, BR.hidden) {
val arg = if (it) "add" else "rm"
val (name, pkg) = process val (name, pkg) = process
Shell.su("magiskhide --$arg $pkg $name").submit() Shell.su("magiskhide --$arg $pkg $name").submit()
} }
@ -102,7 +102,7 @@ class HideProcessItem(
isHidden = !isHidden isHidden = !isHidden
} }
override fun contentSameAs(other: HideProcessItem) = process == other.process override fun contentSameAs(other: HideProcessRvItem) = process == other.process
override fun itemSameAs(other: HideProcessItem) = process.name == other.process.name override fun itemSameAs(other: HideProcessRvItem) = process.name == other.process.name
} }

View File

@ -3,6 +3,7 @@ package com.topjohnwu.magisk.ui.hide
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
import android.os.Process import android.os.Process
import androidx.databinding.Bindable import androidx.databinding.Bindable
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@ -14,7 +15,6 @@ import com.topjohnwu.magisk.arch.itemBindingOf
import com.topjohnwu.magisk.core.Config import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.ktx.get import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.ktx.packageName import com.topjohnwu.magisk.ktx.packageName
import com.topjohnwu.magisk.ktx.processes
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.set import com.topjohnwu.magisk.utils.set
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
@ -45,11 +45,11 @@ class HideViewModel : BaseViewModel(), Queryable {
submitQuery() submitQuery()
} }
val items = filterableListOf<HideItem>() val items = filterableListOf<HideRvItem>()
val itemBinding = itemBindingOf<HideItem> { val itemBinding = itemBindingOf<HideRvItem> {
it.bindExtra(BR.viewModel, this) it.bindExtra(BR.viewModel, this)
} }
val itemInternalBinding = itemBindingOf<HideProcessItem> { val itemInternalBinding = itemBindingOf<HideProcessRvItem> {
it.bindExtra(BR.viewModel, this) it.bindExtra(BR.viewModel, this)
} }
@ -62,14 +62,13 @@ class HideViewModel : BaseViewModel(), Queryable {
state = State.LOADING state = State.LOADING
val (apps, diff) = withContext(Dispatchers.Default) { val (apps, diff) = withContext(Dispatchers.Default) {
val pm = get<PackageManager>() val pm = get<PackageManager>()
val hides = Shell.su("magiskhide --ls").exec().out.map { HideTarget(it) } val hideList = Shell.su("magiskhide --ls").exec().out.map { CmdlineHiddenItem(it) }
val apps = pm.getInstalledApplications(PackageManager.MATCH_UNINSTALLED_PACKAGES) val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES)
.asSequence() .asSequence()
.filter { it.enabled && !blacklist.contains(it.packageName) } .filter { it.enabled && !blacklist.contains(it.packageName) }
.map { HideAppInfo(it, pm) } .map { HideAppInfo(it, pm, hideList) }
.map { createTarget(it, hides) }
.filter { it.processes.isNotEmpty() } .filter { it.processes.isNotEmpty() }
.map { HideItem(it) } .map { HideRvItem(it) }
.toList() .toList()
.sorted() .sorted()
apps to items.calculateDiff(apps) apps to items.calculateDiff(apps)
@ -80,18 +79,6 @@ class HideViewModel : BaseViewModel(), Queryable {
// --- // ---
private fun createTarget(info: HideAppInfo, hideList: List<HideTarget>): 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() { override fun query() {
items.filter { items.filter {
fun showHidden() = it.itemsChecked != 0 fun showHidden() = it.itemsChecked != 0

View File

@ -9,7 +9,7 @@
<variable <variable
name="item" name="item"
type="com.topjohnwu.magisk.ui.hide.HideItem" /> type="com.topjohnwu.magisk.ui.hide.HideRvItem" />
<variable <variable
name="viewModel" name="viewModel"

View File

@ -7,7 +7,7 @@
<variable <variable
name="item" name="item"
type="com.topjohnwu.magisk.ui.hide.HideProcessItem" /> type="com.topjohnwu.magisk.ui.hide.HideProcessRvItem" />
<variable <variable
name="viewModel" name="viewModel"

View File

@ -68,7 +68,7 @@ static void load_overlay_rc(const char *overlay) {
// Do not allow overwrite init.rc // Do not allow overwrite init.rc
unlinkat(dfd, "init.rc", 0); unlinkat(dfd, "init.rc", 0);
for (dirent *entry; (entry = xreaddir(dir.get()));) { for (dirent *entry; (entry = xreaddir(dir.get()));) {
if (strend(entry->d_name, ".rc") == 0) { if (str_ends(entry->d_name, ".rc")) {
LOGD("Found rc script [%s]\n", entry->d_name); LOGD("Found rc script [%s]\n", entry->d_name);
int rc = xopenat(dfd, entry->d_name, O_RDONLY | O_CLOEXEC); int rc = xopenat(dfd, entry->d_name, O_RDONLY | O_CLOEXEC);
rc_list.push_back(fd_full_read(rc)); rc_list.push_back(fd_full_read(rc));

View File

@ -48,23 +48,27 @@ void set_hide_state(bool state) {
hide_state = state; hide_state = state;
} }
template <bool str_op(string_view, string_view)>
static bool proc_name_match(int pid, const char *name) { static bool proc_name_match(int pid, const char *name) {
char buf[4019]; char buf[4019];
sprintf(buf, "/proc/%d/cmdline", pid); sprintf(buf, "/proc/%d/cmdline", pid);
if (FILE *f = fopen(buf, "re")) { if (auto fp = open_file(buf, "re")) {
fgets(buf, sizeof(buf), f); fgets(buf, sizeof(buf), fp.get());
fclose(f); if (str_op(buf, name)) {
if (strcmp(buf, name) == 0) LOGD("hide_utils: kill PID=[%d] (%s)\n", pid, buf);
return true; return true;
}
} }
return false; 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 { crawl_procfs([=](int pid) -> bool {
if (proc_name_match(pid, name)) { if (filter(pid, name)) {
if (kill(pid, SIGTERM) == 0) kill(pid, SIGTERM);
LOGD("hide_utils: killed PID=[%d] (%s)\n", pid, name);
return multi; return multi;
} }
return true; return true;
@ -72,6 +76,8 @@ static void kill_process(const char *name, bool multi = false) {
} }
static bool validate(const char *s) { static bool validate(const char *s) {
if (strcmp(s, ISOLATED_MAGIC) == 0)
return true;
bool dot = false; bool dot = false;
for (char c; (c = *s); ++s) { for (char c; (c = *s); ++s) {
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
@ -87,7 +93,18 @@ static bool validate(const char *s) {
return dot; 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') if (proc[0] == '\0')
proc = pkg; proc = pkg;
@ -105,15 +122,12 @@ static int add_list(const char *pkg, const char *proc = "") {
char *err = db_exec(sql); char *err = db_exec(sql);
db_err_cmd(err, return DAEMON_ERROR); 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); mutex_guard lock(monitor_lock);
hide_set.emplace(pkg, proc); add_hide_set(pkg, proc);
} }
kill_process(proc);
return DAEMON_SUCCESS; return DAEMON_SUCCESS;
} }
@ -123,27 +137,28 @@ int add_list(int client) {
int ret = add_list(pkg, proc); int ret = add_list(pkg, proc);
free(pkg); free(pkg);
free(proc); free(proc);
update_uid_map(); if (ret == DAEMON_SUCCESS)
update_uid_map();
return ret; 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 // Critical region
mutex_guard lock(monitor_lock); mutex_guard lock(monitor_lock);
bool remove = false;
for (auto it = hide_set.begin(); it != hide_set.end();) { for (auto it = hide_set.begin(); it != hide_set.end();) {
if (it->first == pkg && (proc[0] == '\0' || it->second == proc)) { if (it->first == pkg && (proc[0] == '\0' || it->second == proc)) {
remove = true; 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); it = hide_set.erase(it);
} else { } else {
++it; ++it;
} }
} }
if (!remove)
return HIDE_ITEM_NOT_EXIST;
} }
if (!remove)
return HIDE_ITEM_NOT_EXIST;
char sql[4096]; char sql[4096];
if (proc[0] == '\0') if (proc[0] == '\0')
@ -167,10 +182,11 @@ int rm_list(int client) {
return ret; return ret;
} }
static void init_list(const char *pkg, const char *proc) { static bool str_ends_safe(string_view s, string_view ss) {
LOGI("hide_list init: [%s/%s]\n", pkg, proc); // Never kill webview zygote
hide_set.emplace(pkg, proc); if (s == "webview_zygote")
kill_process(proc); return false;
return str_ends(s, ss);
} }
#define SNET_PROC "com.google.android.gms.unstable" #define SNET_PROC "com.google.android.gms.unstable"
@ -181,25 +197,26 @@ static bool init_list() {
LOGD("hide_list: initialize\n"); LOGD("hide_list: initialize\n");
char *err = db_exec("SELECT * FROM hidelist", [](db_row &row) -> bool { 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; return true;
}); });
db_err_cmd(err, return false); 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) { if (SDK_INT >= 29) {
kill_process("usap32", true); kill_process("usap32", true);
kill_process("usap64", true); kill_process("usap64", true);
kill_process("_zygote", true, proc_name_match<&str_ends_safe>);
} }
// Add SafetyNet by default // Add SafetyNet by default
init_list(GMS_PKG, SNET_PROC); add_hide_set(GMS_PKG, SNET_PROC);
init_list(MICROG_PKG, SNET_PROC); add_hide_set(MICROG_PKG, SNET_PROC);
// We also need to hide the default GMS process if MAGISKTMP != /sbin // We also need to hide the default GMS process if MAGISKTMP != /sbin
// The snet process communicates with the main process and get additional info // The snet process communicates with the main process and get additional info
if (MAGISKTMP != "/sbin") if (MAGISKTMP != "/sbin")
init_list(GMS_PKG, GMS_PKG); add_hide_set(GMS_PKG, GMS_PKG);
update_uid_map(); update_uid_map();
return true; return true;
@ -251,7 +268,7 @@ int launch_magiskhide() {
hide_late_sensitive_props(); hide_late_sensitive_props();
// Start monitoring // 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)) if (xpthread_create(&proc_monitor_thread, nullptr, start, nullptr))
return DAEMON_ERROR; return DAEMON_ERROR;

View File

@ -13,6 +13,7 @@
#include <daemon.hpp> #include <daemon.hpp>
#define SIGTERMTHRD SIGUSR1 #define SIGTERMTHRD SIGUSR1
#define ISOLATED_MAGIC "isolated"
// CLI entries // CLI entries
int launch_magiskhide(); int launch_magiskhide();

View File

@ -1,6 +1,3 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <signal.h> #include <signal.h>
@ -61,13 +58,13 @@ static int parse_ppid(int pid) {
int ppid; int ppid;
sprintf(path, "/proc/%d/stat", pid); sprintf(path, "/proc/%d/stat", pid);
FILE *stat = fopen(path, "re");
if (stat == nullptr) auto stat = open_file(path, "re");
if (!stat)
return -1; return -1;
// PID COMM STATE PPID ..... // PID COMM STATE PPID .....
fscanf(stat, "%*d %*s %*c %d", &ppid); fscanf(stat.get(), "%*d %*s %*c %d", &ppid);
fclose(stat);
return ppid; return ppid;
} }
@ -89,20 +86,27 @@ void update_uid_map() {
string data_path(APP_DATA_DIR); string data_path(APP_DATA_DIR);
size_t len = data_path.length(); size_t len = data_path.length();
auto dir = open_dir(APP_DATA_DIR); auto dir = open_dir(APP_DATA_DIR);
bool firstIteration = true;
for (dirent *entry; (entry = xreaddir(dir.get()));) { for (dirent *entry; (entry = xreaddir(dir.get()));) {
data_path.resize(len); data_path.resize(len);
data_path += '/'; data_path += '/';
data_path += entry->d_name; data_path += entry->d_name; // multiuser user id
data_path += '/'; data_path += '/';
size_t user_len = data_path.length(); size_t user_len = data_path.length();
struct stat st; struct stat st;
for (auto &hide : hide_set) { 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.resize(user_len);
data_path += hide.first; data_path += hide.first;
if (stat(data_path.data(), &st)) if (stat(data_path.data(), &st))
continue; continue;
uid_proc_map[st.st_uid].emplace_back(hide.second); 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); sprintf(path, "/proc/%d", pid);
if (stat(path, &st)) { if (stat(path, &st)) {
// Process killed unexpectedly, ignore // Process died unexpectedly, ignore
detach_pid(pid); detach_pid(pid);
return true; return true;
} }
@ -237,7 +241,7 @@ static bool check_pid(int pid) {
if (auto f = open_file(path, "re")) { if (auto f = open_file(path, "re")) {
fgets(cmdline, sizeof(cmdline), f.get()); fgets(cmdline, sizeof(cmdline), f.get());
} else { } else {
// Process killed unexpectedly, ignore // Process died unexpectedly, ignore
detach_pid(pid); detach_pid(pid);
return true; return true;
} }
@ -247,36 +251,59 @@ static bool check_pid(int pid) {
return false; return false;
int uid = st.st_uid; int uid = st.st_uid;
auto it = uid_proc_map.find(uid); auto it = uid_proc_map.end();
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;
// Finally this is our target! if (uid % 100000 > 90000) {
// Detach from ptrace but should still remain stopped. // Isolated process
// The hide daemon will resume the process. it = uid_proc_map.find(-1);
PTRACE_LOG("target found\n"); if (it == uid_proc_map.end())
LOGI("proc_monitor: [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid); goto not_target;
detach_pid(pid, SIGSTOP); for (auto &s : it->second) {
hide_daemon(pid); if (str_starts(cmdline, s)) {
return true; 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); detach_pid(pid);
return true; return true;
} }
@ -324,7 +351,7 @@ static void new_zygote(int pid) {
} }
#define WEVENT(s) (((s) & 0xffff0000) >> 16) #define WEVENT(s) (((s) & 0xffff0000) >> 16)
#define DETACH_AND_CONT { detach = true; continue; } #define DETACH_AND_CONT { detach_pid(pid); continue; }
void proc_monitor() { void proc_monitor() {
// Unblock some signals // Unblock some signals
@ -354,9 +381,7 @@ void proc_monitor() {
setitimer(ITIMER_REAL, &interval, nullptr); setitimer(ITIMER_REAL, &interval, nullptr);
} }
int status; for (int status;;) {
for (;;) {
const int pid = waitpid(-1, &status, __WALL | __WNOTHREAD); const int pid = waitpid(-1, &status, __WALL | __WNOTHREAD);
if (pid < 0) { if (pid < 0) {
if (errno == ECHILD) { if (errno == ECHILD) {
@ -370,38 +395,35 @@ void proc_monitor() {
} }
continue; 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 */) if (!WIFSTOPPED(status) /* Ignore if not ptrace-stop */)
DETACH_AND_CONT; DETACH_AND_CONT;
if (WSTOPSIG(status) == SIGTRAP && WEVENT(status)) { int event = WEVENT(status);
int signal = WSTOPSIG(status);
if (signal == SIGTRAP && event) {
unsigned long msg; unsigned long msg;
xptrace(PTRACE_GETEVENTMSG, pid, nullptr, &msg); xptrace(PTRACE_GETEVENTMSG, pid, nullptr, &msg);
if (zygote_map.count(pid)) { if (zygote_map.count(pid)) {
// Zygote event // Zygote event
switch (WEVENT(status)) { switch (event) {
case PTRACE_EVENT_FORK: case PTRACE_EVENT_FORK:
case PTRACE_EVENT_VFORK: case PTRACE_EVENT_VFORK:
PTRACE_LOG("zygote forked: [%d]\n", msg); PTRACE_LOG("zygote forked: [%lu]\n", msg);
attaches[msg] = true; attaches[msg] = true;
break; break;
case PTRACE_EVENT_EXIT: case PTRACE_EVENT_EXIT:
PTRACE_LOG("zygote exited with status: [%d]\n", msg); PTRACE_LOG("zygote exited with status: [%lu]\n", msg);
[[fallthrough]]; [[fallthrough]];
default: default:
zygote_map.erase(pid); zygote_map.erase(pid);
DETACH_AND_CONT; DETACH_AND_CONT;
} }
} else { } else {
switch (WEVENT(status)) { switch (event) {
case PTRACE_EVENT_CLONE: 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)) if (attaches[pid] && check_pid(pid))
continue; continue;
break; break;
@ -414,7 +436,7 @@ void proc_monitor() {
} }
} }
xptrace(PTRACE_CONT, pid); xptrace(PTRACE_CONT, pid);
} else if (WSTOPSIG(status) == SIGSTOP) { } else if (signal == SIGSTOP) {
if (!attaches[pid]) { if (!attaches[pid]) {
// Double check if this is actually a process // Double check if this is actually a process
attaches[pid] = is_process(pid); attaches[pid] = is_process(pid);
@ -432,8 +454,8 @@ void proc_monitor() {
} }
} else { } else {
// Not caused by us, resend signal // Not caused by us, resend signal
xptrace(PTRACE_CONT, pid, nullptr, WSTOPSIG(status)); xptrace(PTRACE_CONT, pid, nullptr, signal);
PTRACE_LOG("signal [%d]\n", WSTOPSIG(status)); PTRACE_LOG("signal [%d]\n", signal);
} }
} }
} }

View File

@ -58,12 +58,6 @@ int gen_rand_str(char *buf, int len, bool varlen) {
return len - 1; 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 exec_command(exec_t &exec) {
int pipefd[] = {-1, -1}; int pipefd[] = {-1, -1};
int outfd = -1; int outfd = -1;
@ -151,12 +145,6 @@ void set_nice_name(const char *name) {
prctl(PR_SET_NAME, 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(). * Bionic's atoi runs through strtol().
* Use our own implementation for faster conversion. * Use our own implementation for faster conversion.

View File

@ -8,9 +8,6 @@
#define UID_ROOT 0 #define UID_ROOT 0
#define UID_SHELL 2000 #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 { class mutex_guard {
public: public:
explicit mutex_guard(pthread_mutex_t &m): mutex(&m) { 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(thread_entry entry, void *arg = nullptr, const pthread_attr_t *attr = nullptr);
int new_daemon_thread(std::function<void()> &&entry); int new_daemon_thread(std::function<void()> &&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_dont_care();
int fork_no_orphan(); int fork_no_orphan();
int strend(const char *s1, const char *s2);
void init_argv0(int argc, char **argv); void init_argv0(int argc, char **argv);
void set_nice_name(const char *name); void set_nice_name(const char *name);
uint32_t binary_gcd(uint32_t u, uint32_t v); uint32_t binary_gcd(uint32_t u, uint32_t v);