/* proc_monitor.c - 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 */ #include #include #include #include #include #include #include #include #include #include "magisk.h" #include "utils.h" #include "magiskhide.h" static char init_ns[32], zygote_ns[2][32], cache_block[256]; static int hide_queue = 0, zygote_num, has_cache = 1, pipefd[2] = { -1, -1 }; // Workaround for the lack of pthread_cancel static void term_thread(int sig) { LOGD("proc_monitor: running cleanup\n"); destroy_list(); hideEnabled = 0; // Unregister listener log_events[HIDE_EVENT].fd = -1; close(pipefd[0]); close(pipefd[1]); pipefd[0] = pipefd[1] = -1; pthread_mutex_destroy(&hide_lock); pthread_mutex_destroy(&file_lock); LOGD("proc_monitor: terminating...\n"); pthread_exit(NULL); } static void hide_done(int sig) { --hide_queue; if (hide_queue == 0) { xmount(NULL, "/", NULL, MS_REMOUNT, NULL); xsymlink(DATABIN, "/data/magisk"); xsymlink(MAINIMG, "/data/magisk.img"); xsymlink(MOUNTPOINT, "/magisk"); xmount(NULL, "/", NULL, MS_REMOUNT | MS_RDONLY, NULL); } } static int read_namespace(const int pid, char* target, const size_t size) { char path[32]; snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid); if (access(path, R_OK) == -1) return 1; xreadlink(path, target, size); return 0; } static void store_zygote_ns(int pid) { if (zygote_num == 2) return; do { usleep(500); read_namespace(pid, zygote_ns[zygote_num], 32); } while (strcmp(zygote_ns[zygote_num], init_ns) == 0); ++zygote_num; } static void lazy_unmount(const char* mountpoint) { if (xumount2(mountpoint, MNT_DETACH) != -1) LOGD("hide_daemon: Unmounted (%s)\n", mountpoint); } static void hide_daemon(int pid, int ppid) { LOGD("hide_daemon: start unmount for pid=[%d]\n", pid); strcpy(argv0, "hide_daemon"); char *line, buffer[PATH_MAX]; struct vector mount_list; manage_selinux(); clean_magisk_props(); if (switch_mnt_ns(pid)) goto exit; snprintf(buffer, sizeof(buffer), "/proc/%d/mounts", pid); vec_init(&mount_list); file_to_vector(buffer, &mount_list); // Find the cache block name if not found yet if (has_cache && cache_block[0] == '\0') { vec_for_each(&mount_list, line) { if (strstr(line, " /cache ")) { sscanf(line, "%256s", cache_block); break; } } if (strlen(cache_block) == 0) has_cache = 0; } // Unmout cache mounts if (has_cache) { vec_for_each(&mount_list, line) { if (strstr(line, cache_block) && (strstr(line, " /system/") || strstr(line, " /vendor/"))) { sscanf(line, "%*s %4096s", buffer); lazy_unmount(buffer); } } } // Unmount dummy skeletons, /sbin links vec_for_each(&mount_list, line) { if (strstr(line, "tmpfs /system") || strstr(line, "tmpfs /vendor") || strstr(line, "tmpfs /sbin")) { sscanf(line, "%*s %4096s", buffer); lazy_unmount(buffer); } free(line); } vec_destroy(&mount_list); // Re-read mount infos snprintf(buffer, sizeof(buffer), "/proc/%d/mounts", pid); vec_init(&mount_list); file_to_vector(buffer, &mount_list); // Unmount any loop mounts vec_for_each(&mount_list, line) { if (strstr(line, "/dev/block/loop")) { sscanf(line, "%*s %4096s", buffer); lazy_unmount(buffer); } free(line); } exit: // Send resume signal kill(pid, SIGCONT); // Free up memory vec_destroy(&mount_list); // Wait a while and link it back sleep(10); kill(ppid, HIDE_DONE); _exit(0); } void proc_monitor() { // Unblock user signals sigset_t block_set; sigemptyset(&block_set); sigaddset(&block_set, TERM_THREAD); sigaddset(&block_set, HIDE_DONE); pthread_sigmask(SIG_UNBLOCK, &block_set, NULL); // Register the cancel signal struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = term_thread; sigaction(TERM_THREAD, &act, NULL); act.sa_handler = hide_done; sigaction(HIDE_DONE, &act, NULL); cache_block[0] = '\0'; // Get the mount namespace of init if (read_namespace(1, init_ns, 32)) { LOGE("proc_monitor: Your kernel doesn't support mount namespace :(\n"); term_thread(TERM_THREAD); } LOGI("proc_monitor: init ns=%s\n", init_ns); // Get the mount namespace of zygote zygote_num = 0; while(!zygote_num) { // Check zygote every 10 ms usleep(10000); ps_filter_proc_name("zygote", store_zygote_ns); } ps_filter_proc_name("zygote64", store_zygote_ns); switch(zygote_num) { case 1: LOGI("proc_monitor: zygote ns=%s\n", zygote_ns[0]); break; case 2: LOGI("proc_monitor: zygote ns=%s zygote64 ns=%s\n", zygote_ns[0], zygote_ns[1]); break; } // Register our listener to logcat monitor xpipe2(pipefd, O_CLOEXEC); log_events[HIDE_EVENT].fd = pipefd[1]; for (char *log, *line;; free(log)) { if (read(pipefd[0], &log, sizeof(log)) != sizeof(log)) { /* It might be interrupted */ log = NULL; continue; } char *ss = strchr(log, '['); int pid, ret, comma = 0; char *pos = ss, processName[256], ns[32]; while(1) { pos = strchr(pos, ','); if(pos == NULL) break; pos[0] = ' '; ++comma; } if (comma == 6) ret = sscanf(ss, "[%*d %d %*d %*d %256s", &pid, processName); else ret = sscanf(ss, "[%*d %d %*d %256s", &pid, processName); if(ret != 2) continue; // Critical region pthread_mutex_lock(&hide_lock); vec_for_each(hide_list, line) { if (strcmp(processName, line) == 0) { while(1) { ret = 1; for (int i = 0; i < zygote_num; ++i) { read_namespace(pid, ns, sizeof(ns)); if (strcmp(ns, zygote_ns[i]) == 0) { usleep(50); ret = 0; break; } } if (ret) break; } // Send pause signal ASAP if (kill(pid, SIGSTOP) == -1) continue; LOGI("proc_monitor: %s (PID=%d ns=%s)\n", processName, pid, ns); xmount(NULL, "/", NULL, MS_REMOUNT, NULL); unlink("/magisk"); unlink("/data/magisk"); unlink("/data/magisk.img"); unlink(MAGISKRC); xmount(NULL, "/", NULL, MS_REMOUNT | MS_RDONLY, NULL); ++hide_queue; /* * The setns system call do not support multithread processes * We have to fork a new process, setns, then do the unmounts */ int selfpid = getpid(); if (fork_dont_care() == 0) hide_daemon(pid, selfpid); break; } } pthread_mutex_unlock(&hide_lock); } }