Magisk/native/jni/magiskhide/proc_monitor.cpp

293 lines
7.3 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/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 <map>
#include <algorithm>
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 set<int> hide_uid;
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();
hide_uid.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);
close(inotify_fd);
inotify_fd = -1;
2018-11-01 18:23:12 +01:00
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);
}
/* APK monitoring doesn't seem to require checking namespace
* separation from PPID. Preserve this function just in case */
#if 0
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;
}
#endif
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) {
2018-11-23 20:32:33 +01:00
char buffer[4096];
2017-07-10 17:39:33 +02:00
if (switch_mnt_ns(pid))
goto exit;
2017-07-10 17:39:33 +02:00
LOGD("hide_daemon: handling PID=[%d]\n", pid);
manage_selinux();
clean_magisk_props();
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
2018-06-16 23:16:52 +02:00
// Unmount dummy skeletons and /sbin links
file_readline("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")) {
sscanf(s.data(), "%*s %4096s", buffer);
2017-07-10 17:39:33 +02:00
lazy_unmount(buffer);
}
return true;
});
2017-07-10 17:39:33 +02:00
// Unmount everything under /system, /vendor, and data mounts
file_readline("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))) {
sscanf(s.data(), "%*s %4096s", buffer);
2017-07-10 17:39:33 +02:00
lazy_unmount(buffer);
}
return true;
});
2017-07-10 17:39:33 +02:00
exit:
// Send resume signal
kill(pid, SIGCONT);
_exit(0);
2017-07-10 17:39:33 +02:00
}
// A mapping from pid to namespace inode to avoid time-consuming GC
static map<int, uint64_t> pid_ns_map;
static bool process_pid(int pid) {
// We're only interested in PIDs > 1000
if (pid <= 1000)
return true;
struct stat ns;
int uid = get_uid(pid);
if (hide_uid.count(uid)) {
// Make sure we can read mount namespace
if (read_ns(pid, &ns))
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;
if (uid == gms_uid) {
// Check /proc/uid/cmdline to see if it's SAFETYNET_PROCESS
if (!is_pid_safetynet_process(pid))
return true;
LOGD("proc_monitor: " SAFETYNET_PROCESS "\n");
}
// Send pause signal ASAP
if (kill(pid, SIGSTOP) == -1)
return true;
pid_ns_map[pid] = ns.st_ino;
LOGI("proc_monitor: UID=[%d] PID=[%d] ns=[%llu]\n", 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 true;
}
2019-02-14 06:52:59 +01:00
static int xinotify_add_watch(int fd, const char* path, uint32_t mask) {
int ret = inotify_add_watch(fd, path, mask);
if (ret >= 0) {
2019-02-16 08:30:48 +01:00
LOGD("proc_monitor: Monitoring %s\n", path);
2019-02-14 06:52:59 +01:00
} else {
PLOGE("proc_monitor: Monitor %s", path);
}
return ret;
}
static int new_inotify;
static const string_view APK_EXT(".apk");
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 '>' */
char key[32], value[1024];
char *tok;
start += 9; /* Skip '<package ' */
while ((tok = strtok_r(nullptr, " ", &start))) {
sscanf(tok, "%[^=]=\"%[^\"]", key, value);
string_view value_view(value);
if (strcmp(key, "name") == 0) {
if (std::count(hide_list.begin(), hide_list.end(), value_view) == 0)
return true;
} else if (strcmp(key, "codePath") == 0) {
if (ends_with(value_view, APK_EXT)) {
// Directly add to inotify list
xinotify_add_watch(new_inotify, 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)) {
strcpy(value + value_view.length(), "/");
strcpy(value + value_view.length() + 1, entry->d_name);
xinotify_add_watch(new_inotify, value, IN_OPEN);
break;
}
}
closedir(dir);
}
} else if (strcmp(key, "userId") == 0 || strcmp(key, "sharedUserId") == 0) {
hide_uid.insert(parse_int(value));
}
}
return true;
}
void update_inotify_mask() {
new_inotify = inotify_init();
if (new_inotify < 0) {
LOGE("proc_monitor: Cannot initialize inotify: %s\n", strerror(errno));
term_thread(TERM_THREAD);
}
fcntl(new_inotify, F_SETFD, FD_CLOEXEC);
2019-02-16 08:30:48 +01:00
LOGD("proc_monitor: Updating inotify list\n");
{
MutexGuard lock(list_lock);
hide_uid.clear();
file_readline("/data/system/packages.xml", parse_packages_xml, true);
}
// Add /data/system to monitor /data/system/packages.xml
xinotify_add_watch(new_inotify, "/data/system", IN_CLOSE_WRITE);
2019-02-14 10:24:30 +01:00
int tmp = inotify_fd;
inotify_fd = new_inotify;
if (tmp >= 0)
close(tmp);
}
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
// Read inotify events
ssize_t len;
char buf[512];
2019-02-16 08:49:36 +01:00
auto event = reinterpret_cast<inotify_event *>(buf);
while ((len = read(inotify_fd, buf, sizeof(buf))) >= 0) {
2019-02-16 08:49:36 +01:00
if (len < sizeof(*event))
continue;
if (event->mask & IN_OPEN) {
// Since we're just watching files,
// extracting file name is not possible from querying event
MutexGuard lock(list_lock);
crawl_procfs(process_pid);
} 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();
}
2017-06-02 22:31:01 +02:00
}
PLOGE("proc_monitor: read inotify");
}