Magisk/native/jni/magiskhide/proc_monitor.cpp
topjohnwu d8cd2031c7 SIGSTOP any possible process ASAP
Shut down any UID matching process and resume if it turns out not to
be our target. Since we will record every single process we have ever
paused, this means that the same process will not be paused erroneously
for another time.

This is an optimization to hijack the app as soon as possible.
2019-03-02 04:24:41 -05:00

313 lines
7.7 KiB
C++

/* 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <pthread.h>
#include <sys/inotify.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <vector>
#include <string>
#include <map>
#include <algorithm>
#include <magisk.h>
#include <utils.h>
#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);
}
#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;
}
#endif
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<string> 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<string, string> hide_map; /* process -> package_name */
static map<int, int> wd_uid_map; /* inotify wd -> uid */
static map<int, uint64_t> pid_ns_map; /* pid -> last ns inode */
static map<int, vector<string_view>> 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);
}
return ret;
}
static bool parse_packages_xml(string_view &s) {
static const string_view APK_EXT(".apk");
if (!str_starts(s, "<package "))
return true;
/* <package key1="value1" key2="value2"....> */
char *start = (char *) s.data();
start[s.length() - 2] = '\0'; /* Remove trailing '>' */
start += 9; /* Skip '<package ' */
char key[32], value[1024];
char *pkg = nullptr;
int wd = -1;
char *tok;
while ((tok = strtok_r(nullptr, " ", &start))) {
sscanf(tok, "%[^=]=\"%[^\"]", key, value);
string_view key_view(key);
string_view value_view(value);
if (key_view == "name") {
for (auto &hide : hide_map) {
if (hide.second == value_view) {
pkg = hide.second.data();
break;
}
}
if (!pkg)
return true;
} else if (key_view == "codePath") {
if (ends_with(value_view, APK_EXT)) {
// Directly add to inotify list
wd = xinotify_add_watch(inotify_fd, value, IN_OPEN);
} else {
DIR *dir = opendir(value);
if (dir == nullptr)
return true;
struct dirent *entry;
while ((entry = xreaddir(dir))) {
if (ends_with(entry->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<inotify_event *>(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();
}