Add new API exemptFd

This commit is contained in:
topjohnwu 2022-09-09 03:27:19 -07:00
parent ccf21b0992
commit 44029875a6
3 changed files with 102 additions and 49 deletions

View File

@ -23,7 +23,7 @@
#include <jni.h>
#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 <class T>
@ -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);
}

View File

@ -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<FLAG_MAX> flags;
uint32_t info_flags;
bitset<MAX_FD_SIZE> allowed_fds;
vector<int> exempted_fds;
HookContext() : env(nullptr), args{nullptr}, process(nullptr), pid(-1), info_flags(0) {}
void sanitize_fds();
void run_modules_pre(const vector<int> &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_abi_v4 *>(api);
v4->exemptFd = [](int fd) { return g_ctx != nullptr && g_ctx->exempt_fd(fd); };
}
// fallthrough
case 3:
case 2: {
auto v2 = static_cast<api_abi_v2 *>(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<int> &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();

View File

@ -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; \
}