diff --git a/native/jni/Android.mk b/native/jni/Android.mk index 05fbaec4e..1cdf8ad6b 100644 --- a/native/jni/Android.mk +++ b/native/jni/Android.mk @@ -23,6 +23,7 @@ LOCAL_SRC_FILES := \ core/bootstages.cpp \ core/socket.cpp \ core/db.cpp \ + core/package.cpp \ core/cert.cpp \ core/scripting.cpp \ core/restorecon.cpp \ diff --git a/native/jni/core/bootstages.cpp b/native/jni/core/bootstages.cpp index b9f873083..e9179d703 100644 --- a/native/jni/core/bootstages.cpp +++ b/native/jni/core/bootstages.cpp @@ -128,7 +128,7 @@ static bool magisk_env() { unlink(stub_path.data()); string pkg; - get_manager(&pkg); + get_manager(0, &pkg); sprintf(buf, "%s/0/%s/install", APP_DATA_DIR, pkg.empty() ? "xxx" /* Ensure non-exist path */ : pkg.data()); @@ -376,7 +376,7 @@ void boot_complete(int client) { xmkdir(SECURE_DIR, 0700); if (stub_fd > 0) { - if (!get_manager()) { + if (get_manager() < 0) { // Install stub struct stat st{}; fstat(stub_fd, &st); diff --git a/native/jni/core/db.cpp b/native/jni/core/db.cpp index 3de5e11aa..e1e073bd9 100644 --- a/native/jni/core/db.cpp +++ b/native/jni/core/db.cpp @@ -359,48 +359,6 @@ int get_db_strings(db_strings &str, int key) { return 0; } -bool get_manager(int user_id, std::string *pkg, struct stat *st) { - db_strings str; - get_db_strings(str, SU_MANAGER); - char app_path[128]; - - if (!str[SU_MANAGER].empty()) { - // App is repackaged - sprintf(app_path, "%s/%d/%s", APP_DATA_DIR, user_id, str[SU_MANAGER].data()); - if (stat(app_path, st) == 0) { - if (pkg) - pkg->swap(str[SU_MANAGER]); - return true; - } - } - - // Check the official package name - sprintf(app_path, "%s/%d/" JAVA_PACKAGE_NAME, APP_DATA_DIR, user_id); - if (stat(app_path, st) == 0) { - if (pkg) - *pkg = JAVA_PACKAGE_NAME; - return true; - } else { - LOGE("su: cannot find manager\n"); - memset(st, 0, sizeof(*st)); - if (pkg) - pkg->clear(); - return false; - } -} - -bool get_manager(string *pkg) { - struct stat st; - return get_manager(0, pkg, &st); -} - -int get_manager_app_id() { - struct stat st; - if (get_manager(0, nullptr, &st)) - return to_app_id(st.st_uid); - return -1; -} - void exec_sql(int client) { run_finally f([=]{ close(client); }); string sql = read_string(client); diff --git a/native/jni/core/package.cpp b/native/jni/core/package.cpp new file mode 100644 index 000000000..e6907775c --- /dev/null +++ b/native/jni/core/package.cpp @@ -0,0 +1,114 @@ +#include +#include +#include +#include + +using namespace std; + +// These functions will be called on every single zygote process specialization and su request, +// so performance is absolutely critical. Most operations should either have its result cached +// or simply skipped unless necessary. + +static atomic pkg_xml_ino = 0; + +// pkg_lock protects mgr_app_id and mgr_pkg +static pthread_mutex_t pkg_lock = PTHREAD_MUTEX_INITIALIZER; +static int mgr_app_id = -1; +static string *mgr_pkg; + +bool need_pkg_refresh() { + struct stat st{}; + stat("/data/system/packages.xml", &st); + ino_t ino = st.st_ino; + if (pkg_xml_ino.compare_exchange_strong(ino, st.st_ino)) { + // Packages have not changed + return false; + } else { + mutex_guard g(pkg_lock); + mgr_app_id = -1; + return true; + } +} + +// app_id = app_no + AID_APP_START +// app_no range: [0, 9999] +vector get_app_no_list() { + vector list; + auto data_dir = xopen_dir(APP_DATA_DIR); + if (!data_dir) + return list; + dirent *entry; + while ((entry = xreaddir(data_dir.get()))) { + // For each user + int dfd = xopenat(dirfd(data_dir.get()), entry->d_name, O_RDONLY); + if (auto dir = xopen_dir(dfd)) { + while ((entry = xreaddir(dir.get()))) { + // For each package + struct stat st{}; + xfstatat(dfd, entry->d_name, &st, 0); + int app_id = to_app_id(st.st_uid); + if (app_id >= AID_APP_START && app_id <= AID_APP_END) { + int app_no = app_id - AID_APP_START; + if (list.size() <= app_no) { + list.resize(app_no + 1); + } + list[app_no] = true; + } + } + } else { + close(dfd); + } + } + return list; +} + +int get_manager(int user_id, std::string *pkg) { + mutex_guard g(pkg_lock); + + char app_path[128]; + struct stat st{}; + if (mgr_pkg == nullptr) + default_new(mgr_pkg); + + int app_id = mgr_app_id; + if (app_id > 0) { + // Just need to check whether the app is installed in the user + snprintf(app_path, sizeof(app_path), "%s/%d/%s", APP_DATA_DIR, user_id, mgr_pkg->data()); + if (access(app_path, F_OK) == 0) { + if (pkg) *pkg = *mgr_pkg; + return user_id * AID_USER_OFFSET + app_id; + } else { + goto not_found; + } + } else { + db_strings str; + get_db_strings(str, SU_MANAGER); + + if (!str[SU_MANAGER].empty()) { + // App is repackaged + snprintf(app_path, sizeof(app_path), + "%s/%d/%s", APP_DATA_DIR, user_id, str[SU_MANAGER].data()); + if (stat(app_path, &st) == 0) { + mgr_pkg->swap(str[SU_MANAGER]); + } + } else { + // Check the original package name + snprintf(app_path, sizeof(app_path), "%s/%d/" JAVA_PACKAGE_NAME, APP_DATA_DIR, user_id); + if (stat(app_path, &st) == 0) { + *mgr_pkg = JAVA_PACKAGE_NAME; + } else { + goto not_found; + } + } + } + + mgr_app_id = to_app_id(st.st_uid); + if (pkg) *pkg = *mgr_pkg; + return st.st_uid; + +not_found: + LOGE("su: cannot find manager\n"); + if (pkg) + pkg->clear(); + return -1; +} diff --git a/native/jni/include/daemon.hpp b/native/jni/include/daemon.hpp index f4e87d3d8..1be549bf3 100644 --- a/native/jni/include/daemon.hpp +++ b/native/jni/include/daemon.hpp @@ -9,6 +9,10 @@ #include +#define AID_APP_START 10000 +#define AID_APP_END 19999 +#define AID_USER_OFFSET 100000 + // Daemon command codes namespace MainRequest { enum : int { @@ -81,6 +85,11 @@ void denylist_handler(int client, const sock_cred *cred); void su_daemon_handler(int client, const sock_cred *cred); void zygisk_handler(int client, const sock_cred *cred); +// Package +bool need_pkg_refresh(); +std::vector get_app_no_list(); +int get_manager(int user_id = 0, std::string *pkg = nullptr); + // Denylist void initialize_denylist(); int denylist_cli(int argc, char **argv); diff --git a/native/jni/include/db.hpp b/native/jni/include/db.hpp index 42d87ef79..6427178f3 100644 --- a/native/jni/include/db.hpp +++ b/native/jni/include/db.hpp @@ -125,9 +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); -bool get_manager(int user_id, std::string *pkg, struct stat *st); -bool get_manager(std::string *pkg = nullptr); -int get_manager_app_id(); void exec_sql(int client); char *db_exec(const char *sql); char *db_exec(const char *sql, const db_row_cb &fn); diff --git a/native/jni/su/connect.cpp b/native/jni/su/connect.cpp index 9daf9c4af..3521037cc 100644 --- a/native/jni/su/connect.cpp +++ b/native/jni/su/connect.cpp @@ -205,7 +205,7 @@ int app_request(const su_context &ctx) { strcpy(fifo, "/dev/socket/"); gen_rand_str(fifo + 12, 32, true); mkfifo(fifo, 0600); - chown(fifo, ctx.info->mgr_st.st_uid, ctx.info->mgr_st.st_gid); + chown(fifo, ctx.info->mgr_uid, ctx.info->mgr_uid); setfilecon(fifo, "u:object_r:" SEPOL_FILE_TYPE ":s0"); // Send request diff --git a/native/jni/su/su.hpp b/native/jni/su/su.hpp index 25d830da3..7542c5512 100644 --- a/native/jni/su/su.hpp +++ b/native/jni/su/su.hpp @@ -23,7 +23,7 @@ public: db_settings cfg; su_access access; std::string mgr_pkg; - struct stat mgr_st; + int mgr_uid; void check_db(); // These should be guarded with global cache lock diff --git a/native/jni/su/su_daemon.cpp b/native/jni/su/su_daemon.cpp index 5e55789aa..e4c968465 100644 --- a/native/jni/su/su_daemon.cpp +++ b/native/jni/su/su_daemon.cpp @@ -20,7 +20,7 @@ static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER; static shared_ptr cached; su_info::su_info(int uid) : -uid(uid), eval_uid(-1), access(DEFAULT_SU_ACCESS), mgr_st{}, +uid(uid), eval_uid(-1), access(DEFAULT_SU_ACCESS), mgr_uid(-1), timestamp(0), _lock(PTHREAD_MUTEX_INITIALIZER) {} su_info::~su_info() { @@ -82,7 +82,7 @@ void su_info::check_db() { // We need to check our manager if (access.log || access.notify) - get_manager(to_user_id(eval_uid), &mgr_pkg, &mgr_st); + mgr_uid = get_manager(to_user_id(eval_uid), &mgr_pkg); } bool uid_granted_root(int uid) { @@ -137,6 +137,32 @@ bool uid_granted_root(int uid) { return granted; } +static void prune_su_access() { + vector app_no_list = get_app_no_list(); + vector rm_uids; + char query[256], *err; + strlcpy(query, "SELECT uid FROM policies", sizeof(query)); + err = db_exec(query, [&](db_row &row) -> bool { + int uid = parse_int(row["uid"]); + int app_id = to_app_id(uid); + if (app_id >= AID_APP_START && app_id <= AID_APP_END) { + int app_no = app_id - AID_APP_START; + if (app_no >= app_no_list.size() || !app_no_list[app_no]) { + // The app_id is no longer installed + rm_uids.push_back(uid); + } + } + return true; + }); + db_err_cmd(err, return); + + for (int uid : rm_uids) { + snprintf(query, sizeof(query), "DELETE FROM policies WHERE uid == %d", uid); + // Don't care about errors + db_exec(query); + } +} + static shared_ptr get_su_info(unsigned uid) { LOGD("su: request from uid=[%d]\n", uid); @@ -144,6 +170,10 @@ static shared_ptr get_su_info(unsigned uid) { { mutex_guard lock(cache_lock); + if (need_pkg_refresh()) { + cached.reset(); + prune_su_access(); + } if (!cached || cached->uid != uid || !cached->is_fresh()) cached = make_shared(uid); cached->refresh(); @@ -157,7 +187,7 @@ static shared_ptr get_su_info(unsigned uid) { info->check_db(); // If it's root or the manager, allow it silently - if (info->uid == UID_ROOT || to_app_id(info->uid) == to_app_id(info->mgr_st.st_uid)) { + if (info->uid == UID_ROOT || to_app_id(info->uid) == to_app_id(info->mgr_uid)) { info->access = SILENT_SU_ACCESS; return info; } @@ -189,7 +219,7 @@ static shared_ptr get_su_info(unsigned uid) { return info; // If still not determined, check if manager exists - if (info->mgr_pkg.empty()) { + if (info->mgr_uid < 0) { info->access = NO_SU_ACCESS; return info; } diff --git a/native/jni/zygisk/deny/deny.hpp b/native/jni/zygisk/deny/deny.hpp index 9c262f816..656b1dcca 100644 --- a/native/jni/zygisk/deny/deny.hpp +++ b/native/jni/zygisk/deny/deny.hpp @@ -47,8 +47,6 @@ void ls_list(int client); // Utility functions bool is_deny_target(int uid, std::string_view process); - void revert_unmount(); extern std::atomic denylist_enforced; -extern std::atomic cached_manager_app_id; diff --git a/native/jni/zygisk/deny/utils.cpp b/native/jni/zygisk/deny/utils.cpp index 9bc301359..81ad8e380 100644 --- a/native/jni/zygisk/deny/utils.cpp +++ b/native/jni/zygisk/deny/utils.cpp @@ -32,23 +32,13 @@ atomic denylist_enforced = false; #define do_kill (zygisk_enabled && denylist_enforced) -static unsigned long long pkg_xml_ino = 0; - static void rescan_apps() { - { - struct stat st{}; - stat("/data/system/packages.xml", &st); - if (pkg_xml_ino == st.st_ino) { - // Packages has not changed, do not rescan - return; - } - pkg_xml_ino = st.st_ino; - } + if (!need_pkg_refresh()) + return; LOGD("denylist: rescanning apps\n"); app_id_to_pkgs.clear(); - cached_manager_app_id = -1; auto data_dir = xopen_dir(APP_DATA_DIR); if (!data_dir) diff --git a/native/jni/zygisk/entry.cpp b/native/jni/zygisk/entry.cpp index 819f4f0bd..1c9d447a2 100644 --- a/native/jni/zygisk/entry.cpp +++ b/native/jni/zygisk/entry.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include "zygisk.hpp" #include "module.hpp" @@ -313,8 +312,6 @@ static void magiskd_passthrough(int client) { send_fd(client, is_64_bit ? app_process_64 : app_process_32); } -atomic cached_manager_app_id = -1; - extern bool uid_granted_root(int uid); static void get_process_info(int client, const sock_cred *cred) { int uid = read_int(client); @@ -322,19 +319,10 @@ static void get_process_info(int client, const sock_cred *cred) { 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 - // really want to cache its app ID value. - if (is_deny_target(uid, process)) { flags |= PROCESS_ON_DENYLIST; } - int manager_app_id = cached_manager_app_id; - if (manager_app_id < 0) { - manager_app_id = get_manager_app_id(); - cached_manager_app_id = manager_app_id; - } + int manager_app_id = get_manager(); if (to_app_id(uid) == manager_app_id) { flags |= PROCESS_IS_MAGISK_APP; }