diff --git a/native/jni/core/bootstages.cpp b/native/jni/core/bootstages.cpp index 40871ad7a..26325b90f 100644 --- a/native/jni/core/bootstages.cpp +++ b/native/jni/core/bootstages.cpp @@ -5,14 +5,14 @@ * execution, load modules, install Magisk Manager etc. */ +#include +#include #include #include #include #include #include #include -#include -#include #include #include @@ -589,16 +589,6 @@ static void dump_logs() { unblock_boot_process(); } -void zygote_notify(int client, struct ucred *cred) { - /* TODO: notify services that need zygote PIDs */ - char *path = read_string(client); - LOGD("%s PID=[%d]\n", path, cred->pid); - close(client); - usleep(100000); - bind_mount(MAGISKTMP "/app_process", path); - free(path); -} - void post_fs_data(int client) { // ack write_int(client, 0); diff --git a/native/jni/include/daemon.h b/native/jni/include/daemon.h index e47cfbc74..7ae9eecac 100644 --- a/native/jni/include/daemon.h +++ b/native/jni/include/daemon.h @@ -61,7 +61,6 @@ void write_key_token(int fd, const char *key, int tok); ***************/ void unlock_blocks(); -void zygote_notify(int client, struct ucred *cred); void post_fs_data(int client); void late_start(int client); void boot_complete(int client); @@ -80,6 +79,8 @@ void install_apk(const char *apk); **************/ void magiskhide_handler(int client); +void zygote_notify(int client, struct ucred *cred); +void zygote_notify(int pid); /************* * Superuser * diff --git a/native/jni/magiskhide/hide_utils.cpp b/native/jni/magiskhide/hide_utils.cpp index 2cc364db7..64e72cece 100644 --- a/native/jni/magiskhide/hide_utils.cpp +++ b/native/jni/magiskhide/hide_utils.cpp @@ -1,3 +1,6 @@ +#include +#include +#include #include #include #include @@ -5,8 +8,6 @@ #include #include #include -#include -#include #include #include @@ -136,7 +137,7 @@ static int add_list(const char *pkg, const char *proc = "") { // Critical region { - MutexGuard lock(map_lock); + MutexGuard lock(monitor_lock); hide_map[proc] = pkg; } @@ -150,14 +151,14 @@ int add_list(int client) { int ret = add_list(pkg, proc); free(pkg); free(proc); - update_inotify_mask(); + update_uid_map(); return ret; } static int rm_list(const char *pkg, const char *proc = "") { { // Critical region - MutexGuard lock(map_lock); + MutexGuard lock(monitor_lock); bool remove = false; if (proc[0] == '\0') { auto next = hide_map.begin(); @@ -200,7 +201,7 @@ int rm_list(int client) { free(pkg); free(proc); if (ret == DAEMON_SUCCESS) - update_inotify_mask(); + update_uid_map(); return ret; } @@ -243,7 +244,7 @@ bool init_list() { rm_list(SAFETYNET_COMPONENT); init_list(SAFETYNET_PKG, SAFETYNET_PROCESS); - update_inotify_mask(); + update_uid_map(); return true; } @@ -300,7 +301,7 @@ void launch_magiskhide(int client) { hide_sensitive_props(); // Initialize the mutex lock - pthread_mutex_init(&map_lock, nullptr); + pthread_mutex_init(&monitor_lock, nullptr); // Initialize the hide list if (!init_list()) @@ -325,7 +326,7 @@ int stop_magiskhide() { hide_enabled = false; set_hide_config(); - pthread_kill(proc_monitor_thread, TERM_THREAD); + pthread_kill(proc_monitor_thread, SIGTERMTHRD); return DAEMON_SUCCESS; } @@ -340,3 +341,22 @@ void auto_start_magiskhide() { }); } } + +int next_zygote = -1; + +void zygote_notify(int pid) { + if (hide_enabled) { + MutexGuard lock(monitor_lock); + next_zygote = pid; + pthread_kill(proc_monitor_thread, SIGZYGOTE); + } +} + +void zygote_notify(int client, struct ucred *cred) { + char *path = read_string(client); + close(client); + zygote_notify(cred->pid); + usleep(100000); + xmount(MAGISKTMP "/app_process", path, nullptr, MS_BIND, nullptr); + free(path); +} diff --git a/native/jni/magiskhide/magiskhide.h b/native/jni/magiskhide/magiskhide.h index dbef73353..cb67ca00e 100644 --- a/native/jni/magiskhide/magiskhide.h +++ b/native/jni/magiskhide/magiskhide.h @@ -5,31 +5,30 @@ #include #include #include -#include #include -#include #include #include #include "daemon.h" -#define TERM_THREAD SIGUSR1 +#define SIGTERMTHRD SIGUSR1 +#define SIGZYGOTE SIGUSR2 #define SAFETYNET_COMPONENT "com.google.android.gms/.droidguard.DroidGuardService" #define SAFETYNET_PROCESS "com.google.android.gms.unstable" #define SAFETYNET_PKG "com.google.android.gms" -// Daemon entries +#define WEVENT(s) (((s) & 0xffff0000) >> 16) + +// CLI entries void launch_magiskhide(int client); int stop_magiskhide(); int add_list(int client); int rm_list(int client); void ls_list(int client); -// Update APK list for inotify -void update_inotify_mask(); - -// Process monitor +// Process monitoring +void *update_uid_map(void * p = nullptr); void proc_monitor(); // Utility functions @@ -38,18 +37,6 @@ void clean_magisk_props(); void crawl_procfs(const std::function &fn); bool proc_name_match(int pid, const char *name); -static inline int get_uid(const int pid) { - char path[16]; - struct stat st; - - sprintf(path, "/proc/%d", pid); - if (stat(path, &st) == -1) - return -1; - - // We don't care about multiuser - return st.st_uid % 100000; -} - /* * Bionic's atoi runs through strtol(). * Use our own implementation for faster conversion. @@ -66,8 +53,9 @@ static inline int parse_int(const char *s) { } extern bool hide_enabled; -extern pthread_mutex_t map_lock; +extern pthread_mutex_t monitor_lock; extern std::map hide_map; +extern int next_zygote; enum { LAUNCH_MAGISKHIDE, diff --git a/native/jni/magiskhide/proc_monitor.cpp b/native/jni/magiskhide/proc_monitor.cpp index 5e3265c4c..8bc81e248 100644 --- a/native/jni/magiskhide/proc_monitor.cpp +++ b/native/jni/magiskhide/proc_monitor.cpp @@ -14,14 +14,12 @@ #include #include #include +#include #include #include #include #include -#include -#include -#include -#include +#include #include #include @@ -33,7 +31,27 @@ using namespace std; extern char *system_block, *vendor_block, *data_block; static int inotify_fd = -1; -static void term_thread(int sig = TERM_THREAD); + +static void term_thread(int sig = SIGTERMTHRD); +static void new_zygote(int pid); + +/************************ + * All the maps and sets + ************************/ + +map hide_map; /* process -> package_name */ +static map zygote_map; /* zygote pid -> mnt ns */ +static map> uid_proc_map; /* uid -> list of process */ + +pthread_mutex_t monitor_lock; + +static set attaches; /* A set of pid that should be attached */ +static set detaches; /* A set of tid that should be detached */ +static set unknown; /* A set of pid/tid that is unknown */ + +/******** + * Utils + ********/ static inline int read_ns(const int pid, struct stat *st) { char path[32]; @@ -46,23 +64,68 @@ static inline void lazy_unmount(const char* mountpoint) { LOGD("hide_daemon: Unmounted (%s)\n", mountpoint); } -#if 0 -static inline int parse_ppid(const int pid) { - char path[32]; - int ppid; - - sprintf(path, "/proc/%d/stat", pid); - FILE *stat = fopen(path, "re"); - if (stat == nullptr) - return -1; - - /* PID COMM STATE PPID ..... */ - fscanf(stat, "%*d %*s %*c %d", &ppid); - fclose(stat); - - return ppid; +static long xptrace(bool log, int request, pid_t pid, void *addr, void *data) { + long ret = ptrace(request, pid, addr, data); + if (log && ret == -1) + PLOGE("ptrace %d", pid); + return ret; } -#endif + +static long xptrace(int request, pid_t pid, void *addr, void *data) { + return xptrace(true, request, pid, addr, data); +} + +static long xptrace(int request, pid_t pid, void *addr = nullptr, intptr_t data = 0) { + return xptrace(true, request, pid, addr, reinterpret_cast(data)); +} + +static bool parse_packages_xml(string_view &s) { + if (!str_starts(s, " */ + char *start = (char *) s.data(); + start[s.length() - 2] = '\0'; /* Remove trailing '>' */ + start += 9; /* Skip ' void { @@ -113,200 +176,271 @@ static void hide_daemon(int pid) { lazy_unmount(s.data()); } -/******************** - * All the damn maps - ********************/ +/************************ + * Async signal handlers + ************************/ -map hide_map; /* process -> package_name */ -static map wd_uid_map; /* inotify wd -> uid */ -static map pid_ns_map; /* pid -> last ns inode */ -static map> uid_proc_map; /* uid -> list of process */ - -// All maps are protected by this lock -pthread_mutex_t map_lock; - -static bool check_pid(int pid, int uid) { - // We're only interested in PIDs > 1000 - if (pid <= 1000) - return true; - - // Not our target UID - if (uid != get_uid(pid)) - return true; - - struct stat ns; - if (read_ns(pid, &ns)) - return true; - - // Check if we have already seen it before - auto pos = pid_ns_map.find(pid); - if (pos != pid_ns_map.end() && pos->second == ns.st_ino) - return true; - - // Will rather kill all just for one - if (kill(pid, SIGSTOP) == -1) - return true; - // Auto send resume signal if return - RunFinally resume([=]() -> void { - kill(pid, SIGCONT); - }); - - // Record this PID - pid_ns_map[pid] = ns.st_ino; - - // Check whether process name match hide list - const char *process = nullptr; - for (auto &proc : uid_proc_map[uid]) - if (proc_name_match(pid, proc.data())) - process = proc.data(); - - if (!process) - return true; - - LOGI("proc_monitor: [%s] UID=[%d] PID=[%d] ns=[%llu]\n", process, uid, pid, ns.st_ino); - - // Disable auto resume PID, and let the daemon do it - resume.disable(); - if (fork_dont_care() == 0) - hide_daemon(pid); - - // We found what we want, stop traversal - return false; -} - -static int xinotify_add_watch(int fd, const char* path, uint32_t mask) { - int ret = inotify_add_watch(fd, path, mask); - if (ret >= 0) { - LOGD("proc_monitor: Monitoring %s\n", path); - } else { - PLOGE("proc_monitor: Monitor %s", path); +static void inotify_event(int) { + /* Make sure we can actually read stuffs + * or else the whole thread will be blocked.*/ + struct pollfd pfd = { + .fd = inotify_fd, + .events = POLLIN, + .revents = 0 + }; + if (poll(&pfd, 1, 0) <= 0) + return; // Nothing to read + char buf[512]; + auto event = reinterpret_cast(buf); + read(inotify_fd, buf, sizeof(buf)); + if ((event->mask & IN_CLOSE_WRITE) && strcmp(event->name, "packages.xml") == 0) { + LOGD("proc_monitor: /data/system/packages.xml updated\n"); + // Use new thread to parse xml, don't block zygote tracing + new_daemon_thread(update_uid_map); } - return ret; } -static bool parse_packages_xml(string_view &s) { - static const string_view APK_EXT(".apk"); - if (!str_starts(s, " */ - char *start = (char *) s.data(); - start[s.length() - 2] = '\0'; /* Remove trailing '>' */ - start += 9; /* Skip 'd_name, APK_EXT)) { - value[value_view.length()] = '/'; - strcpy(value + value_view.length() + 1, entry->d_name); - wd = xinotify_add_watch(inotify_fd, value, IN_OPEN); - break; - } - } - closedir(dir); - } - } else if (key_view == "userId" || key_view == "sharedUserId") { - int uid = parse_int(value); - wd_uid_map[wd] = uid; - for (auto &hide : hide_map) { - if (hide.second == pkg) - uid_proc_map[uid].emplace_back(hide.first); - } - } - } - return true; -} - -void update_inotify_mask() { - int new_inotify = xinotify_init1(IN_CLOEXEC); - if (new_inotify < 0) - term_thread(); - - // Swap out and close old inotify_fd - int tmp = inotify_fd; - inotify_fd = new_inotify; - if (tmp >= 0) - close(tmp); - - LOGD("proc_monitor: Updating inotify list\n"); +static void zygote_sig(int) { + int pid; { - MutexGuard lock(map_lock); - uid_proc_map.clear(); - wd_uid_map.clear(); - file_readline("/data/system/packages.xml", parse_packages_xml, true); + MutexGuard lock(monitor_lock); + pid = next_zygote; + next_zygote = -1; } - xinotify_add_watch(inotify_fd, "/data/system", IN_CLOSE_WRITE); + if (pid > 0) + new_zygote(pid); } // Workaround for the lack of pthread_cancel static void term_thread(int) { LOGD("proc_monitor: cleaning up\n"); + // Clear maps hide_map.clear(); uid_proc_map.clear(); - pid_ns_map.clear(); - wd_uid_map.clear(); + zygote_map.clear(); + // Clear sets + attaches.clear(); + detaches.clear(); + unknown.clear(); + // Misc hide_enabled = false; - pthread_mutex_destroy(&map_lock); + pthread_mutex_destroy(&monitor_lock); close(inotify_fd); inotify_fd = -1; LOGD("proc_monitor: terminate\n"); pthread_exit(nullptr); } -void proc_monitor() { - // Unblock user signals - sigset_t block_set; - sigemptyset(&block_set); - sigaddset(&block_set, TERM_THREAD); - pthread_sigmask(SIG_UNBLOCK, &block_set, nullptr); +/****************** + * Ptrace Madness + ******************/ - // Register the cancel signal - struct sigaction act{}; - act.sa_handler = term_thread; - sigaction(TERM_THREAD, &act, nullptr); +/* Ptrace is super tricky, preserve all excessive debug in code + * but disable when actually building for usage (you won't want + * your logcat spammed with new thread events from all apps) */ - // Read inotify events - ssize_t len; - int uid; - char buf[512]; - auto event = reinterpret_cast(buf); - while ((len = read(inotify_fd, buf, sizeof(buf))) >= 0) { - if (len < sizeof(*event)) - continue; - if (event->mask & IN_OPEN) { - MutexGuard lock(map_lock); - uid = wd_uid_map[event->wd]; - crawl_procfs([=](int pid) -> bool { return check_pid(pid, uid); }); - } else if ((event->mask & IN_CLOSE_WRITE) && strcmp(event->name, "packages.xml") == 0) { - LOGD("proc_monitor: /data/system/packages.xml updated\n"); - update_inotify_mask(); +//#define PTRACE_LOG(fmt, args...) LOGD("PID=[%d] " fmt, pid, ##args) +#define PTRACE_LOG(...) + +static bool check_pid(int pid) { + char path[128]; + char cmdline[1024]; + sprintf(path, "/proc/%d/cmdline", pid); + FILE *f = fopen(path, "re"); + // Process killed unexpectedly, ignore + if (!f) return true; + fgets(cmdline, sizeof(cmdline), f); + fclose(f); + if (strncmp(cmdline, "zygote", 6) == 0) + return false; + + /* This process is fully initialized, we will stop + * tracing it no matter if it is a target or not. */ + attaches.erase(pid); + + sprintf(path, "/proc/%d", pid); + struct stat st; + lstat(path, &st); + int uid = st.st_uid % 100000; + 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; + + /* Finally this is our target! + * Detach from ptrace but should still remain stopped. + * The hide daemon will resume the process. */ + xptrace(PTRACE_DETACH, pid, nullptr, SIGSTOP); + LOGI("proc_monitor: [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid); + PTRACE_LOG("target found\n"); + if (fork_dont_care() == 0) + hide_daemon(pid); + return true; + } + } + } + PTRACE_LOG("not our target\n"); + xptrace(PTRACE_DETACH, pid); + return true; +} + +static void handle_unknown(int tid, int pid = -1) { + if (unknown.count(tid)) { + unknown.erase(tid); + tgkill(pid < 0 ? tid : pid, tid, SIGSTOP); + } +} + +static void new_zygote(int pid) { + if (zygote_map.count(pid)) + return; + + LOGD("proc_monitor: ptrace zygote PID=[%d]\n", pid); + + struct stat st; + if (read_ns(pid, &st)) + return; + zygote_map[pid] = st; + + xptrace(PTRACE_ATTACH, pid); + + waitpid(pid, nullptr, __WALL | __WNOTHREAD); + xptrace(PTRACE_SETOPTIONS, pid, nullptr, + PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACEEXIT); + xptrace(PTRACE_CONT, pid); +} + +void proc_monitor() { + inotify_fd = xinotify_init1(IN_CLOEXEC); + if (inotify_fd < 0) + term_thread(); + + // Unblock some signals + sigset_t block_set; + sigemptyset(&block_set); + sigaddset(&block_set, SIGTERMTHRD); + sigaddset(&block_set, SIGZYGOTE); + sigaddset(&block_set, SIGIO); + pthread_sigmask(SIG_UNBLOCK, &block_set, nullptr); + + struct sigaction act{}; + act.sa_handler = term_thread; + sigaction(SIGTERMTHRD, &act, nullptr); + act.sa_handler = zygote_sig; + sigaction(SIGZYGOTE, &act, nullptr); + act.sa_handler = inotify_event; + sigaction(SIGIO, &act, nullptr); + + // Setup inotify asynchronous I/O + fcntl(inotify_fd, F_SETFL, O_ASYNC); + struct f_owner_ex ex = { + .type = F_OWNER_TID, + .pid = gettid() + }; + fcntl(inotify_fd, F_SETOWN_EX, &ex); + + // Start monitoring packages.xml + inotify_add_watch(inotify_fd, "/data/system", IN_CLOSE_WRITE); + + // First find existing zygotes + crawl_procfs([](int pid) -> bool { + char buf[512]; + snprintf(buf, sizeof(buf), "/proc/%d/cmdline", pid); + FILE *f = fopen(buf, "re"); + if (f) { + fgets(buf, sizeof(buf), f); + if (strncmp(buf, "zygote", 6) == 0) + new_zygote(pid); + fclose(f); + } + return true; + }); + + int status; + + for (;;) { + int pid = waitpid(-1, &status, __WALL | __WNOTHREAD); + if (pid < 0) + continue; + if (WIFSTOPPED(status)) { + if (detaches.count(pid)) { + PTRACE_LOG("detach\n"); + detaches.erase(pid); + xptrace(PTRACE_DETACH, pid); + continue; + } + if (WSTOPSIG(status) == SIGTRAP && WEVENT(status)) { + unsigned long msg; + xptrace(PTRACE_GETEVENTMSG, pid, nullptr, &msg); + if (zygote_map.count(pid)) { + // Zygote event + switch (WEVENT(status)) { + case PTRACE_EVENT_FORK: + case PTRACE_EVENT_VFORK: + PTRACE_LOG("zygote forked: [%d]\n", msg); + attaches.insert(msg); + handle_unknown(msg); + break; + case PTRACE_EVENT_EXIT: + PTRACE_LOG("zygote exited with status: [%d]\n", msg); + zygote_map.erase(pid); + break; + default: + PTRACE_LOG("unknown event: %d\n", WEVENT(status)); + break; + } + xptrace(PTRACE_CONT, pid); + } else { + switch (WEVENT(status)) { + case PTRACE_EVENT_CLONE: + PTRACE_LOG("create new threads: [%d]\n", msg); + detaches.insert(msg); + handle_unknown(msg, pid); + if (attaches.count(pid) && check_pid(pid)) + continue; + break; + case PTRACE_EVENT_EXIT: + PTRACE_LOG("exited with status: [%d]\n", msg); + attaches.erase(pid); + unknown.erase(pid); + break; + default: + PTRACE_LOG("unknown event: %d\n", WEVENT(status)); + break; + } + xptrace(PTRACE_CONT, pid); + } + } else if (WSTOPSIG(status) == SIGSTOP) { + if (attaches.count(pid)) { + PTRACE_LOG("SIGSTOP from zygote child\n"); + xptrace(PTRACE_SETOPTIONS, pid, nullptr, PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXIT); + } else { + PTRACE_LOG("SIGSTOP from unknown\n"); + unknown.insert(pid); + } + xptrace(PTRACE_CONT, pid); + } else { + // Not caused by us, resend signal + xptrace(PTRACE_CONT, pid, nullptr, WSTOPSIG(status)); + PTRACE_LOG("signal [%d]\n", WSTOPSIG(status)); + } + } else { + // Nothing to do with us + ptrace(PTRACE_DETACH, pid); + PTRACE_LOG("terminate\n"); } } - PLOGE("proc_monitor: read inotify"); - term_thread(); }