diff --git a/native/jni/Android.mk b/native/jni/Android.mk index 96789cfd4..7874e0918 100644 --- a/native/jni/Android.mk +++ b/native/jni/Android.mk @@ -35,7 +35,6 @@ LOCAL_SRC_FILES := \ core/applets.cpp \ core/magisk.cpp \ core/daemon.cpp \ - core/logcat.cpp \ core/bootstages.cpp \ core/socket.cpp \ core/db.cpp \ diff --git a/native/jni/core/bootstages.cpp b/native/jni/core/bootstages.cpp index 651316efe..c1e9d1f6a 100644 --- a/native/jni/core/bootstages.cpp +++ b/native/jni/core/bootstages.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include using namespace std; @@ -699,8 +698,6 @@ void post_fs_data(int client) { unblock_boot_process(); } - start_logcat(); - LOGI("* Running post-fs-data.d scripts\n"); exec_common_script("post-fs-data"); diff --git a/native/jni/core/logcat.cpp b/native/jni/core/logcat.cpp deleted file mode 100644 index 04160ada1..000000000 --- a/native/jni/core/logcat.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -static std::vector log_cmd; -static pthread_mutex_t event_lock = PTHREAD_MUTEX_INITIALIZER; -static time_t LAST_TIMESTAMP = 0; - -bool logcat_started = false; - -struct log_listener { - bool enable = false; - bool (*filter)(const char *); - BlockingQueue queue; -}; - -static struct log_listener events[] = { - { /* HIDE_EVENT */ - .filter = [](auto log) -> bool { return strstr(log, "am_proc_start") != nullptr; } - }, - { /* LOG_EVENT */ - .filter = [](auto log) -> bool { return !strstr(log, "am_proc_start"); } - } -}; - -static void init_args() { - // Construct cmdline - log_cmd.push_back(MIRRDIR "/system/bin/logcat"); - // Test whether these buffers actually works - const char *buffers[] = { "main", "events", "crash" }; - for (auto b : buffers) { - if (exec_command_sync(MIRRDIR "/system/bin/logcat", "-b", b, "-d", "-f", "/dev/null") == 0) { - log_cmd.push_back("-b"); - log_cmd.push_back(b); - } - } - chmod("/dev/null", 0666); - log_cmd.insert(log_cmd.end(), { "-v", "threadtime", "-s", "am_proc_start", "Magisk" }); -#ifdef MAGISK_DEBUG - log_cmd.push_back("*:F"); -#endif - log_cmd.push_back(nullptr); -} - -static bool test_logcat() { - int test = exec_command_sync(MIRRDIR "/system/bin/logcat", "-d", "-f", "/dev/null"); - chmod("/dev/null", 0666); - return test == 0; -} - -static void *logcat_gobbler(void *) { - int log_pid; - char line[4096]; - struct tm tm{}; - time_t prev; - - // Set tm year info - time_t now = time(nullptr); - localtime_r(&now, &tm); - - while (true) { - prev = 0; - exec_t exec { - .fd = -1, - .argv = log_cmd.data() - }; - log_pid = exec_command(exec); - FILE *logs = fdopen(exec.fd, "r"); - while (fgets(line, sizeof(line), logs)) { - if (line[0] == '-') - continue; - // Parse timestamp - strptime(line, "%m-%d %H:%M:%S", &tm); - now = mktime(&tm); - if (now < prev) { - /* Log timestamps should be monotonic increasing, if this happens, - * it means that we occur the super rare case: crossing year boundary - * (e.g 2019 -> 2020). Reset and reparse timestamp */ - now = time(nullptr); - localtime_r(&now, &tm); - strptime(line, "%m-%d %H:%M:%S", &tm); - now = mktime(&tm); - } - // Skip old logs - if (now < LAST_TIMESTAMP) - continue; - LAST_TIMESTAMP = prev = now; - pthread_mutex_lock(&event_lock); - for (auto &event : events) { - if (event.enable && event.filter(line)) - event.queue.emplace_back(line); - } - pthread_mutex_unlock(&event_lock); - } - - fclose(logs); - kill(log_pid, SIGTERM); - waitpid(log_pid, nullptr, 0); - - LOGI("logcat: unexpected output EOF"); - - // Wait a few seconds and retry - sleep(2); - if (!test_logcat()) { - // Cancel all events and terminate - logcat_started = false; - for (auto &event : events) - event.queue.cancel(); - return nullptr; - } - } -} - -static void *log_writer(void *) { - rename(LOGFILE, LOGFILE ".bak"); - FILE *log = xfopen(LOGFILE, "ae"); - setbuf(log, nullptr); - auto &queue = start_logging(LOG_EVENT); - while (true) { - fprintf(log, "%s", queue.take().c_str()); - } -} - -BlockingQueue &start_logging(logcat_event event) { - pthread_mutex_lock(&event_lock); - events[event].enable = true; - pthread_mutex_unlock(&event_lock); - return events[event].queue; -} - -void stop_logging(logcat_event event) { - pthread_mutex_lock(&event_lock); - events[event].enable = false; - events[event].queue.clear(); - pthread_mutex_unlock(&event_lock); -} - -bool start_logcat() { - if (logcat_started) - return true; - if (!test_logcat()) - return false; - init_args(); - pthread_t t; - pthread_create(&t, nullptr, log_writer, nullptr); - pthread_detach(t); - pthread_create(&t, nullptr, logcat_gobbler, nullptr); - pthread_detach(t); - logcat_started = true; - return true; -} diff --git a/native/jni/include/logcat.h b/native/jni/include/logcat.h deleted file mode 100644 index c2ec8df0b..000000000 --- a/native/jni/include/logcat.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include -#include - -enum logcat_event { - HIDE_EVENT, - LOG_EVENT -}; - -extern bool logcat_started; - -BlockingQueue &start_logging(logcat_event event); -void stop_logging(logcat_event event); -bool start_logcat(); diff --git a/native/jni/magiskhide/hide_utils.cpp b/native/jni/magiskhide/hide_utils.cpp index c9b16529c..9b2b37680 100644 --- a/native/jni/magiskhide/hide_utils.cpp +++ b/native/jni/magiskhide/hide_utils.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include "magiskhide.h" @@ -197,6 +196,10 @@ int add_list(int client) { char *proc = read_string(client); int ret = add_list(proc); free(proc); + + // Update inotify list + update_apk_list(); + return ret; } @@ -230,6 +233,10 @@ int rm_list(int client) { char *proc = read_string(client); int ret = rm_list(proc); free(proc); + + // Update inotify list + update_apk_list(); + return ret; } @@ -283,9 +290,6 @@ int launch_magiskhide(int client) { if (hide_enabled) return HIDE_IS_ENABLED; - if (!logcat_started) - return LOGCAT_DISABLED; - hide_enabled = true; set_hide_config(); LOGI("* Starting MagiskHide\n"); @@ -328,8 +332,6 @@ int stop_magiskhide() { } void auto_start_magiskhide() { - if (!start_logcat()) - return; db_settings dbs; get_db_settings(&dbs, HIDE_CONFIG); if (dbs[HIDE_CONFIG]) { diff --git a/native/jni/magiskhide/magiskhide.cpp b/native/jni/magiskhide/magiskhide.cpp index b1585c23e..05a82e207 100644 --- a/native/jni/magiskhide/magiskhide.cpp +++ b/native/jni/magiskhide/magiskhide.cpp @@ -110,9 +110,6 @@ int magiskhide_main(int argc, char *argv[]) { switch (code) { case DAEMON_SUCCESS: break; - case LOGCAT_DISABLED: - fprintf(stderr, "Logcat is disabled, cannot start MagiskHide\n"); - break; case HIDE_NOT_ENABLED: fprintf(stderr, "MagiskHide is not enabled\n"); break; diff --git a/native/jni/magiskhide/magiskhide.h b/native/jni/magiskhide/magiskhide.h index f70c8740c..6678f3ba1 100644 --- a/native/jni/magiskhide/magiskhide.h +++ b/native/jni/magiskhide/magiskhide.h @@ -16,6 +16,9 @@ int add_list(int client); int rm_list(int client); void ls_list(int client); +// Update APK list for inotify +void update_apk_list(); + // Process monitor void proc_monitor(); @@ -42,8 +45,7 @@ enum { }; enum { - LOGCAT_DISABLED = DAEMON_LAST, - HIDE_IS_ENABLED, + HIDE_IS_ENABLED = DAEMON_LAST, HIDE_NOT_ENABLED, HIDE_ITEM_EXIST, HIDE_ITEM_NOT_EXIST diff --git a/native/jni/magiskhide/proc_monitor.cpp b/native/jni/magiskhide/proc_monitor.cpp index 98b29d217..6efa4568e 100644 --- a/native/jni/magiskhide/proc_monitor.cpp +++ b/native/jni/magiskhide/proc_monitor.cpp @@ -1,26 +1,31 @@ /* proc_monitor.cpp - Monitor am_proc_start events and unmount * - * We monitor the logcat am_proc_start events. When a target starts up, - * we pause it ASAP, and fork a new process to join its mount namespace - * and do all the unmounting/mocking + * We monitor the listed APK files from /data/app until they get opened + * via inotify to detect a new app launch. + * + * If it's a target we pause it ASAP, and fork a new process to join + * its mount namespace and do all the unmounting/mocking. */ #include #include #include #include +#include #include #include #include +#include #include #include #include #include #include +#include +#include #include #include -#include #include "magiskhide.h" @@ -28,10 +33,12 @@ using namespace std; extern char *system_block, *vendor_block, *data_block; +#define EVENT_SIZE sizeof(struct inotify_event) +#define EVENT_BUF_LEN (1024 * (EVENT_SIZE + 16)) + // Workaround for the lack of pthread_cancel static void term_thread(int) { LOGD("proc_monitor: running cleanup\n"); - stop_logging(HIDE_EVENT); hide_list.clear(); hide_enabled = false; pthread_mutex_destroy(&list_lock); @@ -39,7 +46,7 @@ static void term_thread(int) { pthread_exit(nullptr); } -static int read_ns(const int pid, struct stat *st) { +static inline int read_ns(const int pid, struct stat *st) { char path[32]; sprintf(path, "/proc/%d/ns/mnt", pid); return stat(path, st); @@ -50,19 +57,33 @@ static inline void lazy_unmount(const char* mountpoint) { LOGD("hide_daemon: Unmounted (%s)\n", mountpoint); } -static int parse_ppid(int pid) { +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 inline uid_t get_uid(const int pid) { + char path[16]; + struct stat st; + + sprintf(path, "/proc/%d", pid); + if (stat(path, &st) == -1) + return -1; + + return st.st_uid; +} + static void hide_daemon(int pid) { LOGD("hide_daemon: handling pid=[%d]\n", pid); @@ -106,6 +127,206 @@ exit: _exit(0); } +/* + * Bionic's atoi runs through strtol() and fault-tolerence checkings. + * Since we don't need it, use our own implementation of atoi() + * for faster conversion. + */ +static inline int fast_atoi(const char *str) { + int val = 0; + + while (*str) + val = val * 10 + (*str++ - '0'); + + return val; +} + +// Leave /proc fd opened as we're going to read from it repeatedly +static DIR *dfd; +// Use unordered map with pid and namespace inode number to avoid time-consuming GC +static unordered_map pid_ns_map; +// Use set for slow insertion but fast searching(which we'd encounter a lot more) +static set hide_uid; + +static void detect_new_processes() { + struct dirent *dp; + struct stat ns, pns; + int pid, ppid; + bool hide; + uid_t uid; + unordered_map::const_iterator pos; + + // Iterate through /proc and get a process that reads the target APK + rewinddir(dfd); + while ((dp = readdir(dfd))) { + if (!isdigit(dp->d_name[0])) + continue; + + // dp->d_name is now the pid + pid = fast_atoi(dp->d_name); + + // We're only interested in PIDs > 1000 + if (pid <= 1000) + continue; + + uid = get_uid(pid) % 100000; // Handle multiuser + if (hide_uid.find(uid) != hide_uid.end()) { + // Make sure our target is alive + if ((ppid = parse_ppid(pid)) < 0 || read_ns(ppid, &pns) || read_ns(pid, &ns)) + continue; + + // Check if it's a process we haven't already hijacked + hide = false; + pos = pid_ns_map.find(pid); + if (pos == pid_ns_map.end()) { + hide = true; + pid_ns_map.insert(pair(pid, ns.st_ino)); + } else if (pos->second != ns.st_ino) { + hide = true; + pid_ns_map[pos->first] = ns.st_ino; + } + + if (hide) { + // Send pause signal ASAP + if (kill(pid, SIGSTOP) == -1) + continue; + + /* + * The setns system call do not support multithread processes + * We have to fork a new process, setns, then do the unmounts + */ + LOGI("proc_monitor: UID=[%ju] PID=[%d] ns=[%llu]\n", + (uintmax_t)uid, pid, ns.st_ino); + if (fork_dont_care() == 0) + hide_daemon(pid); + } + } + } +} + +static int inotify_fd = 0; +static void listdir_apk(const char *name) { + DIR *dir; + struct dirent *entry; + const char *ext; + char buf[4096]; + char path[4096]; + char *ptr; + + if (!(dir = opendir(name))) + return; + + while ((entry = readdir(dir)) != NULL) { + snprintf(path, sizeof(path), "%s/%s", name, + entry->d_name); + + if (entry->d_type == DT_DIR) { + if (strcmp(entry->d_name, ".") == 0 + || strcmp(entry->d_name, "..") == 0) + continue; + listdir_apk(path); + } else { + ext = &path[strlen(path) - 4]; + if (!strncmp(".apk", ext, 4)) { + pthread_mutex_lock(&list_lock); + for (auto &s : hide_list) { + // Replace '/' with '\0' to stop reading beyond the actual package name + strcpy(buf, s.c_str()); + if ((ptr = strchr(buf, '/'))) + ptr[0] = '\0'; + + // Compare with (path + 10) to trim "/data/app/" + if (strncmp(path + 10, buf, strlen(buf)) == 0) { + if (inotify_add_watch(inotify_fd, path, IN_OPEN | IN_DELETE) > 0) { + LOGI("proc_monitor: Monitoring %s\n", path, inotify_fd); + } else { + LOGE("proc_monitor: Failed to monitor %s: %s\n", strerror(errno)); + } + break; + } + } + pthread_mutex_unlock(&list_lock); + } + } + } + + closedir(dir); +} + +static void update_pkg_list() { + DIR *dir; + struct dirent *entry; + struct stat st; + char buf[4096]; + char path[4096]; + char *ptr; + const char data_path[] = "/data/data"; + + if (!(dir = opendir(data_path))) + return; + + pthread_mutex_lock(&list_lock); + for (auto &s : hide_list) + LOGD("proc_monitor: hide_list: %s\n", s.c_str()); + pthread_mutex_unlock(&list_lock); + + hide_uid.clear(); + + while ((entry = readdir(dir)) != NULL) { + snprintf(path, sizeof(path), "%s/%s", data_path, + entry->d_name); + + if (entry->d_type == DT_DIR) { + pthread_mutex_lock(&list_lock); + for (auto &s : hide_list) { + // Replace '/' with '\0' to stop reading beyond the actual package name + strcpy(buf, s.c_str()); + if ((ptr = strchr(buf, '/'))) + ptr[0] = '\0'; + + if (strcmp(entry->d_name, buf) == 0) { + if (stat(path, &st) == -1) + continue; + + LOGI("proc_monitor: %s UID is %d\n", buf, st.st_uid); + hide_uid.insert(st.st_uid); + } + } + pthread_mutex_unlock(&list_lock); + } + } + + closedir(dir); +} + +// Iterate through /data/app and search all .apk files +void update_apk_list() { + // Setup inotify + const char data_app[] = "/data/app"; + + if (inotify_fd) + close(inotify_fd); + + inotify_fd = inotify_init(); + if (inotify_fd < 0) { + LOGE("proc_monitor: Cannot initialize inotify: %s\n", strerror(errno)); + term_thread(TERM_THREAD); + } + + LOGI("proc_monitor: Updating APK list\n"); + listdir_apk(data_app); + + // Add /data/app itself to the watch list to detect app (un)installations/updates + if (inotify_add_watch(inotify_fd, data_app, IN_CLOSE_WRITE | IN_MOVED_TO | IN_DELETE) > 0) { + LOGI("proc_monitor: Monitoring %s\n", data_app, inotify_fd); + } else { + LOGE("proc_monitor: Failed to monitor %s: %s\n", strerror(errno)); + } + + // Update pkg_uid_map by reading from /data/data + update_pkg_list(); +} + void proc_monitor() { // Unblock user signals sigset_t block_set; @@ -123,57 +344,44 @@ void proc_monitor() { term_thread(TERM_THREAD); } - auto &queue = start_logging(HIDE_EVENT); - while (true) { - char *log; - int pid, ppid; - struct stat ns, pns; + update_apk_list(); - string line = queue.take(); - if ((log = strchr(&line[0], '[')) == nullptr) + if ((dfd = opendir("/proc")) == NULL) { + LOGE("proc_monitor: Unable to open /proc\n"); + term_thread(TERM_THREAD); + } + + // Detect existing processes for the first time + detect_new_processes(); + + // Read inotify events + struct inotify_event *event; + ssize_t len; + char *p; + char buffer[EVENT_BUF_LEN] __attribute__ ((aligned(__alignof__(struct inotify_event)))); + for (;;) { + len = read(inotify_fd, buffer, EVENT_BUF_LEN); + if (len == -1) { + LOGE("proc_monitor: failed to read from inotify: %s\n", strerror(errno)); + sleep(1); continue; + } - // Extract pid - if (sscanf(log, "[%*d,%d", &pid) != 1) - continue; + for (p = buffer; p < buffer + len; ) { + event = (struct inotify_event *)p; - // Extract last token (component name) - const char *tok, *cpnt = ""; - while ((tok = strtok_r(nullptr, ",[]\n", &log))) - cpnt = tok; - if (cpnt[0] == '\0') - continue; - - // Make sure our target is alive - if ((ppid = parse_ppid(pid)) < 0 || read_ns(ppid, &pns)) - continue; - - bool hide = false; - pthread_mutex_lock(&list_lock); - for (auto &s : hide_list) { - if (strncmp(cpnt, s.c_str(), s.size() - 1) == 0) { - hide = true; + if (event->mask & IN_OPEN) { + // Since we're just watching files, + // extracting file name is not possible from querying event + // LOGI("proc_monitor: inotify: APK opened\n"); + detect_new_processes(); + } else { + LOGI("proc_monitor: inotify: /data/app change detected\n"); + update_apk_list(); break; } + + p += EVENT_SIZE + event->len; } - pthread_mutex_unlock(&list_lock); - - if (!hide) - continue; - - while (read_ns(pid, &ns) == 0 && ns.st_dev == pns.st_dev && ns.st_ino == pns.st_ino) - usleep(500); - - // Send pause signal ASAP - if (kill(pid, SIGSTOP) == -1) - continue; - - /* - * The setns system call do not support multithread processes - * We have to fork a new process, setns, then do the unmounts - */ - LOGI("proc_monitor: %s PID=[%d] ns=[%llu]\n", cpnt, pid, ns.st_ino); - if (fork_dont_care() == 0) - hide_daemon(pid); } }