diff --git a/native/jni/core/db.cpp b/native/jni/core/db.cpp index 7ab60a1db..75b3f60fa 100644 --- a/native/jni/core/db.cpp +++ b/native/jni/core/db.cpp @@ -11,10 +11,13 @@ using namespace std; -typedef struct sqlite3 sqlite3; +struct sqlite3; static sqlite3 *mDB = nullptr; +#define DBLOGV(...) +//#define DBLOGV(...) LOGD("magiskdb: " __VA_ARGS__) + // SQLite APIs #define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */ @@ -294,7 +297,7 @@ int get_db_settings(db_settings &cfg, int key) { char *err; auto settings_cb = [&](db_row &row) -> bool { cfg[row["key"]] = parse_int(row["value"]); - LOGD("magiskdb: query %s=[%s]\n", row["key"].data(), row["value"].data()); + DBLOGV("query %s=[%s]\n", row["key"].data(), row["value"].data()); return true; }; if (key >= 0) { @@ -312,7 +315,7 @@ int get_db_strings(db_strings &str, int key) { char *err; auto string_cb = [&](db_row &row) -> bool { str[row["key"]] = row["value"]; - LOGD("magiskdb: query %s=[%s]\n", row["key"].data(), row["value"].data()); + DBLOGV("query %s=[%s]\n", row["key"].data(), row["value"].data()); return true; }; if (key >= 0) { @@ -326,21 +329,6 @@ int get_db_strings(db_strings &str, int key) { return 0; } -int get_uid_policy(su_access &su, int uid) { - char query[256], *err; - sprintf(query, "SELECT policy, logging, notification FROM policies " - "WHERE uid=%d AND (until=0 OR until>%li)", uid, time(nullptr)); - err = db_exec(query, [&](db_row &row) -> bool { - su.policy = (policy_t) parse_int(row["policy"]); - su.log = parse_int(row["logging"]); - su.notify = parse_int(row["notification"]); - LOGD("magiskdb: query policy=[%d] log=[%d] notify=[%d]\n", su.policy, su.log, su.notify); - return true; - }); - db_err_cmd(err, return 1); - return 0; -} - bool get_manager(int user_id, std::string *pkg, struct stat *st) { db_strings str; get_db_strings(str, SU_MANAGER); diff --git a/native/jni/include/db.hpp b/native/jni/include/db.hpp index 3491382df..42d87ef79 100644 --- a/native/jni/include/db.hpp +++ b/native/jni/include/db.hpp @@ -125,7 +125,6 @@ using db_row_cb = std::function; int get_db_settings(db_settings &cfg, int key = -1); int get_db_strings(db_strings &str, int key = -1); -int get_uid_policy(su_access &su, int uid); bool get_manager(int user_id, std::string *pkg, struct stat *st); bool get_manager(std::string *pkg = nullptr); int get_manager_app_id(); diff --git a/native/jni/su/su_daemon.cpp b/native/jni/su/su_daemon.cpp index 1906ff804..da765b1ee 100644 --- a/native/jni/su/su_daemon.cpp +++ b/native/jni/su/su_daemon.cpp @@ -64,14 +64,81 @@ static void database_check(const shared_ptr &info) { break; } - if (uid > 0) - get_uid_policy(info->access, uid); + su_access &su = info->access; + + if (uid > 0) { + char query[256], *err; + sprintf(query, + "SELECT policy, logging, notification FROM policies " + "WHERE uid=%d AND (until=0 OR until>%li)", uid, time(nullptr)); + err = db_exec(query, [&](db_row &row) -> bool { + su.policy = (policy_t) parse_int(row["policy"]); + su.log = parse_int(row["logging"]); + su.notify = parse_int(row["notification"]); + LOGD("magiskdb: query policy=[%d] log=[%d] notify=[%d]\n", + su.policy, su.log, su.notify); + return true; + }); + db_err_cmd(err, return); + } // We need to check our manager - if (info->access.log || info->access.notify) + if (su.log || su.notify) get_manager(to_user_id(uid), &info->mgr_pkg, &info->mgr_st); } +bool uid_granted_root(int uid) { + if (uid == UID_ROOT) + return true; + + db_settings cfg; + get_db_settings(cfg); + + // Check user root access settings + switch (cfg[ROOT_ACCESS]) { + case ROOT_ACCESS_DISABLED: + return false; + case ROOT_ACCESS_APPS_ONLY: + if (uid == UID_SHELL) + return false; + break; + case ROOT_ACCESS_ADB_ONLY: + if (uid != UID_SHELL) + return false; + break; + case ROOT_ACCESS_APPS_AND_ADB: + break; + } + + // Check multiuser settings + switch (cfg[SU_MULTIUSER_MODE]) { + case MULTIUSER_MODE_OWNER_ONLY: + if (to_user_id(uid) != 0) + return false; + break; + case MULTIUSER_MODE_OWNER_MANAGED: + uid = to_app_id(uid); + break; + case MULTIUSER_MODE_USER: + default: + break; + } + + bool granted = false; + + char query[256], *err; + snprintf(query, sizeof(query), + "SELECT policy FROM policies WHERE uid=%d AND (until=0 OR until>%li)", + uid, time(nullptr)); + err = db_exec(query, [&](db_row &row) -> bool { + granted = parse_int(row["policy"]) == ALLOW; + return true; + }); + db_err_cmd(err, return false); + + return granted; +} + static shared_ptr get_su_info(unsigned uid) { LOGD("su: request from uid=[%d]\n", uid); diff --git a/native/jni/zygisk/api.hpp b/native/jni/zygisk/api.hpp index 76455f51f..f5d790fe4 100644 --- a/native/jni/zygisk/api.hpp +++ b/native/jni/zygisk/api.hpp @@ -138,20 +138,25 @@ enum Option : int { // Setting this option only makes sense in preAppSpecialize. // The actual unmounting happens during app process specialization. // - // Processes added to Magisk's denylist will have all Magisk and its modules' files unmounted - // from its mount namespace. In addition, all Zygisk code will be unloaded from memory, which - // also implies that no Zygisk modules (including yours) are loaded. - // - // However, if for any reason your module still wants the unmount part of the denylist - // operation to be enabled EVEN IF THE PROCESS IS NOT ON THE DENYLIST, set this option. + // Set this option to force all Magisk and modules' files to be unmounted from the + // mount namespace of the process, regardless of the denylist enforcement status. FORCE_DENYLIST_UNMOUNT = 0, // When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize. - // Be aware that after dlclose-ing your module, all of your code will be unmapped. - // YOU SHOULD NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTION IN THE PROCESS. + // Be aware that after dlclose-ing your module, all of your code will be unmapped from memory. + // YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS. DLCLOSE_MODULE_LIBRARY = 1, }; +// Bit masks of the return value of Api::getFlags() +enum StateFlag : uint32_t { + // The user has granted root access to the current process + PROCESS_GRANTED_ROOT = (1u << 0), + + // The current process was added on the denylist + PROCESS_ON_DENYLIST = (1u << 1), +}; + // All API functions will stop working after post[XXX]Specialize as Zygisk will be unloaded // from the specialized process afterwards. struct Api { @@ -188,6 +193,10 @@ struct Api { // Check zygisk::Option for the full list of options available. void setOption(Option opt); + // Get information about the current process. + // Returns bitwise-or'd zygisk::StateFlag values. + uint32_t getFlags(); + // Hook JNI native methods for a class // // Lookup all registered JNI native methods and replace it with your own functions. @@ -272,6 +281,7 @@ struct api_table { int (*connectCompanion)(void * /* _this */); void (*setOption)(void * /* _this */, Option); int (*getModuleDir)(void * /* _this */); + uint32_t (*getFlags)(void * /* _this */); }; template @@ -295,6 +305,9 @@ inline int Api::getModuleDir() { inline void Api::setOption(Option opt) { if (impl->setOption) impl->setOption(impl->_this, opt); } +inline uint32_t Api::getFlags() { + return impl->getFlags ? impl->getFlags(impl->_this) : 0; +} inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) { if (impl->hookJniNativeMethods) impl->hookJniNativeMethods(env, className, methods, numMethods); } diff --git a/native/jni/zygisk/deny/cli.cpp b/native/jni/zygisk/deny/cli.cpp index 0f8602c6d..65b28ad05 100644 --- a/native/jni/zygisk/deny/cli.cpp +++ b/native/jni/zygisk/deny/cli.cpp @@ -51,7 +51,7 @@ void denylist_handler(int client, const sock_cred *cred) { ls_list(client); return; case DENY_STATUS: - res = (zygisk_enabled && denylist_enabled) ? DENY_IS_ENFORCED : DENY_NOT_ENFORCED; + res = (zygisk_enabled && denylist_enforced) ? DENY_IS_ENFORCED : DENY_NOT_ENFORCED; break; } diff --git a/native/jni/zygisk/deny/deny.hpp b/native/jni/zygisk/deny/deny.hpp index 67896ffc5..41433888c 100644 --- a/native/jni/zygisk/deny/deny.hpp +++ b/native/jni/zygisk/deny/deny.hpp @@ -22,7 +22,7 @@ bool is_deny_target(int uid, std::string_view process); void revert_unmount(); -extern std::atomic denylist_enabled; +extern std::atomic denylist_enforced; extern std::atomic cached_manager_app_id; enum : int { diff --git a/native/jni/zygisk/deny/utils.cpp b/native/jni/zygisk/deny/utils.cpp index 7f252c299..a8ff6ff6b 100644 --- a/native/jni/zygisk/deny/utils.cpp +++ b/native/jni/zygisk/deny/utils.cpp @@ -21,9 +21,9 @@ static int inotify_fd = -1; // Locks the variables above static pthread_mutex_t data_lock = PTHREAD_MUTEX_INITIALIZER; -atomic denylist_enabled = false; +atomic denylist_enforced = false; -#define do_kill (zygisk_enabled && denylist_enabled) +#define do_kill (zygisk_enabled && denylist_enforced) static void rebuild_map() { app_id_proc_map->clear(); @@ -317,13 +317,13 @@ static bool str_ends_safe(string_view s, string_view ss) { static void update_deny_config() { char sql[64]; sprintf(sql, "REPLACE INTO settings (key,value) VALUES('%s',%d)", - DB_SETTING_KEYS[DENYLIST_CONFIG], denylist_enabled.load()); + DB_SETTING_KEYS[DENYLIST_CONFIG], denylist_enforced.load()); char *err = db_exec(sql); db_err(err); } int enable_deny() { - if (denylist_enabled) { + if (denylist_enforced) { return DAEMON_SUCCESS; } else { mutex_guard lock(data_lock); @@ -338,10 +338,10 @@ int enable_deny() { LOGI("* Enable DenyList\n"); - denylist_enabled = true; + denylist_enforced = true; if (!ensure_data()) { - denylist_enabled = false; + denylist_enforced = false; return DAEMON_ERROR; } @@ -358,8 +358,8 @@ int enable_deny() { } int disable_deny() { - if (denylist_enabled) { - denylist_enabled = false; + if (denylist_enforced) { + denylist_enforced = false; LOGI("* Disable DenyList\n"); mutex_guard lock(data_lock); @@ -370,7 +370,7 @@ int disable_deny() { } void initialize_denylist() { - if (!denylist_enabled) { + if (!denylist_enforced) { db_settings dbs; get_db_settings(dbs, DENYLIST_CONFIG); if (dbs[DENYLIST_CONFIG]) diff --git a/native/jni/zygisk/entry.cpp b/native/jni/zygisk/entry.cpp index 912d22f83..930c20129 100644 --- a/native/jni/zygisk/entry.cpp +++ b/native/jni/zygisk/entry.cpp @@ -9,6 +9,7 @@ #include #include "zygisk.hpp" +#include "module.hpp" #include "deny/deny.hpp" using namespace std; @@ -166,7 +167,7 @@ static int zygisk_log(int prio, const char *fmt, va_list ap) { return ret; } -std::vector remote_get_info(int uid, const char *process, AppInfo *info) { +std::vector remote_get_info(int uid, const char *process, uint32_t *flags) { vector fds; if (int fd = connect_daemon(); fd >= 0) { write_int(fd, ZYGISK_REQUEST); @@ -174,8 +175,8 @@ std::vector remote_get_info(int uid, const char *process, AppInfo *info) { write_int(fd, uid); write_string(fd, process); - xxread(fd, info, sizeof(*info)); - if (!info->on_denylist) { + xxread(fd, flags, sizeof(*flags)); + if ((*flags & UNMOUNT_MASK) != UNMOUNT_MASK) { fds = recv_fds(fd); } close(fd); @@ -314,11 +315,13 @@ static void magiskd_passthrough(int client) { atomic cached_manager_app_id = -1; +extern bool uid_granted_root(int uid); static void get_process_info(int client, const sock_cred *cred) { - AppInfo info{}; int uid = read_int(client); string process = read_string(client); + uint32_t flags = 0; + // This function is called on every single zygote process specialization, // so performance is critical. get_manager_app_id() is expensive as it goes // through a SQLite query and potentially multiple filesystem stats, so we @@ -334,15 +337,22 @@ static void get_process_info(int client, const sock_cred *cred) { } if (to_app_id(uid) == manager_app_id) { - info.is_magisk_app = true; - } else if (denylist_enabled) { - info.on_denylist = is_deny_target(uid, process); + flags |= PROCESS_IS_MAGISK_APP; + } + if (denylist_enforced) { + flags |= DENYLIST_ENFORCING; + } + if (is_deny_target(uid, process)) { + flags |= PROCESS_ON_DENYLIST; + } + if (uid_granted_root(uid)) { + flags |= PROCESS_GRANTED_ROOT; } } - xwrite(client, &info, sizeof(info)); + xwrite(client, &flags, sizeof(flags)); - if (!info.on_denylist) { + if ((flags & UNMOUNT_MASK) != UNMOUNT_MASK) { char buf[256]; get_exe(cred->pid, buf, sizeof(buf)); vector fds = get_module_fds(str_ends(buf, "64")); diff --git a/native/jni/zygisk/hook.cpp b/native/jni/zygisk/hook.cpp index 986d712d5..1c7d00054 100644 --- a/native/jni/zygisk/hook.cpp +++ b/native/jni/zygisk/hook.cpp @@ -26,7 +26,7 @@ static bool unhook_functions(); namespace { enum { - UNMOUNT_FLAG, + DO_UNMOUNT, FORK_AND_SPECIALIZE, APP_SPECIALIZE, SERVER_SPECIALIZE, @@ -46,12 +46,13 @@ struct HookContext { void *raw_args; }; const char *process; - int pid; - bitset flags; - AppInfo info; vector modules; + bitset state; - HookContext() : pid(-1), info{} {} + int pid; + uint32_t flags; + + HookContext() : pid(-1), flags(0) {} static void close_fds(); void unload_zygisk(); @@ -154,7 +155,7 @@ DCL_HOOK_FUNC(int, fork) { DCL_HOOK_FUNC(int, unshare, int flags) { int res = old_unshare(flags); if (g_ctx && (flags & CLONE_NEWNS) != 0 && res == 0) { - if (g_ctx->flags[UNMOUNT_FLAG]) { + if (g_ctx->state[DO_UNMOUNT]) { revert_unmount(); } else { umount2("/system/bin/app_process64", MNT_DETACH); @@ -174,7 +175,7 @@ DCL_HOOK_FUNC(void, android_log_close) { DCL_HOOK_FUNC(int, selinux_android_setcontext, uid_t uid, int isSystemServer, const char *seinfo, const char *pkgname) { if (g_ctx) { - g_ctx->flags[CAN_DLCLOSE] = unhook_functions(); + g_ctx->state[CAN_DLCLOSE] = unhook_functions(); } return old_selinux_android_setcontext(uid, isSystemServer, seinfo, pkgname); } @@ -297,6 +298,7 @@ bool ZygiskModule::RegisterModule(ApiTable *table, long *module) { switch (ver) { case 2: table->v2.getModuleDir = [](ZygiskModule *m) { return m->getModuleDir(); }; + table->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); }; [[fallthrough]]; case 1: table->v1.hookJniNativeMethods = &hookJniNativeMethods; @@ -345,7 +347,7 @@ void ZygiskModule::setOption(zygisk::Option opt) { return; switch (opt) { case zygisk::FORCE_DENYLIST_UNMOUNT: - g_ctx->flags[UNMOUNT_FLAG] = true; + g_ctx->state[DO_UNMOUNT] = true; break; case zygisk::DLCLOSE_MODULE_LIBRARY: unload = true; @@ -353,6 +355,10 @@ void ZygiskModule::setOption(zygisk::Option opt) { } } +uint32_t ZygiskModule::getFlags() { + return g_ctx ? (g_ctx->flags & ~PRIVATE_MASK) : 0; +} + void HookContext::run_modules_pre(const vector &fds) { char buf[256]; @@ -384,9 +390,9 @@ void HookContext::run_modules_pre(const vector &fds) { for (auto &m : modules) { m.entry(&m.api, env); - if (flags[APP_SPECIALIZE]) { + if (state[APP_SPECIALIZE]) { m.preAppSpecialize(args); - } else if (flags[SERVER_SPECIALIZE]) { + } else if (state[SERVER_SPECIALIZE]) { m.preServerSpecialize(server_args); } } @@ -403,9 +409,9 @@ void HookContext::run_modules_pre(const vector &fds) { void HookContext::run_modules_post() { for (const auto &m : modules) { - if (flags[APP_SPECIALIZE]) { + if (state[APP_SPECIALIZE]) { m.postAppSpecialize(args); - } else if (flags[SERVER_SPECIALIZE]) { + } else if (state[SERVER_SPECIALIZE]) { m.postServerSpecialize(server_args); } m.doUnload(); @@ -417,7 +423,7 @@ void HookContext::close_fds() { } void HookContext::unload_zygisk() { - if (flags[CAN_DLCLOSE]) { + if (state[CAN_DLCLOSE]) { // Do NOT call the destructor operator delete(jni_method_map); // Directly unmap the whole memory block @@ -436,19 +442,19 @@ void HookContext::unload_zygisk() { void HookContext::nativeSpecializeAppProcess_pre() { g_ctx = this; - flags[APP_SPECIALIZE] = true; + state[APP_SPECIALIZE] = true; process = env->GetStringUTFChars(args->nice_name, nullptr); - if (flags[FORK_AND_SPECIALIZE]) { + if (state[FORK_AND_SPECIALIZE]) { ZLOGV("pre forkAndSpecialize [%s]\n", process); } else { ZLOGV("pre specialize [%s]\n", process); } - auto module_fds = remote_get_info(args->uid, process, &info); - if (info.on_denylist) { + auto module_fds = remote_get_info(args->uid, process, &flags); + if ((flags & UNMOUNT_MASK) == UNMOUNT_MASK) { // TODO: Handle MOUNT_EXTERNAL_NONE on older platforms ZLOGI("[%s] is on the denylist\n", process); - flags[UNMOUNT_FLAG] = true; + state[DO_UNMOUNT] = true; } else { run_modules_pre(module_fds); } @@ -458,7 +464,7 @@ void HookContext::nativeSpecializeAppProcess_pre() { } void HookContext::nativeSpecializeAppProcess_post() { - if (flags[FORK_AND_SPECIALIZE]) { + if (state[FORK_AND_SPECIALIZE]) { ZLOGV("post forkAndSpecialize [%s]\n", process); } else { ZLOGV("post specialize [%s]\n", process); @@ -466,21 +472,21 @@ void HookContext::nativeSpecializeAppProcess_post() { env->ReleaseStringUTFChars(args->nice_name, process); run_modules_post(); - if (info.is_magisk_app) { + if (flags & PROCESS_IS_MAGISK_APP) { setenv("ZYGISK_ENABLED", "1", 1); } g_ctx = nullptr; - if (!flags[FORK_AND_SPECIALIZE]) { + if (!state[FORK_AND_SPECIALIZE]) { unload_zygisk(); } } void HookContext::nativeForkSystemServer_pre() { fork_pre(); - flags[SERVER_SPECIALIZE] = true; + state[SERVER_SPECIALIZE] = true; if (pid == 0) { ZLOGV("pre forkSystemServer\n"); - run_modules_pre(remote_get_info(1000, "system_server", &info)); + run_modules_pre(remote_get_info(1000, "system_server", &flags)); close_fds(); android_logging(); } @@ -496,7 +502,7 @@ void HookContext::nativeForkSystemServer_post() { void HookContext::nativeForkAndSpecialize_pre() { fork_pre(); - flags[FORK_AND_SPECIALIZE] = true; + state[FORK_AND_SPECIALIZE] = true; if (pid == 0) { nativeSpecializeAppProcess_pre(); } diff --git a/native/jni/zygisk/module.hpp b/native/jni/zygisk/module.hpp index 513ec7652..d0db06516 100644 --- a/native/jni/zygisk/module.hpp +++ b/native/jni/zygisk/module.hpp @@ -52,6 +52,17 @@ struct ServerSpecializeArgsImpl { effective_capabilities(effective_capabilities) {} }; +enum : uint32_t { + PROCESS_GRANTED_ROOT = zygisk::StateFlag::PROCESS_GRANTED_ROOT, + PROCESS_ON_DENYLIST = zygisk::StateFlag::PROCESS_ON_DENYLIST, + + DENYLIST_ENFORCING = (1u << 30), + PROCESS_IS_MAGISK_APP = (1u << 31), + + UNMOUNT_MASK = (PROCESS_ON_DENYLIST | DENYLIST_ENFORCING), + PRIVATE_MASK = (0x3u << 30) +}; + template struct force_cast_wrapper { template @@ -80,10 +91,11 @@ struct ApiTable { int (*connectCompanion)(ZygiskModule *); void (*setOption)(ZygiskModule *, zygisk::Option); - } v1; + } v1{}; struct { int (*getModuleDir)(ZygiskModule *); - } v2; + uint32_t (*getFlags)(ZygiskModule *); + } v2{}; ApiTable(ZygiskModule *m); }; @@ -105,6 +117,7 @@ struct ZygiskModule { int connectCompanion() const; int getModuleDir() const; void setOption(zygisk::Option opt); + static uint32_t getFlags(); void doUnload() const { if (unload) dlclose(handle); } ZygiskModule(int id, void *handle, void *entry); diff --git a/native/jni/zygisk/zygisk.hpp b/native/jni/zygisk/zygisk.hpp index 9b150ef8a..2b07ef884 100644 --- a/native/jni/zygisk/zygisk.hpp +++ b/native/jni/zygisk/zygisk.hpp @@ -37,14 +37,9 @@ uintptr_t get_function_off(int pid, uintptr_t addr, char *lib); // Get function address, given library name + offset uintptr_t get_function_addr(int pid, const char *lib, uintptr_t off); -struct AppInfo { - bool is_magisk_app; - bool on_denylist; -}; - extern void *self_handle; void unload_first_stage(); void hook_functions(); -std::vector remote_get_info(int uid, const char *process, AppInfo *info); +std::vector remote_get_info(int uid, const char *process, uint32_t *flags); int remote_request_unmount();