Magisk/native/jni/magiskhide/proc_monitor.cpp

410 lines
9.8 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>
#include <ctype.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/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>
2019-01-20 05:59:37 +01:00
#include <vector>
#include <string>
#include <unordered_map>
#include <set>
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
#define EVENT_SIZE sizeof(struct inotify_event)
#define EVENT_BUF_LEN (1024 * (EVENT_SIZE + 16))
2017-04-06 00:12:29 +02:00
// Workaround for the lack of pthread_cancel
2018-11-01 18:23:12 +01:00
static void term_thread(int) {
LOGD("proc_monitor: running cleanup\n");
2019-01-20 05:59:37 +01:00
hide_list.clear();
2018-11-16 06:37:41 +01:00
hide_enabled = false;
2018-11-01 18:23:12 +01:00
pthread_mutex_destroy(&list_lock);
LOGD("proc_monitor: terminating\n");
2018-11-07 08:10:38 +01:00
pthread_exit(nullptr);
2017-04-06 00:12:29 +02:00
}
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 inline int parse_ppid(const int pid) {
2018-11-23 20:32:33 +01:00
char path[32];
int ppid;
sprintf(path, "/proc/%d/stat", pid);
2018-11-24 21:53:15 +01:00
FILE *stat = fopen(path, "re");
2018-11-23 20:32:33 +01:00
if (stat == nullptr)
return -1;
/* PID COMM STATE PPID ..... */
2018-11-23 20:32:33 +01:00
fscanf(stat, "%*d %*s %*c %d", &ppid);
2018-11-24 03:15:44 +01:00
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 bool is_pid_safetynet_process(const int pid) {
char path[32];
char buf[64];
int fd;
ssize_t len;
sprintf(path, "/proc/%d/cmdline", pid);
fd = open(path, O_RDONLY);
if (fd == -1)
return false;
len = read(fd, buf, sizeof(buf));
close(fd);
if (len == -1)
return false;
return !strcmp(buf, SAFETYNET_PROCESS);
}
static void hide_daemon(int pid) {
LOGD("hide_daemon: handling pid=[%d]\n", pid);
2017-07-10 17:39:33 +02:00
2018-11-23 20:32:33 +01:00
char buffer[4096];
2019-01-20 05:59:37 +01:00
vector<string> mounts;
2017-07-10 17:39:33 +02:00
manage_selinux();
2017-07-18 06:26:23 +02:00
clean_magisk_props();
2017-07-10 17:39:33 +02:00
if (switch_mnt_ns(pid))
goto exit;
2017-07-10 17:39:33 +02:00
2018-11-13 08:07:02 +01:00
snprintf(buffer, sizeof(buffer), "/proc/%d", pid);
chdir(buffer);
2017-07-10 17:39:33 +02:00
2019-01-20 05:59:37 +01:00
mounts = file_to_vector("mounts");
2018-06-16 23:16:52 +02:00
// Unmount dummy skeletons and /sbin links
2018-11-01 18:23:12 +01:00
for (auto &s : mounts) {
2019-01-20 05:59:37 +01:00
if (str_contains(s, "tmpfs /system/") || str_contains(s, "tmpfs /vendor/") ||
str_contains(s, "tmpfs /sbin")) {
sscanf(s.c_str(), "%*s %4096s", buffer);
2017-07-10 17:39:33 +02:00
lazy_unmount(buffer);
}
}
// Re-read mount infos
2019-01-20 05:59:37 +01:00
mounts = file_to_vector("mounts");
2017-07-10 17:39:33 +02:00
// Unmount everything under /system, /vendor, and data mounts
2018-11-01 18:23:12 +01:00
for (auto &s : mounts) {
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))) {
2019-01-20 05:59:37 +01:00
sscanf(s.c_str(), "%*s %4096s", buffer);
2017-07-10 17:39:33 +02:00
lazy_unmount(buffer);
}
}
exit:
// Send resume signal
kill(pid, SIGCONT);
_exit(0);
2017-07-10 17:39:33 +02:00
}
/*
* 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<int, uint64_t> pid_ns_map;
// Use set for slow insertion but fast searching(which we'd encounter a lot more)
static set<uid_t> hide_uid;
// Treat GMS separately as we're only interested in one component
static int gms_uid = -1;
static void detect_new_processes() {
struct dirent *dp;
struct stat ns, pns;
int pid, ppid;
bool hide;
uid_t uid;
unordered_map<int, uint64_t>::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<int, uint64_t>(pid, ns.st_ino));
} else if (pos->second != ns.st_ino) {
hide = true;
pid_ns_map[pos->first] = ns.st_ino;
}
if (hide) {
if (uid == gms_uid) {
// Check /proc/uid/cmdline to see if it's SAFETYNET_PROCESS
if (!is_pid_safetynet_process(pid))
continue;
LOGI("proc_monitor: found %s\n", SAFETYNET_PROCESS);
}
// 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 path[4096];
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) {
// Compare with (path + 10) to trim "/data/app/"
if (strncmp(path + 10, s.c_str(), s.length()) == 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 path[4096];
const char* target;
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) {
target = s.c_str();
if (strcmp(entry->d_name, target) == 0) {
if (stat(path, &st) == -1)
continue;
LOGI("proc_monitor: %s UID is %d\n", target, st.st_uid);
hide_uid.insert(st.st_uid);
if (strcmp(entry->d_name, SAFETYNET_PKG) == 0) {
LOGI("proc_monitor: Got GMS: %d\n", st.st_uid);
gms_uid = 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();
}
2017-04-21 18:54:08 +02:00
void proc_monitor() {
// Unblock user signals
sigset_t block_set;
sigemptyset(&block_set);
sigaddset(&block_set, TERM_THREAD);
2019-01-20 05:59:37 +01:00
pthread_sigmask(SIG_UNBLOCK, &block_set, nullptr);
2017-04-06 00:12:29 +02:00
// Register the cancel signal
2019-01-20 05:59:37 +01:00
struct sigaction act{};
act.sa_handler = term_thread;
2019-01-20 05:59:37 +01:00
sigaction(TERM_THREAD, &act, nullptr);
2017-05-07 21:11:14 +02:00
if (access("/proc/1/ns/mnt", F_OK) != 0) {
2017-07-10 17:39:33 +02:00
LOGE("proc_monitor: Your kernel doesn't support mount namespace :(\n");
term_thread(TERM_THREAD);
2017-07-10 17:39:33 +02:00
}
update_apk_list();
2018-10-12 06:50:47 +02:00
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);
2018-10-12 06:50:47 +02:00
continue;
}
2018-10-12 06:50:47 +02:00
for (p = buffer; p < buffer + len; ) {
event = (struct inotify_event *)p;
2018-10-12 06:50:47 +02:00
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();
2018-10-12 06:50:47 +02:00
break;
}
2018-10-12 06:50:47 +02:00
p += EVENT_SIZE + event->len;
}
2017-06-02 22:31:01 +02:00
}
}