Make zygisk survive zygote restarts

Close #4777
This commit is contained in:
topjohnwu
2021-10-27 01:53:16 -07:00
parent 4c747c4148
commit ea75a09f95
14 changed files with 339 additions and 174 deletions

View File

@@ -1,114 +0,0 @@
#include <dlfcn.h>
#include <fcntl.h>
#include <utils.hpp>
#include <socket.hpp>
#include <daemon.hpp>
#include <magisk.hpp>
#include "zygisk.hpp"
using namespace std;
void zygiskd(int socket) {
if (getuid() != 0 || fcntl(socket, F_GETFD) < 0)
exit(-1);
android_logging();
#if defined(__LP64__)
set_nice_name("zygiskd64");
LOGI("* Launching zygiskd64\n");
#else
set_nice_name("zygiskd32");
LOGI("* Launching zygiskd32\n");
#endif
// Load modules
using comp_entry = void(*)(int);
vector<comp_entry> modules;
{
vector<int> module_fds = recv_fds(socket);
char buf[256];
for (int fd : module_fds) {
snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd);
comp_entry entry = nullptr;
if (void *h = dlopen(buf, RTLD_LAZY)) {
*(void **) &entry = dlsym(h, "zygisk_companion_entry");
}
modules.push_back(entry);
}
}
// ack
write_int(socket, 0);
// Start accepting requests
pollfd pfd = { socket, POLLIN, 0 };
for (;;) {
poll(&pfd, 1, -1);
if (!(pfd.revents & POLLIN)) {
// Something bad happened in magiskd, terminate zygiskd
exit(0);
}
int client = recv_fd(socket);
int module_id = read_int(client);
if (module_id < modules.size() && modules[module_id]) {
exec_task([=, entry = modules[module_id]] {
int dup = fcntl(client, F_DUPFD_CLOEXEC);
entry(client);
// Only close client if it is the same as dup so we don't
// accidentally close a re-used file descriptor.
// This check is required because the module companion
// handler could've closed the file descriptor already.
if (struct stat s1; fstat(client, &s1) == 0) {
struct stat s2{};
fstat(dup, &s2);
if (s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino) {
close(client);
}
}
close(dup);
});
} else {
close(client);
}
}
}
static int zygiskd_sockets[] = { -1, -1 };
#define zygiskd_socket zygiskd_sockets[is_64_bit]
void connect_companion(int client, bool is_64_bit) {
if (zygiskd_socket >= 0) {
// Make sure the socket is still valid
pollfd pfd = { zygiskd_socket, 0, 0 };
poll(&pfd, 1, 0);
if (pfd.revents) {
// Any revent means error
close(zygiskd_socket);
zygiskd_socket = -1;
}
}
if (zygiskd_socket < 0) {
int fds[2];
socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds);
zygiskd_socket = fds[0];
if (fork_dont_care() == 0) {
string exe = MAGISKTMP + "/magisk" + (is_64_bit ? "64" : "32");
// This fd has to survive exec
fcntl(fds[1], F_SETFD, 0);
char buf[16];
snprintf(buf, sizeof(buf), "%d", fds[1]);
execlp(exe.data(), "magisk", "--companion", buf, (char *) nullptr);
exit(-1);
}
close(fds[1]);
vector<int> module_fds = zygisk_module_fds(is_64_bit);
send_fds(zygiskd_socket, module_fds.data(), module_fds.size());
// Wait for ack
if (read_int(zygiskd_socket) != 0) {
return;
}
}
send_fd(zygiskd_socket, client);
}

View File

