From 44029875a69d3a00248626915512a4d24abed5d6 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Fri, 9 Sep 2022 03:27:19 -0700 Subject: [PATCH] Add new API exemptFd --- native/src/zygisk/api.hpp | 45 ++++++++++------- native/src/zygisk/hook.cpp | 98 ++++++++++++++++++++++++------------ native/src/zygisk/module.hpp | 8 +++ 3 files changed, 102 insertions(+), 49 deletions(-) diff --git a/native/src/zygisk/api.hpp b/native/src/zygisk/api.hpp index 8a2cb178f..a41ca92ad 100644 --- a/native/src/zygisk/api.hpp +++ b/native/src/zygisk/api.hpp @@ -23,7 +23,7 @@ #include -#define ZYGISK_API_VERSION 3 +#define ZYGISK_API_VERSION 4 /* @@ -73,12 +73,11 @@ struct ServerSpecializeArgs; class ModuleBase { public: - // This function is called when the module is loaded into the target process. - // A Zygisk API handle will be sent as an argument; call utility functions or interface - // with Zygisk through this handle. + // This method is called as soon as the module is loaded into the target process. + // A Zygisk API handle will be passed as an argument. virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {} - // This function is called before the app process is specialized. + // This method is called before the app process is specialized. // At this point, the process just got forked from zygote, but no app specific specialization // is applied. This means that the process does not have any sandbox restrictions and // still runs with the same privilege of zygote. @@ -92,16 +91,16 @@ public: // See Api::connectCompanion() for more info. virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {} - // This function is called after the app process is specialized. + // This method is called after the app process is specialized. // At this point, the process has all sandbox restrictions enabled for this application. - // This means that this function runs as the same privilege of the app's own code. + // This means that this method runs as the same privilege of the app's own code. virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {} - // This function is called before the system server process is specialized. + // This method is called before the system server process is specialized. // See preAppSpecialize(args) for more info. virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {} - // This function is called after the system server process is specialized. + // This method is called after the system server process is specialized. // At this point, the process runs with the privilege of system_server. virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {} }; @@ -173,21 +172,21 @@ enum StateFlag : uint32_t { PROCESS_ON_DENYLIST = (1u << 1), }; -// All API functions will stop working after post[XXX]Specialize as Zygisk will be unloaded +// All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded // from the specialized process afterwards. struct Api { // Connect to a root companion process and get a Unix domain socket for IPC. // - // This API only works in the pre[XXX]Specialize functions due to SELinux restrictions. + // This API only works in the pre[XXX]Specialize methods due to SELinux restrictions. // - // The pre[XXX]Specialize functions run with the same privilege of zygote. + // The pre[XXX]Specialize methods run with the same privilege of zygote. // If you would like to do some operations with superuser permissions, register a handler // function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func). // Another good use case for a companion process is that if you want to share some resources // across multiple processes, hold the resources in the companion process and pass it over. // - // The root companion process is ABI aware; that is, when calling this function from a 32-bit + // The root companion process is ABI aware; that is, when calling this method from a 32-bit // process, you will be connected to a 32-bit companion process, and vice versa for 64-bit. // // Returns a file descriptor to a socket that is connected to the socket passed to your @@ -196,8 +195,8 @@ struct Api { // Get the file descriptor of the root folder of the current module. // - // This API only works in the pre[XXX]Specialize functions. - // Accessing the directory returned is only possible in the pre[XXX]Specialize functions + // This API only works in the pre[XXX]Specialize methods. + // Accessing the directory returned is only possible in the pre[XXX]Specialize methods // or in the root companion process (assuming that you sent the fd over the socket). // Both restrictions are due to SELinux and UID. // @@ -205,7 +204,7 @@ struct Api { int getModuleDir(); // Set various options for your module. - // Please note that this function accepts one single option at a time. + // Please note that this method accepts one single option at a time. // Check zygisk::Option for the full list of options available. void setOption(Option opt); @@ -213,9 +212,17 @@ struct Api { // Returns bitwise-or'd zygisk::StateFlag values. uint32_t getFlags(); + // Exempt the provided file descriptor from being automatically closed. + // + // This API only make sense in preAppSpecialize; calling this method in any other situation + // is either a no-op (returns true) or an error (returns false). + // + // When false is returned, the provided file descriptor will eventually be closed by zygote. + bool exemptFd(int fd); + // Hook JNI native methods for a class // - // Lookup all registered JNI native methods and replace it with your own functions. + // Lookup all registered JNI native methods and replace it with your own methods. // The original function pointer will be saved in each JNINativeMethod's fnPtr. // If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr // will be set to nullptr. @@ -295,6 +302,7 @@ struct api_table { void (*setOption)(void * /* impl */, Option); int (*getModuleDir)(void * /* impl */); uint32_t (*getFlags)(void * /* impl */); + bool (*exemptFd)(int); }; template @@ -321,6 +329,9 @@ inline void Api::setOption(Option opt) { inline uint32_t Api::getFlags() { return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0; } +inline bool Api::exemptFd(int fd) { + return tbl->exemptFd != nullptr && tbl->exemptFd(fd); +} inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) { if (tbl->hookJniNativeMethods) tbl->hookJniNativeMethods(env, className, methods, numMethods); } diff --git a/native/src/zygisk/hook.cpp b/native/src/zygisk/hook.cpp index 02ff0d261..7e3a5710d 100644 --- a/native/src/zygisk/hook.cpp +++ b/native/src/zygisk/hook.cpp @@ -28,12 +28,13 @@ static bool unhook_functions(); namespace { enum { + POST_SPECIALIZE, APP_FORK_AND_SPECIALIZE, APP_SPECIALIZE, SERVER_FORK_AND_SPECIALIZE, DO_REVERT_UNMOUNT, CAN_UNLOAD_ZYGISK, - SKIP_CLOSE_LOGD_FD, + SKIP_FD_SANITIZATION, FLAG_MAX }; @@ -59,10 +60,10 @@ struct HookContext { bitset flags; uint32_t info_flags; bitset allowed_fds; + vector exempted_fds; HookContext() : env(nullptr), args{nullptr}, process(nullptr), pid(-1), info_flags(0) {} - void sanitize_fds(); void run_modules_pre(const vector &fds); void run_modules_post(); DCL_PRE_POST(fork) @@ -72,6 +73,8 @@ struct HookContext { DCL_PRE_POST(nativeForkSystemServer) void unload_zygisk(); + void sanitize_fds(); + bool exempt_fd(int fd); }; #undef DCL_PRE_POST @@ -178,15 +181,16 @@ DCL_HOOK_FUNC(int, unshare, int flags) { // Close logd_fd if necessary to prevent crashing // For more info, check comments in zygisk_log_write DCL_HOOK_FUNC(void, android_log_close) { - if (g_ctx == nullptr || g_ctx->pid > 0) { - // This happens either in the zygote daemon or during nativeForkApp, nativeForkUsap etc. + if (g_ctx == nullptr) { + // Happens during un-managed fork like nativeForkApp, nativeForkUsap close(logd_fd.exchange(-1)); - // Do NOT revert back to android logging - } else if (!g_ctx->flags[SKIP_CLOSE_LOGD_FD]) { + } else if (!g_ctx->flags[SKIP_FD_SANITIZATION]) { close(logd_fd.exchange(-1)); - // Switch to plain old android logging because we cannot talk - // to magiskd to fetch our log pipe afterwards anyways. - android_logging(); + if (g_ctx->pid <= 0) { + // Switch to plain old android logging because we cannot talk + // to magiskd to fetch our log pipe afterwards anyways. + android_logging(); + } } old_android_log_close(); } @@ -318,6 +322,11 @@ bool ZygiskModule::RegisterModuleImpl(api_abi_base *api, long *module) { // Fill in API accordingly with module API version switch (api_version) { + case 4: { + auto v4 = static_cast(api); + v4->exemptFd = [](int fd) { return g_ctx != nullptr && g_ctx->exempt_fd(fd); }; + } + // fallthrough case 3: case 2: { auto v2 = static_cast(api); @@ -401,7 +410,7 @@ void HookContext::fork_pre() { // First block SIGCHLD, unblock after original fork is done sigmask(SIG_BLOCK, SIGCHLD); pid = old_fork(); - if (pid != 0) + if (pid != 0 || flags[SKIP_FD_SANITIZATION]) return; // Record all open fds @@ -419,11 +428,30 @@ void HookContext::fork_pre() { } void HookContext::sanitize_fds() { + if (flags[SKIP_FD_SANITIZATION]) + return; + if (flags[APP_FORK_AND_SPECIALIZE]) { - if (args.app->fds_to_ignore == nullptr) { - // The field fds_to_ignore don't exist before Android 8.0, which FDs are not checked - flags[SKIP_CLOSE_LOGD_FD] = true; - } else if (jintArray fdsToIgnore = *args.app->fds_to_ignore) { + auto update_fd_array = [&](int off) -> jintArray { + if (exempted_fds.empty()) + return nullptr; + + jintArray array = env->NewIntArray(off + exempted_fds.size()); + if (array == nullptr) + return nullptr; + + env->SetIntArrayRegion(array, off, exempted_fds.size(), exempted_fds.data()); + for (int fd : exempted_fds) { + if (fd >= 0 && fd < MAX_FD_SIZE) { + allowed_fds[fd] = true; + } + } + *args.app->fds_to_ignore = array; + flags[SKIP_FD_SANITIZATION] = true; + return array; + }; + + if (jintArray fdsToIgnore = *args.app->fds_to_ignore) { int *arr = env->GetIntArrayElements(fdsToIgnore, nullptr); int len = env->GetArrayLength(fdsToIgnore); for (int i = 0; i < len; ++i) { @@ -432,24 +460,12 @@ void HookContext::sanitize_fds() { allowed_fds[fd] = true; } } - - jintArray newFdList = nullptr; - if (logd_fd >= 0 && (newFdList = env->NewIntArray(len + 1))) { + if (jintArray newFdList = update_fd_array(len)) { env->SetIntArrayRegion(newFdList, 0, len, arr); - int fd = logd_fd; - env->SetIntArrayRegion(newFdList, len, 1, &fd); - *args.app->fds_to_ignore = newFdList; - flags[SKIP_CLOSE_LOGD_FD] = true; } env->ReleaseIntArrayElements(fdsToIgnore, arr, JNI_ABORT); } else { - jintArray newFdList = nullptr; - if (logd_fd >= 0 && (newFdList = env->NewIntArray(1))) { - int fd = logd_fd; - env->SetIntArrayRegion(newFdList, 0, 1, &fd); - *args.app->fds_to_ignore = newFdList; - flags[SKIP_CLOSE_LOGD_FD] = true; - } + update_fd_array(0); } } @@ -510,6 +526,7 @@ void HookContext::run_modules_pre(const vector &fds) { } void HookContext::run_modules_post() { + flags[POST_SPECIALIZE] = true; for (const auto &m : modules) { if (flags[APP_SPECIALIZE]) { m.postAppSpecialize(args.app); @@ -564,14 +581,23 @@ void HookContext::unload_zygisk() { } } +bool HookContext::exempt_fd(int fd) { + if (flags[POST_SPECIALIZE] || flags[SKIP_FD_SANITIZATION]) + return true; + if (!flags[APP_FORK_AND_SPECIALIZE]) + return false; + exempted_fds.push_back(fd); + return true; +} + // ----------------------------------------------------------------- void HookContext::nativeSpecializeAppProcess_pre() { - g_ctx = this; - // App specialize does not check FD - flags[SKIP_CLOSE_LOGD_FD] = true; process = env->GetStringUTFChars(args.app->nice_name, nullptr); ZLOGV("pre specialize [%s]\n", process); + g_ctx = this; + // App specialize does not check FD + flags[SKIP_FD_SANITIZATION] = true; app_specialize_pre(); } @@ -623,8 +649,16 @@ void HookContext::nativeForkSystemServer_post() { void HookContext::nativeForkAndSpecialize_pre() { process = env->GetStringUTFChars(args.app->nice_name, nullptr); - flags[APP_FORK_AND_SPECIALIZE] = true; ZLOGV("pre forkAndSpecialize [%s]\n", process); + + flags[APP_FORK_AND_SPECIALIZE] = true; + if (args.app->fds_to_ignore == nullptr) { + // The field fds_to_ignore don't exist before Android 8.0, which FDs are not checked + flags[SKIP_FD_SANITIZATION] = true; + } else if (logd_fd >= 0) { + exempted_fds.push_back(logd_fd); + } + fork_pre(); if (pid == 0) { app_specialize_pre(); diff --git a/native/src/zygisk/module.hpp b/native/src/zygisk/module.hpp index 8592fbfa7..a5b0bd180 100644 --- a/native/src/zygisk/module.hpp +++ b/native/src/zygisk/module.hpp @@ -10,14 +10,17 @@ struct ZygiskModule; struct AppSpecializeArgs_v1; using AppSpecializeArgs_v2 = AppSpecializeArgs_v1; struct AppSpecializeArgs_v3; +using AppSpecializeArgs_v4 = AppSpecializeArgs_v3; struct module_abi_v1; using module_abi_v2 = module_abi_v1; using module_abi_v3 = module_abi_v1; +using module_abi_v4 = module_abi_v1; struct api_abi_v1; struct api_abi_v2; using api_abi_v3 = api_abi_v2; +struct api_abi_v4; struct AppSpecializeArgs_v3 { jint &uid; @@ -132,6 +135,10 @@ struct api_abi_v2 : public api_abi_v1 { uint32_t (*getFlags)(ZygiskModule *); }; +struct api_abi_v4 : public api_abi_v2 { + bool (*exemptFd)(int); +}; + #define call_app(method) \ switch (*mod.api_version) { \ case 1: \ @@ -141,6 +148,7 @@ case 2: { \ break; \ } \ case 3: \ +case 4: \ mod.v1->method(mod.v1->impl, args);\ break; \ }