diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt index ad49ede58..dc26bb7ad 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt @@ -227,25 +227,14 @@ object Zygisk : BaseSettingsItem.Toggle() { get() = Config.zygisk set(value) { Config.zygisk = value - DenyList.isEnabled = value - DenyListConfig.isEnabled = value notifyPropertyChanged(BR.description) - DenyList.notifyPropertyChanged(BR.description) } val mismatch get() = value != Info.isZygiskEnabled } object DenyList : BaseSettingsItem.Toggle() { override val title = R.string.settings_denylist_title.asText() - override val description get() = - if (isEnabled) { - if (Zygisk.mismatch) - R.string.reboot_apply_change.asText() - else - R.string.settings_denylist_summary.asText() - } else { - R.string.settings_denylist_error.asText(R.string.zygisk.asText()) - } + override val description get() = R.string.settings_denylist_summary.asText() override var value = Config.denyList set(value) { @@ -260,18 +249,11 @@ object DenyList : BaseSettingsItem.Toggle() { } } } - - override fun refresh() { - isEnabled = Zygisk.value - } } object DenyListConfig : BaseSettingsItem.Blank() { override val title = R.string.settings_denylist_config_title.asText() override val description = R.string.settings_denylist_config_summary.asText() - override fun refresh() { - isEnabled = Zygisk.value - } } // --- Superuser diff --git a/native/src/Android.mk b/native/src/Android.mk index 4ab294341..91822e1c4 100644 --- a/native/src/Android.mk +++ b/native/src/Android.mk @@ -37,7 +37,8 @@ LOCAL_SRC_FILES := \ core/zygisk/module.cpp \ core/zygisk/hook.cpp \ core/deny/cli.cpp \ - core/deny/utils.cpp + core/deny/utils.cpp \ + core/deny/logcat.cpp LOCAL_LDLIBS := -llog LOCAL_LDFLAGS := -Wl,--dynamic-list=src/exported_sym.txt diff --git a/native/src/core/deny/cli.cpp b/native/src/core/deny/cli.cpp index f07db3a99..3fabf017e 100644 --- a/native/src/core/deny/cli.cpp +++ b/native/src/core/deny/cli.cpp @@ -53,8 +53,7 @@ void denylist_handler(int client, const sock_cred *cred) { ls_list(client); return; case DenyRequest::STATUS: - res = (zygisk_enabled && denylist_enforced) - ? DenyResponse::ENFORCED : DenyResponse::NOT_ENFORCED; + res = denylist_enforced ? DenyResponse::ENFORCED : DenyResponse::NOT_ENFORCED; break; default: // Unknown request code diff --git a/native/src/core/deny/deny.hpp b/native/src/core/deny/deny.hpp index 557b92c42..dfbedc8d7 100644 --- a/native/src/core/deny/deny.hpp +++ b/native/src/core/deny/deny.hpp @@ -44,3 +44,7 @@ int disable_deny(); int add_list(int client); int rm_list(int client); void ls_list(int client); + +bool proc_context_match(int pid, std::string_view context); +void *logcat(void *arg); +extern bool logcat_exit; diff --git a/native/src/core/deny/logcat.cpp b/native/src/core/deny/logcat.cpp new file mode 100644 index 000000000..2e2c502fc --- /dev/null +++ b/native/src/core/deny/logcat.cpp @@ -0,0 +1,266 @@ +#include +#include +#include +#include + +#include + +#include "deny.hpp" + +using namespace std; + +extern "C" { + +struct logger_entry { + uint16_t len; /* length of the payload */ + uint16_t hdr_size; /* sizeof(struct logger_entry) */ + int32_t pid; /* generating process's pid */ + uint32_t tid; /* generating process's tid */ + uint32_t sec; /* seconds since Epoch */ + uint32_t nsec; /* nanoseconds */ + uint32_t lid; /* log id of the payload, bottom 4 bits currently */ + uint32_t uid; /* generating process's uid */ +}; + +#define LOGGER_ENTRY_MAX_LEN (5 * 1024) +struct log_msg { + union [[gnu::aligned(4)]] { + unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1]; + struct logger_entry entry; + }; +}; + +typedef struct AndroidLogEntry_t { + time_t tv_sec; + long tv_nsec; + android_LogPriority priority; + int32_t uid; + int32_t pid; + int32_t tid; + const char *tag; + size_t tagLen; + size_t messageLen; + const char *message; +} AndroidLogEntry; + +[[gnu::weak]] struct logger_list *android_logger_list_alloc(int mode, unsigned int tail, pid_t pid); +[[gnu::weak]] void android_logger_list_free(struct logger_list *list); +[[gnu::weak]] int android_logger_list_read(struct logger_list *list, struct log_msg *log_msg); +[[gnu::weak]] struct logger *android_logger_open(struct logger_list *list, log_id_t id); +[[gnu::weak]] int android_log_processLogBuffer(struct logger_entry *buf, AndroidLogEntry *entry); + +typedef struct [[gnu::packed]] { + int32_t tag; // Little Endian Order +} android_event_header_t; + +typedef struct [[gnu::packed]] { + int8_t type; // EVENT_TYPE_INT + int32_t data; // Little Endian Order +} android_event_int_t; + +typedef struct [[gnu::packed]] { + int8_t type; // EVENT_TYPE_STRING; + int32_t length; // Little Endian Order + char data[]; +} android_event_string_t; + +typedef struct [[gnu::packed]] { + int8_t type; // EVENT_TYPE_LIST + int8_t element_count; +} android_event_list_t; + +// 30014 am_proc_start (User|1|5),(PID|1|5),(UID|1|5),(Process Name|3),(Type|3),(Component|3) +typedef struct [[gnu::packed]] { + android_event_header_t tag; + android_event_list_t list; + android_event_int_t user; + android_event_int_t pid; + android_event_int_t uid; + android_event_string_t process_name; +// android_event_string_t type; +// android_event_string_t component; +} android_event_am_proc_start; + +// 3040 boot_progress_ams_ready (time|2|3) + +} + +// zygote pid -> mnt ns +static map zygote_map; +bool logcat_exit; + +static int read_ns(const int pid, struct stat *st) { + char path[32]; + sprintf(path, "/proc/%d/ns/mnt", pid); + return stat(path, st); +} + +static int parse_ppid(int pid) { + char path[32]; + int ppid; + sprintf(path, "/proc/%d/stat", pid); + auto stat = open_file(path, "re"); + if (!stat) return -1; + // PID COMM STATE PPID ..... + fscanf(stat.get(), "%*d %*s %*c %d", &ppid); + return ppid; +} + +static void check_zygote() { + zygote_map.clear(); + auto proc = open("/proc", O_RDONLY | O_CLOEXEC); + auto proc_dir = xopen_dir(proc); + if (!proc_dir) return; + dirent *entry; + int pid; + struct stat st{}; + while ((entry = readdir(proc_dir.get()))) { + pid = parse_int(entry->d_name); + if (pid <= 0) continue; + if (fstatat(proc, entry->d_name, &st, 0)) continue; + if (st.st_uid != 0) continue; + if (proc_context_match(pid, "u:r:zygote:s0") && parse_ppid(pid) == 1) { + if (read_ns(pid, &st) == 0) { + LOGI("logcat: zygote PID=[%d]\n", pid); + zygote_map[pid] = st; + } + } + } +} + +static void handle_proc(int pid, bool app_zygote = false) { + if (fork_dont_care() == 0) { + if (app_zygote) { + revert_unmount(pid); + kill(pid, SIGCONT); + _exit(0); + } + + int ppid = parse_ppid(pid); + auto it = zygote_map.find(ppid); + if (it == zygote_map.end()) { + LOGW("logcat: skip PID=[%d] PPID=[%d]\n", pid, ppid); + _exit(0); + } + + char path[16]; + struct stat st{}; + sprintf(path, "/proc/%d", pid); + while (read_ns(pid, &st) == 0 && it->second.st_ino == st.st_ino) { + if (stat(path, &st) == 0 && st.st_uid == 0) { + usleep(10 * 1000); + } else { + LOGW("logcat: skip PID=[%d] UID=[%d]\n", pid, st.st_uid); + _exit(0); + } + } + + revert_unmount(pid); + _exit(0); + } +} + +static void process_main_buffer(struct logger_entry *buf) { + AndroidLogEntry entry; + if (android_log_processLogBuffer(buf, &entry) < 0) return; + entry.tagLen--; + auto tag = string_view(entry.tag, entry.tagLen); + + static bool ready = false; + if (tag == "AppZygote") { + if (entry.uid != 1000) return; + if (entry.message[0] == 'S') { + ready = true; + } else { + ready = false; + } + return; + } + + if (!ready || tag != "AppZygoteInit") return; + if (!proc_context_match(buf->pid, "u:r:app_zygote:s0")) return; + ready = false; + + char cmdline[1024]; + sprintf(cmdline, "/proc/%d/cmdline", buf->pid); + if (auto f = open_file(cmdline, "re")) { + fgets(cmdline, sizeof(cmdline), f.get()); + } else { + return; + } + + if (is_deny_target(entry.uid, cmdline)) { + kill(buf->pid, SIGSTOP); + LOGI("logcat: [%s] PID=[%d] UID=[%d]\n", cmdline, buf->pid, entry.uid); + handle_proc(buf->pid, true); + } +} + +static void process_events_buffer(struct logger_entry *buf) { + if (buf->uid != 1000) return; + auto *event_data = reinterpret_cast(buf) + buf->hdr_size; + auto *event_header = reinterpret_cast(event_data); + if (event_header->tag == 30014) { + auto *am_proc_start = reinterpret_cast(event_data); + auto proc = string_view(am_proc_start->process_name.data, + am_proc_start->process_name.length); + if (is_deny_target(am_proc_start->uid.data, proc)) { + LOGI("logcat: [%.*s] PID=[%d] UID=[%d]\n", + am_proc_start->process_name.length, am_proc_start->process_name.data, + am_proc_start->pid.data, am_proc_start->uid.data); + handle_proc(am_proc_start->pid.data); + } + return; + } + if (event_header->tag == 3040) { + LOGD("logcat: soft reboot\n"); + check_zygote(); + } +} + +[[noreturn]] void run() { + while (true) { + const unique_ptr logger_list{ + android_logger_list_alloc(0, 1, 0), &android_logger_list_free}; + + for (log_id id: {LOG_ID_MAIN, LOG_ID_EVENTS}) { + auto *logger = android_logger_open(logger_list.get(), id); + if (logger == nullptr) continue; + } + + struct log_msg msg{}; + while (true) { + if (logcat_exit) { + break; + } + + if (android_logger_list_read(logger_list.get(), &msg) <= 0) { + break; + } + + switch (msg.entry.lid) { + case LOG_ID_EVENTS: + process_events_buffer(&msg.entry); + break; + case LOG_ID_MAIN: + process_main_buffer(&msg.entry); + default: + break; + } + } + + if (logcat_exit) { + break; + } + + sleep(1); + } + + LOGD("logcat: terminate\n"); + pthread_exit(nullptr); +} + +void *logcat(void *) { + check_zygote(); + run(); +} diff --git a/native/src/core/deny/utils.cpp b/native/src/core/deny/utils.cpp index 63629d5d5..501543e63 100644 --- a/native/src/core/deny/utils.cpp +++ b/native/src/core/deny/utils.cpp @@ -33,8 +33,6 @@ static pthread_mutex_t data_lock = PTHREAD_MUTEX_INITIALIZER; atomic denylist_enforced = false; -#define do_kill (zygisk_enabled && denylist_enforced) - static void rescan_apps() { LOGD("denylist: rescanning apps\n"); @@ -121,7 +119,7 @@ static bool proc_name_match(int pid, string_view name) { return false; } -static bool proc_context_match(int pid, string_view context) { +bool proc_context_match(int pid, string_view context) { char buf[PATH_MAX]; sprintf(buf, "/proc/%d/attr/current", pid); if (auto fp = open_file(buf, "re")) { @@ -186,7 +184,7 @@ static bool add_hide_set(const char *pkg, const char *proc) { if (!p.second) return false; LOGI("denylist add: [%s/%s]\n", pkg, proc); - if (!do_kill) + if (!denylist_enforced) return true; if (str_eql(pkg, ISOLATED_MAGIC)) { // Kill all matching isolated processes @@ -350,15 +348,21 @@ int enable_deny() { LOGI("* Enable DenyList\n"); - denylist_enforced = true; - if (!ensure_data()) { denylist_enforced = false; return DenyResponse::ERROR; } + if (!zygisk_enabled) { + logcat_exit = false; + if (new_daemon_thread(&logcat)) + return DenyResponse::ERROR; + } + + denylist_enforced = true; + // On Android Q+, also kill blastula pool and all app zygotes - if (SDK_INT >= 29 && zygisk_enabled) { + if (SDK_INT >= 29) { kill_process("usap32", true); kill_process("usap64", true); kill_process<&proc_context_match>("u:r:app_zygote:s0", true); @@ -373,6 +377,10 @@ int disable_deny() { if (denylist_enforced) { denylist_enforced = false; LOGI("* Disable DenyList\n"); + + if (!zygisk_enabled) { + logcat_exit = true; + } } update_deny_config(); return DenyResponse::OK; diff --git a/native/src/core/include/core.hpp b/native/src/core/include/core.hpp index ee42eb80a..b6ac4ec24 100644 --- a/native/src/core/include/core.hpp +++ b/native/src/core/include/core.hpp @@ -91,3 +91,4 @@ extern std::atomic denylist_enforced; int denylist_cli(int argc, char **argv); void initialize_denylist(); bool is_deny_target(int uid, std::string_view process); +void revert_unmount(int pid = -1) noexcept; diff --git a/native/src/core/lib.rs b/native/src/core/lib.rs index 67a69ac7c..3a13ebc3a 100644 --- a/native/src/core/lib.rs +++ b/native/src/core/lib.rs @@ -72,6 +72,8 @@ pub mod ffi { #[cxx_name = "resolve_preinit_dir_rs"] fn resolve_preinit_dir(base_dir: Utf8CStrRef) -> String; + fn switch_mnt_ns(pid: i32) -> i32; + #[cxx_name = "MagiskD"] type CxxMagiskD; fn post_fs_data(self: &CxxMagiskD) -> bool; @@ -90,7 +92,7 @@ pub mod ffi { fn read_certificate(fd: i32, version: i32) -> Vec; fn setup_mounts(); fn find_preinit_device() -> String; - fn revert_unmount(); + fn revert_unmount(pid: i32); unsafe fn persist_get_prop(name: Utf8CStrRef, prop_cb: Pin<&mut PropCb>); unsafe fn persist_get_props(prop_cb: Pin<&mut PropCb>); unsafe fn persist_delete_prop(name: Utf8CStrRef) -> bool; diff --git a/native/src/core/mount.rs b/native/src/core/mount.rs index 7eeb6c4a5..49203e967 100644 --- a/native/src/core/mount.rs +++ b/native/src/core/mount.rs @@ -10,7 +10,7 @@ use base::{ }; use crate::consts::{MODULEMNT, MODULEROOT, PREINITDEV, PREINITMIRR, WORKERDIR}; -use crate::ffi::{get_magisk_tmp, resolve_preinit_dir}; +use crate::ffi::{get_magisk_tmp, resolve_preinit_dir, switch_mnt_ns}; use crate::get_prop; pub fn setup_mounts() { @@ -275,7 +275,14 @@ pub fn find_preinit_device() -> String { .to_string() } -pub fn revert_unmount() { +pub fn revert_unmount(pid: i32) { + if pid > 0 { + if switch_mnt_ns(pid) != 0 { + return; + } + debug!("denylist: handling PID=[{}]", pid); + } + let mut targets = Vec::new(); // Unmount Magisk tmpfs and mounts from module files