@@ -1,7 +1,5 @@
#include <libgen.h>
#include <dlfcn.h>
#include <sys/mount.h>
#include <sys/sendfile.h>
#include <sys/prctl.h>
#include <android/log.h>
@@ -145,37 +143,7 @@ static void zygisk_init() {
}
}
// Start code for magiskd IPC
int app_process_main(int argc, char *argv[]) {
android_logging();
if (int fd = connect_daemon(); fd >= 0) {
write_int(fd, ZYGISK_REQUEST);
write_int(fd, ZYGISK_SETUP);
if (read_int(fd) == 0) {
string path = read_string(fd);
string lib = path + ".1.so";
if (char *ld = getenv("LD_PRELOAD")) {
char env[256];
sprintf(env, "%s:%s", ld, lib.data());
setenv("LD_PRELOAD", env, 1);
} else {
setenv("LD_PRELOAD", lib.data(), 1);
}
setenv(INJECT_ENV_1, "1", 1);
}
close(fd);
}
// Execute real app_process
char buf[256];
xreadlink("/proc/self/exe", buf, sizeof(buf));
xumount2("/proc/self/exe", MNT_DETACH);
execve(buf, argv, environ);
return 1;
}
// The following code runs in zygote/app process
static int zygisk_log(int prio, const char *fmt, va_list ap) {
// If we don't have log pipe set, ask magiskd for it
@@ -248,6 +216,50 @@ static bool get_exe(int pid, char *buf, size_t sz) {
return xreadlink(buf, buf, sz) > 0;
}
static int zygiskd_sockets[] = { -1, -1 };
#define zygiskd_socket zygiskd_sockets[is_64_bit]
static void connect_companion(int client, bool is_64_bit) {
if (zygiskd_socket >= 0) {
// Make sure the socket is still valid
pollfd pfd = { zygiskd_socket, 0, 0 };
poll(&pfd, 1, 0);
if (pfd.revents) {
// Any revent means error
close(zygiskd_socket);
zygiskd_socket = -1;
}
}
if (zygiskd_socket < 0) {
int fds[2];
socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds);
zygiskd_socket = fds[0];
if (fork_dont_care() == 0) {
string exe = MAGISKTMP + "/magisk" + (is_64_bit ? "64" : "32");
// This fd has to survive exec
fcntl(fds[1], F_SETFD, 0);
char buf[16];
snprintf(buf, sizeof(buf), "%d", fds[1]);
execl(exe.data(), "zygisk", "companion", buf, (char *) nullptr);
exit(-1);
}
close(fds[1]);
vector<int> module_fds = zygisk_module_fds(is_64_bit);
send_fds(zygiskd_socket, module_fds.data(), module_fds.size());
// Wait for ack
if (read_int(zygiskd_socket) != 0) {
return;
}
}
send_fd(zygiskd_socket, client);
}
static timespec last_zygote_start;
static int zygote_start_counts[] = { 0, 0 };
#define zygote_start_count zygote_start_counts[is_64_bit]
#define zygote_started (zygote_start_counts[0] + zygote_start_counts[1])
#define zygote_start_reset(val) { zygote_start_counts[0] = val; zygote_start_counts[1] = val; }
static void setup_files(int client, const sock_cred *cred) {
LOGD("zygisk: setup files for pid=[%d]\n", cred->pid);
@@ -257,15 +269,54 @@ static void setup_files(int client, const sock_cred *cred) {
return;
}
bool is_64_bit = str_ends(buf, "64");
if (!zygote_started) {
// First zygote launch, record time
clock_gettime(CLOCK_MONOTONIC, &last_zygote_start);
}
if (zygote_start_count) {
// This zygote ABI had started before, kill existing zygiskd
close(zygiskd_sockets[0]);
close(zygiskd_sockets[1]);
zygiskd_sockets[0] = -1;
zygiskd_sockets[1] = -1;
}
++zygote_start_count;
if (zygote_start_count >= 5) {
// Bootloop prevention
timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
if (ts.tv_sec - last_zygote_start.tv_sec > 60) {
// This is very likely manual soft reboot
memcpy(&last_zygote_start, &ts, sizeof(ts));
zygote_start_reset(1);
} else {
// If any zygote relaunched more than 5 times within a minute,
// don't do any setups further to prevent bootloop.
zygote_start_reset(999);
write_int(client, 1);
return;
}
}
write_int(client, 0);
send_fd(client, is_64_bit ? app_process_64 : app_process_32);
string path = MAGISKTMP + "/" ZYGISKBIN "/zygisk." + basename(buf);
cp_afc(buf, (path + ".1.so").data());
cp_afc(buf, (path + ".2.so").data());
write_string(client, path);
}
static void magiskd_passthrough(int client) {
bool is_64_bit = read_int(client);
write_int(client, 0);
send_fd(client, is_64_bit ? app_process_64 : app_process_32);
}
int cached_manager_app_id = -1;
static time_t last_modified = 0;
@@ -344,6 +395,9 @@ void zygisk_handler(int client, const sock_cred *cred) {
case ZYGISK_SETUP:
setup_files(client, cred);
break;
case ZYGISK_PASSTHROUGH:
magiskd_passthrough(client);
break;
case ZYGISK_GET_INFO:
get_process_info(client, cred);
break;

View File

@@ -1,4 +1,5 @@
#include <dlfcn.h>
#include <sys/mount.h>
#include <xhook.h>
#include <bitset>
@@ -143,6 +144,16 @@ DCL_HOOK_FUNC(int, fork) {
return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork();
}
// Unmount app_process overlays in the process's private mount namespace
DCL_HOOK_FUNC(int, unshare, int flags) {
int res = old_unshare(flags);
if (g_ctx && res == 0) {
umount2("/system/bin/app_process64", MNT_DETACH);
umount2("/system/bin/app_process32", MNT_DETACH);
}
return res;
}
// This is the latest point where we can still connect to the magiskd main socket
DCL_HOOK_FUNC(int, selinux_android_setcontext,
uid_t uid, int isSystemServer, const char *seinfo, const char *pkgname) {
@@ -501,6 +512,7 @@ void hook_functions() {
default_new(jni_method_map);
XHOOK_REGISTER(ANDROID_RUNTIME, fork);
XHOOK_REGISTER(ANDROID_RUNTIME, unshare);
XHOOK_REGISTER(ANDROID_RUNTIME, selinux_android_setcontext);
XHOOK_REGISTER(ANDROID_RUNTIME, jniRegisterNativeMethods);
XHOOK_REGISTER_SYM(ANDROID_RUNTIME, "__android_log_close", android_log_close);

199
native/jni/zygisk/main.cpp Normal file
View File

@@ -0,0 +1,199 @@
#include <sys/mount.h>
#include <dlfcn.h>
#include <magisk.hpp>
#include <utils.hpp>
#include <socket.hpp>
#include <daemon.hpp>
#include "zygisk.hpp"
using namespace std;
// Entrypoint for app_process overlay
int app_process_main(int argc, char *argv[]) {
android_logging();
char buf[256];
bool zygote = false;
if (auto fp = open_file("/proc/self/attr/current", "r")) {
fscanf(fp.get(), "%s", buf);
zygote = (buf == "u:r:zygote:s0"sv);
}
if (!zygote) {
// For the non zygote case, we need to get real app_process via passthrough
// We have to connect magiskd via exec-ing magisk due to SELinux restrictions
// This is actually only relevant for calling app_process via ADB shell
// because zygisk shall already have the app_process overlays unmounted
// during app process specialization within its private mount namespace.
int fds[2];
socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds);
if (fork_dont_care() == 0) {
// This fd has to survive exec
fcntl(fds[1], F_SETFD, 0);
snprintf(buf, sizeof(buf), "%d", fds[1]);
#if defined(__LP64__)
execlp("magisk", "zygisk", "passthrough", buf, "1", (char *) nullptr);
#else
execlp("magisk", "zygisk", "passthrough", buf, "0", (char *) nullptr);
#endif
exit(-1);
}
close(fds[1]);
if (read_int(fds[0]) != 0)
return 1;
int app_proc_fd = recv_fd(fds[0]);
if (app_proc_fd < 0)
return 1;
close(fds[0]);
snprintf(buf, sizeof(buf), "/proc/self/fd/%d", app_proc_fd);
fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC);
execve(buf, argv, environ);
return 1;
}
if (int socket = connect_daemon(); socket >= 0) {
do {
write_int(socket, ZYGISK_REQUEST);
write_int(socket, ZYGISK_SETUP);
if (read_int(socket) != 0)
break;
int app_proc_fd = recv_fd(socket);
if (app_proc_fd < 0)
break;
string path = read_string(socket);
string lib = path + ".1.so";
if (char *ld = getenv("LD_PRELOAD")) {
char env[256];
sprintf(env, "%s:%s", ld, lib.data());
setenv("LD_PRELOAD", env, 1);
} else {
setenv("LD_PRELOAD", lib.data(), 1);
}
setenv(INJECT_ENV_1, "1", 1);
close(socket);
snprintf(buf, sizeof(buf), "/proc/self/fd/%d", app_proc_fd);
fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC);
execve(buf, argv, environ);
} while (false);
close(socket);
}
// If encountering any errors, unmount and execute the original app_process
xreadlink("/proc/self/exe", buf, sizeof(buf));
xumount2("/proc/self/exe", MNT_DETACH);
execve(buf, argv, environ);
return 1;
}
static void zygiskd(int socket) {
if (getuid() != 0 || fcntl(socket, F_GETFD) < 0)
exit(-1);
#if defined(__LP64__)
set_nice_name("zygiskd64");
LOGI("* Launching zygiskd64\n");
#else
set_nice_name("zygiskd32");
LOGI("* Launching zygiskd32\n");
#endif
// Load modules
using comp_entry = void(*)(int);
vector<comp_entry> modules;
{
vector<int> module_fds = recv_fds(socket);
char buf[256];
for (int fd : module_fds) {
snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd);
comp_entry entry = nullptr;
if (void *h = dlopen(buf, RTLD_LAZY)) {
*(void **) &entry = dlsym(h, "zygisk_companion_entry");
}
modules.push_back(entry);
}
}
// ack
write_int(socket, 0);
// Start accepting requests
pollfd pfd = { socket, POLLIN, 0 };
for (;;) {
poll(&pfd, 1, -1);
if (pfd.revents && !(pfd.revents & POLLIN)) {
// Something bad happened in magiskd, terminate zygiskd
exit(0);
}
int client = recv_fd(socket);
if (client < 0) {
// Something bad happened in magiskd, terminate zygiskd
exit(0);
}
int module_id = read_int(client);
if (module_id >= 0 && module_id < modules.size() && modules[module_id]) {
exec_task([=, entry = modules[module_id]] {
int dup = fcntl(client, F_DUPFD_CLOEXEC);
entry(client);
// Only close client if it is the same as dup so we don't
// accidentally close a re-used file descriptor.
// This check is required because the module companion
// handler could've closed the file descriptor already.
if (struct stat s1; fstat(client, &s1) == 0) {
struct stat s2{};
fstat(dup, &s2);
if (s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino) {
close(client);
}
}
close(dup);
});
} else {
close(client);
}
}
}
// Entrypoint where we need to re-exec ourselves
// This should only ever be called internally
int zygisk_main(int argc, char *argv[]) {
android_logging();
if (argc == 3 && argv[1] == "companion"sv) {
zygiskd(parse_int(argv[2]));
} else if (argc == 4 && argv[1] == "passthrough"sv) {
int client = parse_int(argv[2]);
int is_64_bit = parse_int(argv[3]);
if (fcntl(client, F_GETFD) < 0)
return 1;
if (int magiskd = connect_daemon(); magiskd >= 0) {
write_int(magiskd, ZYGISK_PASSTHROUGH);
write_int(magiskd, ZYGISK_PASSTHROUGH);
write_int(magiskd, is_64_bit);
if (read_int(magiskd) != 0) {
write_int(client, 1);
return 0;
}
write_int(client, 0);
int real_app_fd = recv_fd(magiskd);
send_fd(client, real_app_fd);
} else {
write_int(client, 1);
return 0;
}
}
return 0;
}

View File

@@ -47,4 +47,3 @@ void hook_functions();
bool unhook_functions();
std::vector<int> remote_get_info(int uid, const char *process, AppInfo *info);
int remote_request_unmount();
void connect_companion(int client, bool is_64_bit);