/* proc_monitor.cpp - Monitor am_proc_start events and unmount * * 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 "magiskhide.h" 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 inline int read_ns(const int pid, struct stat *st) { char path[32]; sprintf(path, "/proc/%d/ns/mnt", pid); return stat(path, st); } static inline void lazy_unmount(const char* mountpoint) { if (umount2(mountpoint, MNT_DETACH) != -1) LOGD("hide_daemon: Unmounted (%s)\n", mountpoint); } 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 void hide_daemon(int pid) { RunFinally fin([=]() -> void { // Send resume signal kill(pid, SIGCONT); _exit(0); }); if (switch_mnt_ns(pid)) return; LOGD("hide_daemon: handling PID=[%d]\n", pid); manage_selinux(); clean_magisk_props(); vector targets; // Unmount dummy skeletons and /sbin links file_readline("/proc/self/mounts", [&](string_view &s) -> bool { if (str_contains(s, "tmpfs /system/") || str_contains(s, "tmpfs /vendor/") || str_contains(s, "tmpfs /sbin")) { char *path = (char *) s.data(); // Skip first token strtok_r(nullptr, " ", &path); targets.emplace_back(strtok_r(nullptr, " ", &path)); } return true; }); for (auto &s : targets) lazy_unmount(s.data()); targets.clear(); // Unmount everything under /system, /vendor, and data mounts file_readline("/proc/self/mounts", [&](string_view &s) -> bool { if ((str_contains(s, " /system/") || str_contains(s, " /vendor/")) && (str_contains(s, system_block) || str_contains(s, vendor_block) || str_contains(s, data_block))) { char *path = (char *) s.data(); // Skip first token strtok_r(nullptr, " ", &path); targets.emplace_back(strtok_r(nullptr, " ", &path)); } return true; }); for (auto &s : targets) lazy_unmount(s.data()); } /******************** * All the damn maps ********************/ 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, pns; int ppid; // Make sure we can read mount namespace if ((ppid = parse_ppid(pid)) < 0 || read_ns(pid, &ns) || read_ns(ppid, &pns)) return true; // mount namespace is not separated, we only unmount once if (ns.st_dev == pns.st_dev && ns.st_ino == pns.st_ino) return true; // Check if it's a process we haven't already hijacked auto pos = pid_ns_map.find(pid); if (pos != pid_ns_map.end() && pos->second == ns.st_ino) return true; // 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; // Send pause signal ASAP if (kill(pid, SIGSTOP) == -1) return true; pid_ns_map[pid] = ns.st_ino; LOGI("proc_monitor: [%s] UID=[%d] PID=[%d] ns=[%llu]\n", process, uid, pid, ns.st_ino); /* * The setns system call do not support multithread processes * We have to fork a new process, setns, then do the unmounts */ if (fork_dont_care() == 0) hide_daemon(pid); 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); } 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"); { MutexGuard lock(map_lock); uid_proc_map.clear(); wd_uid_map.clear(); file_readline("/data/system/packages.xml", parse_packages_xml, true); } xinotify_add_watch(inotify_fd, "/data/system", IN_CLOSE_WRITE); } // Workaround for the lack of pthread_cancel static void term_thread(int) { LOGD("proc_monitor: cleaning up\n"); hide_map.clear(); uid_proc_map.clear(); pid_ns_map.clear(); wd_uid_map.clear(); hide_enabled = false; pthread_mutex_destroy(&map_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); // Register the cancel signal struct sigaction act{}; act.sa_handler = term_thread; sigaction(TERM_THREAD, &act, nullptr); // 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(); } } PLOGE("proc_monitor: read inotify"); term_thread(); }