Add Zygisk API getFlags()

This commit is contained in:
topjohnwu 2022-01-17 19:54:33 -08:00
parent 76ddfeb93a
commit bb7a74e4b4
11 changed files with 173 additions and 82 deletions

View File

@ -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);

View File

@ -125,7 +125,6 @@ using db_row_cb = std::function<bool(db_row&)>;
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();

View File

@ -64,14 +64,81 @@ static void database_check(const shared_ptr<su_info> &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<su_info> get_su_info(unsigned uid) {
LOGD("su: request from uid=[%d]\n", uid);

View File

@ -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 <class T>
@ -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);
}

View File

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

View File

@ -22,7 +22,7 @@ bool is_deny_target(int uid, std::string_view process);
void revert_unmount();
extern std::atomic<bool> denylist_enabled;
extern std::atomic<bool> denylist_enforced;
extern std::atomic<int> cached_manager_app_id;
enum : int {

View File

@ -21,9 +21,9 @@ static int inotify_fd = -1;
// Locks the variables above
static pthread_mutex_t data_lock = PTHREAD_MUTEX_INITIALIZER;
atomic<bool> denylist_enabled = false;
atomic<bool> 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])

View File

@ -9,6 +9,7 @@
#include <db.hpp>
#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<int> remote_get_info(int uid, const char *process, AppInfo *info) {
std::vector<int> remote_get_info(int uid, const char *process, uint32_t *flags) {
vector<int> fds;
if (int fd = connect_daemon(); fd >= 0) {
write_int(fd, ZYGISK_REQUEST);
@ -174,8 +175,8 @@ std::vector<int> 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<int> 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<int> fds = get_module_fds(str_ends(buf, "64"));

View File

@ -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<FLAG_MAX> flags;
AppInfo info;
vector<ZygiskModule> modules;
bitset<FLAG_MAX> 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<int> &fds) {
char buf[256];
@ -384,9 +390,9 @@ void HookContext::run_modules_pre(const vector<int> &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<int> &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();
}

View File

@ -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<typename T>
struct force_cast_wrapper {
template<typename U>
@ -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);

View File

@ -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<int> remote_get_info(int uid, const char *process, AppInfo *info);
std::vector<int> remote_get_info(int uid, const char *process, uint32_t *flags);
int remote_request_unmount();