diff --git a/native/jni/core/core.hpp b/native/jni/core/core.hpp index 4c1fe0dd1..45833fc23 100644 --- a/native/jni/core/core.hpp +++ b/native/jni/core/core.hpp @@ -10,6 +10,7 @@ void unlock_blocks(); void reboot(); void start_log_daemon(); void setup_logfile(bool reset); +void magisk_logging(); // Module stuffs void handle_modules(); diff --git a/native/jni/core/logging.cpp b/native/jni/core/logging.cpp index e66b8e4d0..9b9e6ba5d 100644 --- a/native/jni/core/logging.cpp +++ b/native/jni/core/logging.cpp @@ -17,7 +17,7 @@ struct log_meta { int tid; }; -static atomic logd_fd = -1; +atomic logd_fd = -1; void setup_logfile(bool reset) { if (logd_fd < 0) @@ -121,7 +121,7 @@ static void logfile_writer(int pipefd) { } } -static int magisk_log(int prio, const char *fmt, va_list ap) { +int magisk_log(int prio, const char *fmt, va_list ap) { char buf[MAX_MSG_LEN + 1]; int len = vsnprintf(buf, sizeof(buf), fmt, ap); @@ -172,6 +172,15 @@ void magisk_logging() { log_cb.ex = nop_ex; } +#define alog(prio) [](auto fmt, auto ap){ return __android_log_vprint(ANDROID_LOG_##prio, "Magisk", fmt, ap); } +void android_logging() { + log_cb.d = alog(DEBUG); + log_cb.i = alog(INFO); + log_cb.w = alog(WARN); + log_cb.e = alog(ERROR); + log_cb.ex = nop_ex; +} + void start_log_daemon() { int fds[2]; if (pipe2(fds, O_CLOEXEC) == 0) { diff --git a/native/jni/include/daemon.hpp b/native/jni/include/daemon.hpp index b10121b1e..a5ff833b8 100644 --- a/native/jni/include/daemon.hpp +++ b/native/jni/include/daemon.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -48,7 +49,9 @@ enum : int { int connect_daemon(bool create = false); -void magisk_logging(); +extern std::atomic logd_fd; +int magisk_log(int prio, const char *fmt, va_list ap); +void android_logging(); // Daemon handlers void post_fs_data(int client); diff --git a/native/jni/utils/missing.hpp b/native/jni/utils/missing.hpp index 83f82cddb..afa6f2c5c 100644 --- a/native/jni/utils/missing.hpp +++ b/native/jni/utils/missing.hpp @@ -22,6 +22,7 @@ #define linkat compat_linkat #define inotify_init1 compat_inotify_init1 #define faccessat compat_faccessat +#define sigtimedwait compat_sigtimedwait ssize_t compat_getline(char **lineptr, size_t *n, FILE *stream); ssize_t compat_getdelim(char **lineptr, size_t *n, int delim, FILE *stream); @@ -66,3 +67,12 @@ static inline int compat_inotify_init1(int flags) { static inline int compat_faccessat(int dirfd, const char *pathname, int mode, int flags) { return syscall(__NR_faccessat, dirfd, pathname, mode, flags); } + +static inline int compat_sigtimedwait(const sigset_t* set, siginfo_t* info, const timespec* timeout) { + union { + sigset_t set; + sigset_t set64; + } s{}; + s.set = *set; + return syscall(__NR_rt_sigtimedwait, &s.set64, info, timeout, sizeof(sigset64_t)); +} diff --git a/native/jni/zygisk/entry.cpp b/native/jni/zygisk/entry.cpp index c611dbb00..3b606a4bf 100644 --- a/native/jni/zygisk/entry.cpp +++ b/native/jni/zygisk/entry.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -16,6 +17,17 @@ using namespace std; static void *self_handle = nullptr; static atomic active_threads = -1; +static int zygisk_log(int prio, const char *fmt, va_list ap); + +#define zlog(prio) [](auto fmt, auto ap){ return zygisk_log(ANDROID_LOG_##prio, fmt, ap); } +static void zygisk_logging() { + log_cb.d = zlog(DEBUG); + log_cb.i = zlog(INFO); + log_cb.w = zlog(WARN); + log_cb.e = zlog(ERROR); + log_cb.ex = nop_ex; +} + void self_unload() { LOGD("zygisk: Request to self unload\n"); // If deny failed, do not unload or else it will cause SIGSEGV @@ -81,7 +93,7 @@ static void inject_cleanup_wait() { __attribute__((constructor)) static void inject_init() { if (char *env = getenv(INJECT_ENV_2)) { - magisk_logging(); + zygisk_logging(); LOGD("zygisk: inject 2nd stage\n"); active_threads = 1; unsetenv(INJECT_ENV_2); @@ -100,7 +112,7 @@ static void inject_init() { active_threads++; new_daemon_thread(&unload_first_stage, env); } else if (getenv(INJECT_ENV_1)) { - magisk_logging(); + android_logging(); LOGD("zygisk: inject 1st stage\n"); char *ld = getenv("LD_PRELOAD"); @@ -128,7 +140,7 @@ static void inject_init() { // Start code for magiskd IPC int app_process_main(int argc, char *argv[]) { - magisk_logging(); + android_logging(); if (int fd = connect_daemon(); fd >= 0) { write_int(fd, ZYGISK_REQUEST); @@ -157,6 +169,42 @@ int app_process_main(int argc, char *argv[]) { return 1; } +static int zygisk_log(int prio, const char *fmt, va_list ap) { + // If we don't have log pipe set, ask magiskd for it + // This could happen multiple times in zygote because it was closed to prevent crashing + if (logd_fd < 0) { + // Change logging temporarily to prevent infinite recursion and stack overflow + android_logging(); + if (int fd = connect_daemon(); fd >= 0) { + write_int(fd, ZYGISK_REQUEST); + write_int(fd, ZYGISK_GET_LOG_PIPE); + if (read_int(fd) == 0) { + logd_fd = recv_fd(fd); + } + close(fd); + } + zygisk_logging(); + } + + sigset_t mask; + sigset_t orig_mask; + bool sig = false; + // Make sure SIGPIPE won't crash zygote + if (logd_fd >= 0) { + sig = true; + sigemptyset(&mask); + sigaddset(&mask, SIGPIPE); + pthread_sigmask(SIG_BLOCK, &mask, &orig_mask); + } + int ret = magisk_log(prio, fmt, ap); + if (sig) { + timespec ts{}; + sigtimedwait(&mask, nullptr, &ts); + pthread_sigmask(SIG_SETMASK, &orig_mask, nullptr); + } + return ret; +} + bool remote_check_denylist(int uid, const char *process) { if (int fd = connect_daemon(); fd >= 0) { write_int(fd, ZYGISK_REQUEST); @@ -226,6 +274,16 @@ static void do_unmount(int client, ucred *cred) { } } +static void send_log_pipe(int fd) { + // There is race condition here, but we can't really do much about it... + if (logd_fd) { + write_int(fd, 0); + send_fd(fd, logd_fd); + } else { + write_int(fd, 1); + } +} + void zygisk_handler(int client, ucred *cred) { int code = read_int(client); switch (code) { @@ -238,6 +296,9 @@ void zygisk_handler(int client, ucred *cred) { case ZYGISK_UNMOUNT: do_unmount(client, cred); break; + case ZYGISK_GET_LOG_PIPE: + send_log_pipe(client); + break; } close(client); } diff --git a/native/jni/zygisk/hook.cpp b/native/jni/zygisk/hook.cpp index bc4e46c4b..67af77292 100644 --- a/native/jni/zygisk/hook.cpp +++ b/native/jni/zygisk/hook.cpp @@ -24,10 +24,12 @@ namespace { enum { DENY_FLAG, FORK_AND_SPECIALIZE, + APP_SPECIALIZE, + SERVER_SPECIALIZE, FLAG_MAX }; -#define DCL_JNI_FUNC(name) \ +#define DCL_PRE_POST(name) \ void name##_pre(); \ void name##_post(); @@ -44,15 +46,17 @@ struct HookContext { HookContext() : pid(-1) {} - void pre_fork(); - static void post_fork(); + static void close_fds(); - DCL_JNI_FUNC(nativeForkAndSpecialize) - DCL_JNI_FUNC(nativeSpecializeAppProcess) - DCL_JNI_FUNC(nativeForkSystemServer) + DCL_PRE_POST(fork) + DCL_PRE_POST(run_modules) + + DCL_PRE_POST(nativeForkAndSpecialize) + DCL_PRE_POST(nativeSpecializeAppProcess) + DCL_PRE_POST(nativeForkSystemServer) }; -#undef DCL_JNI_FUNC +#undef DCL_PRE_POST struct StringCmp { using is_transparent = void; @@ -127,15 +131,15 @@ DCL_HOOK_FUNC(int, jniRegisterNativeMethods, return old_jniRegisterNativeMethods(env, className, newMethods.get() ?: methods, numMethods); } +// Skip actual fork and return cached result if applicable DCL_HOOK_FUNC(int, fork) { return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork(); } +// 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) { if (g_ctx && g_ctx->flags[DENY_FLAG]) { - // Request magiskd to cleanup our mount namespace before switching secontext - // This is the latest point where we can still connect to the magiskd main socket if (remote_request_unmount() == 0) { LOGD("zygisk: mount namespace cleaned up\n"); } @@ -143,6 +147,16 @@ DCL_HOOK_FUNC(int, selinux_android_setcontext, return old_selinux_android_setcontext(uid, isSystemServer, seinfo, pkgname); } +// A place to clean things up before calling into zygote::ForkCommon/SpecializeCommon +DCL_HOOK_FUNC(void, android_log_close) { + HookContext::close_fds(); + if (g_ctx && g_ctx->pid <= 0) { + // In child process, no longer be able to access to magiskd + android_logging(); + } + old_android_log_close(); +} + // ----------------------------------------------------------------- // The original android::AppRuntime virtual table @@ -208,8 +222,18 @@ DCL_HOOK_FUNC(void, setArgv0, void *self, const char *argv0, bool setProcName) { // ----------------------------------------------------------------- +void HookContext::run_modules_pre() { /* TODO */ } +void HookContext::run_modules_post() { /* TODO */ } + +void HookContext::close_fds() { + close(logd_fd.exchange(-1)); +} + +// ----------------------------------------------------------------- + void HookContext::nativeSpecializeAppProcess_pre() { g_ctx = this; + flags[APP_SPECIALIZE] = true; process = env->GetStringUTFChars(args->nice_name, nullptr); if (flags[FORK_AND_SPECIALIZE]) { VLOG("zygisk: pre forkAndSpecialize [%s]\n", process); @@ -221,6 +245,8 @@ void HookContext::nativeSpecializeAppProcess_pre() { if (args->mount_external != 0 && remote_check_denylist(args->uid, process)) { flags[DENY_FLAG] = true; LOGI("zygisk: [%s] is on the denylist\n", process); + } else { + run_modules_pre(); } } @@ -232,24 +258,49 @@ void HookContext::nativeSpecializeAppProcess_post() { } env->ReleaseStringUTFChars(args->nice_name, process); - if (flags[DENY_FLAG]) + if (flags[DENY_FLAG]) { self_unload(); + } else { + run_modules_post(); + } g_ctx = nullptr; } void HookContext::nativeForkSystemServer_pre() { - pre_fork(); - if (pid) return; - VLOG("zygisk: pre forkSystemServer\n"); + fork_pre(); + flags[SERVER_SPECIALIZE] = true; + if (pid == 0) { + VLOG("zygisk: pre forkSystemServer\n"); + run_modules_pre(); + close_fds(); + } } void HookContext::nativeForkSystemServer_post() { - run_finally f([]{ post_fork(); }); - if (pid) return; - VLOG("zygisk: post forkSystemServer\n"); + if (pid == 0) { + android_logging(); + VLOG("zygisk: post forkSystemServer\n"); + run_modules_post(); + } + fork_post(); } -// ----------------------------------------------------------------- +void HookContext::nativeForkAndSpecialize_pre() { + fork_pre(); + flags[FORK_AND_SPECIALIZE] = true; + if (pid == 0) { + nativeSpecializeAppProcess_pre(); + close_fds(); + } +} + +void HookContext::nativeForkAndSpecialize_post() { + if (pid == 0) { + android_logging(); + nativeSpecializeAppProcess_post(); + } + fork_post(); +} int sigmask(int how, int signum) { sigset_t set; @@ -260,31 +311,18 @@ int sigmask(int how, int signum) { // Do our own fork before loading any 3rd party code // First block SIGCHLD, unblock after original fork is done -void HookContext::pre_fork() { +void HookContext::fork_pre() { g_ctx = this; sigmask(SIG_BLOCK, SIGCHLD); pid = old_fork(); } // Unblock SIGCHLD in case the original method didn't -void HookContext::post_fork() { +void HookContext::fork_post() { sigmask(SIG_UNBLOCK, SIGCHLD); g_ctx = nullptr; } -void HookContext::nativeForkAndSpecialize_pre() { - pre_fork(); - flags[FORK_AND_SPECIALIZE] = true; - if (pid == 0) - nativeSpecializeAppProcess_pre(); -} - -void HookContext::nativeForkAndSpecialize_post() { - if (pid == 0) - nativeSpecializeAppProcess_post(); - post_fork(); -} - } // namespace static bool hook_refresh() { @@ -315,7 +353,7 @@ static int hook_register(const char *path, const char *symbol, void *new_func, v XHOOK_REGISTER_SYM(PATH_REGEX, #NAME, NAME) #define ANDROID_RUNTIME ".*/libandroid_runtime.so$" -#define APP_PROCESS "^/system/bin/app_process.*" +#define APP_PROCESS "^/system/bin/app_process.*" void hook_functions() { #ifdef MAGISK_DEBUG @@ -329,6 +367,7 @@ void hook_functions() { XHOOK_REGISTER(ANDROID_RUNTIME, fork); XHOOK_REGISTER(ANDROID_RUNTIME, selinux_android_setcontext); XHOOK_REGISTER(ANDROID_RUNTIME, jniRegisterNativeMethods); + XHOOK_REGISTER_SYM(ANDROID_RUNTIME, "__android_log_close", android_log_close); hook_refresh(); // Remove unhooked methods diff --git a/native/jni/zygisk/inject.hpp b/native/jni/zygisk/inject.hpp index dd5e59f49..e33f9cc59 100644 --- a/native/jni/zygisk/inject.hpp +++ b/native/jni/zygisk/inject.hpp @@ -10,6 +10,7 @@ enum : int { ZYGISK_SETUP, ZYGISK_CHECK_DENYLIST, ZYGISK_UNMOUNT, + ZYGISK_GET_LOG_PIPE, }; // Unmap all pages matching the name