Magisk/native/jni/magiskhide/proc_monitor.cpp

435 lines
11 KiB
C++
Raw Normal View History

2018-11-01 18:23:12 +01:00
/* proc_monitor.cpp - Monitor am_proc_start events and unmount
2017-08-01 09:34:16 +02:00
*
* 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.
2017-04-06 00:12:29 +02:00
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
2018-07-13 16:14:32 +02:00
#include <fcntl.h>
2017-04-06 00:12:29 +02:00
#include <signal.h>
#include <pthread.h>
#include <sys/ptrace.h>
#include <sys/inotify.h>
2017-04-06 00:12:29 +02:00
#include <sys/types.h>
#include <sys/wait.h>
2017-07-10 17:39:33 +02:00
#include <sys/mount.h>
2017-04-06 00:12:29 +02:00
#include <magisk.h>
#include <utils.h>
2016-12-30 19:44:24 +01:00
#include "magiskhide.h"
2019-01-20 05:59:37 +01:00
using namespace std;
extern char *system_block, *vendor_block, *data_block;
2017-04-07 01:50:02 +02:00
static int inotify_fd = -1;
static void term_thread(int sig = SIGTERMTHRD);
static void new_zygote(int pid);
/************************
* All the maps and sets
************************/
set<pair<string, string>> hide_set; /* set of <pkg, process> pair */
static map<int, struct stat> zygote_map; /* zygote pid -> mnt ns */
static map<int, vector<string_view>> uid_proc_map; /* uid -> list of process */
pthread_mutex_t monitor_lock;
static set<int> attaches; /* A set of pid that should be attached */
static set<int> detaches; /* A set of tid that should be detached */
static set<int> unknown; /* A set of pid/tid that is unknown */
/********
* Utils
********/
2017-01-01 11:54:13 +01:00
static inline int read_ns(const int pid, struct stat *st) {
2017-07-02 19:02:11 +02:00
char path[32];
sprintf(path, "/proc/%d/ns/mnt", pid);
2018-07-11 17:41:38 +02:00
return stat(path, st);
2017-07-02 19:02:11 +02:00
}
2018-11-23 20:32:33 +01:00
static inline void lazy_unmount(const char* mountpoint) {
2018-01-11 17:23:38 +01:00
if (umount2(mountpoint, MNT_DETACH) != -1)
2017-07-10 17:39:33 +02:00
LOGD("hide_daemon: Unmounted (%s)\n", mountpoint);
}
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;
}
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<void *>(data));
}
2019-03-06 02:27:09 +01:00
static bool parse_packages_xml(string_view s) {
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];
const char *pkg = nullptr;
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_set) {
if (hide.first == value_view) {
pkg = hide.first.data();
break;
}
}
if (!pkg)
return true;
} else if (key_view == "userId" || key_view == "sharedUserId") {
int uid = parse_int(value);
for (auto &hide : hide_set) {
if (hide.first == pkg)
uid_proc_map[uid].emplace_back(hide.second);
}
}
}
return true;
}
static void check_zygote() {
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;
});
}
void *update_uid_map(void*) {
MutexGuard lock(monitor_lock);
uid_proc_map.clear();
file_readline("/data/system/packages.xml", parse_packages_xml, true);
return nullptr;
}
/*************************
* The actual hide daemon
**************************/
static void hide_daemon(int pid) {
RunFinally fin([=]() -> void {
// Send resume signal
kill(pid, SIGCONT);
_exit(0);
});
2017-07-10 17:39:33 +02:00
if (switch_mnt_ns(pid))
return;
2017-07-10 17:39:33 +02:00
LOGD("hide_daemon: handling PID=[%d]\n", pid);
manage_selinux();
clean_magisk_props();
vector<string> targets;
2017-07-10 17:39:33 +02:00
2018-06-16 23:16:52 +02:00
// Unmount dummy skeletons and /sbin links
2019-03-06 02:27:09 +01:00
file_readline("/proc/self/mounts", [&](string_view s) -> bool {
2019-01-20 05:59:37 +01:00
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));
2017-07-10 17:39:33 +02:00
}
return true;
});
2017-07-10 17:39:33 +02:00
for (auto &s : targets)
lazy_unmount(s.data());
targets.clear();
// Unmount everything under /system, /vendor, and data mounts
2019-03-06 02:27:09 +01:00
file_readline("/proc/self/mounts", [&](string_view s) -> bool {
2019-01-20 05:59:37 +01:00
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));
2017-07-10 17:39:33 +02:00
}
return true;
});
2017-07-10 17:39:33 +02:00
for (auto &s : targets)
lazy_unmount(s.data());
2017-07-10 17:39:33 +02:00
}
/************************
* Async signal handlers
************************/
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<struct inotify_event *>(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");
check_zygote();
update_uid_map();
2019-02-14 06:52:59 +01:00
}
}
// Workaround for the lack of pthread_cancel
static void term_thread(int) {
LOGD("proc_monitor: cleaning up\n");
// Clear maps
uid_proc_map.clear();
zygote_map.clear();
// Clear sets
hide_set.clear();
attaches.clear();
detaches.clear();
unknown.clear();
// Misc
hide_enabled = false;
pthread_mutex_destroy(&monitor_lock);
close(inotify_fd);
inotify_fd = -1;
LOGD("proc_monitor: terminate\n");
pthread_exit(nullptr);
}
/******************
* Ptrace Madness
******************/
/* 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) */
//#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);
}
2017-04-21 18:54:08 +02:00
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, SIGIO);
2019-01-20 05:59:37 +01:00
pthread_sigmask(SIG_UNBLOCK, &block_set, nullptr);
2019-01-20 05:59:37 +01:00
struct sigaction act{};
act.sa_handler = term_thread;
sigaction(SIGTERMTHRD, &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
check_zygote();
2017-05-07 21:11:14 +02:00
int status;
for (;;) {
int pid = waitpid(-1, &status, __WALL | __WNOTHREAD);
if (pid < 0)
2019-02-16 08:49:36 +01:00
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");
}
2017-06-02 22:31:01 +02:00
}
}