From a73e7e9f998577aea09dbea920bb5301d0b3583a Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Sat, 18 Apr 2020 02:00:48 -0700 Subject: [PATCH] Introduce new module mount implementation Rewrite the whole module mounting logic from scratch. Even the algorithm is different compared to the old one. This new design focuses on a few key points: - Modular: Custom nodes can be injected into the mount tree. It's the main reason for starting the rewrite (needed for Android 11) - Efficient: Compared to the existing implementation, this is the most efficient (both in terms of computation and memory usage) design I currently can come up with. - Accurate: The old mounting logic relies on handling specifically every edge case I can think of. During this rewrite I actually found some cases that the old design does not handle properly. This new design is architected in a way (node types and its rankings) that it should handle edge cases all by itself when constructing mount trees. --- native/jni/Android.mk | 1 + native/jni/core/bootstages.cpp | 439 +---------------------- native/jni/core/module.cpp | 619 +++++++++++++++++++++++++++++++++ native/jni/include/daemon.hpp | 1 + 4 files changed, 631 insertions(+), 429 deletions(-) create mode 100644 native/jni/core/module.cpp diff --git a/native/jni/Android.mk b/native/jni/Android.mk index 498edda1e..59d60a204 100644 --- a/native/jni/Android.mk +++ b/native/jni/Android.mk @@ -20,6 +20,7 @@ LOCAL_SRC_FILES := \ core/db.cpp \ core/scripting.cpp \ core/restorecon.cpp \ + core/module.cpp \ magiskhide/magiskhide.cpp \ magiskhide/proc_monitor.cpp \ magiskhide/hide_utils.cpp \ diff --git a/native/jni/core/bootstages.cpp b/native/jni/core/bootstages.cpp index 24564c2f4..e469705ca 100644 --- a/native/jni/core/bootstages.cpp +++ b/native/jni/core/bootstages.cpp @@ -16,286 +16,18 @@ #include #include #include -#include using namespace std; -static vector module_list; +extern vector module_list; static bool no_secure_dir = false; static bool pfs_done = false; -static int bind_mount(const char *from, const char *to, bool log = true); extern void auto_start_magiskhide(); -/*************** - * Magic Mount * - ***************/ - -#ifdef MAGISK_DEBUG -#define VLOGI(tag, from, to) LOGI("%s: %s <- %s\n", tag, to, from) -#else -#define VLOGI(tag, from, to) LOGI("%s: %s\n", tag, to) -#endif - -// Precedence: MODULE > SKEL > INTER > DUMMY -#define IS_DUMMY 0x01 /* mount from mirror */ -#define IS_INTER 0x02 /* intermediate node */ -#define IS_SKEL 0x04 /* replace with tmpfs */ -#define IS_MODULE 0x08 /* mount from module */ - -#define IS_DIR(n) ((n)->type == DT_DIR) -#define IS_LNK(n) ((n)->type == DT_LNK) -#define IS_REG(n) ((n)->type == DT_REG) - -class node_entry { -public: - explicit node_entry(const char *name, uint8_t type = DT_DIR, uint8_t status = IS_INTER) - : module(nullptr), name(name), type(type), status(status), parent(nullptr) {} - ~node_entry(); - void create_module_tree(const char *module); - void magic_mount(); - node_entry *extract(const char *name); - - static bool vendor_root; - static bool product_root; - -private: - const char *module; /* Only used when IS_MODULE */ - const string name; - uint8_t type; - uint8_t status; - node_entry *parent; - vector children; - - node_entry(node_entry *parent, const char *module, const char *name, uint8_t type) - : module(module), name(name), type(type), status(0), parent(parent) {} - bool is_special(); - bool is_root(); - string get_path(); - void insert(node_entry *&); - void clone_skeleton(); -}; - -bool node_entry::vendor_root = false; -bool node_entry::product_root = false; - -node_entry::~node_entry() { - for (auto &node : children) - delete node; -} - -#define SPECIAL_NODE (parent->parent ? false : \ -((vendor_root && name == "vendor") || (product_root && name == "product"))) - -bool node_entry::is_special() { - return parent ? SPECIAL_NODE : false; -} - -bool node_entry::is_root() { - return parent ? SPECIAL_NODE : true; -} - -string node_entry::get_path() { - string path; - if (parent) - path = parent->get_path(); - path += "/"; - path += name; - return path; -} - -void node_entry::insert(node_entry *&node) { - node->parent = this; - for (auto &child : children) { - if (child->name == node->name) { - if (node->status > child->status) { - // The new node has higher precedence - delete child; - child = node; - } else { - delete node; - node = child; - } - return; - } - } - children.push_back(node); -} - -void node_entry::create_module_tree(const char *module) { - auto full_path = get_path(); - auto cwd = MODULEROOT + "/"s + module + full_path; - - auto dir = xopen_dir(cwd.data()); - if (!dir) - return; - - // Check directory replace - if (faccessat(dirfd(dir.get()), ".replace", F_OK, 0) == 0) { - if (is_root()) { - // Root nodes should not be replaced - rm_rf(cwd.data()); - } else if (status < IS_MODULE) { - // Upgrade current node to current module - this->module = module; - status = IS_MODULE; - } - return; - } - - for (dirent *entry; (entry = xreaddir(dir.get()));) { - if (entry->d_name == "."sv || entry->d_name == ".."sv) - continue; - // Create new node - auto node = new node_entry(this, module, entry->d_name, entry->d_type); - - auto dest = full_path + "/" + entry->d_name; - auto src = cwd + "/" + entry->d_name; - - /* - * Clone current directory in one of the following conditions: - * - Target does not exist - * - Module file is a symlink - * - Target file is a symlink (exclude special nodes) - */ - bool clone = false; - if (IS_LNK(node) || access(dest.data(), F_OK) != 0) { - clone = true; - } else if (!node->is_special()) { - struct stat s; - xlstat(dest.data(), &s); - if (S_ISLNK(s.st_mode)) - clone = true; - } - if (clone && is_root()) { - // Root nodes should not be cloned - rm_rf(src.data()); - delete node; - continue; - } - - if (clone) { - // Mark self as a skeleton - status |= IS_SKEL; /* This will not overwrite if parent is module */ - node->status = IS_MODULE; - } else if (node->is_special()) { - // Special nodes will be pulled out as root nodes later - node->status = IS_INTER; - } else { - // Clone attributes from real path - clone_attr(dest.data(), src.data()); - if (IS_DIR(node)) { - // First mark as an intermediate node - node->status = IS_INTER; - } else if (IS_REG(node)) { - // This is a file, mark as leaf - node->status = IS_MODULE; - } - } - insert(node); - if (IS_DIR(node)) { - // Recursive traverse through everything - node->create_module_tree(module); - } - } -} - -void node_entry::clone_skeleton() { - // Clone the structure - auto full_path = get_path(); - auto mirror_path = MAGISKTMP + "/" MIRRDIR + full_path; - - if (auto dir = xopen_dir(mirror_path.data()); dir) { - for (dirent *entry; (entry = xreaddir(dir.get()));) { - if (entry->d_name == "."sv || entry->d_name == ".."sv) - continue; - // Create dummy node - auto dummy = new node_entry(entry->d_name, entry->d_type, IS_DUMMY); - insert(dummy); - } - } else { return; } - - if (status & IS_SKEL) { - file_attr attr; - getattr(full_path.data(), &attr); - LOGI("mnt_tmpfs : %s\n", full_path.data()); - xmount("tmpfs", full_path.data(), "tmpfs", 0, nullptr); - setattr(full_path.data(), &attr); - } - - for (auto &child : children) { - auto dest = full_path + "/" + child->name; - string src; - - // Create the dummy file/directory - if (IS_DIR(child)) - xmkdir(dest.data(), 0755); - else if (IS_REG(child)) - close(creat(dest.data(), 0644)); - // Links will be handled later - - if (child->status & IS_MODULE) { - // Mount from module file to dummy file - src = MAGISKTMP + "/" MODULEMNT "/" + child->module + full_path + "/" + child->name; - } else if (child->status & (IS_SKEL | IS_INTER)) { - // It's an intermediate folder, recursive clone - child->clone_skeleton(); - continue; - } else if (child->status & IS_DUMMY) { - // Mount from mirror to dummy file - src = MAGISKTMP + "/" MIRRDIR + full_path + "/" + child->name; - } - - if (IS_LNK(child)) { - // Copy symlinks directly - cp_afc(src.data(), dest.data()); - VLOGI("copy_link ", src.data(), dest.data()); - } else { - bind_mount(src.data(), dest.data()); - } - } -} - -void node_entry::magic_mount() { - if (status & IS_MODULE) { - // Mount module item - auto dest = get_path(); - auto src = MAGISKTMP + "/" MODULEMNT "/" + module + dest; - bind_mount(src.data(), dest.data()); - } else if (status & IS_SKEL) { - // The node is labeled to be cloned with skeleton, lets do it - clone_skeleton(); - } else if (status & IS_INTER) { - // It's an intermediate node, travel deeper - for (auto &child : children) - child->magic_mount(); - } -} - -node_entry *node_entry::extract(const char *name) { - node_entry *node = nullptr; - // Extract the node out of the tree - for (auto it = children.begin(); it != children.end(); ++it) { - if ((*it)->name == name) { - node = *it; - node->parent = nullptr; - children.erase(it); - break; - } - } - return node; -} - -/***************** - * Miscellaneous * - *****************/ - -static int bind_mount(const char *from, const char *to, bool log) { - int ret = xmount(from, to, nullptr, MS_BIND, nullptr); - if (log) VLOGI("bind_mount", from, to); - return ret; -} - +/********* + * Setup * + *********/ #define DIR_IS(part) (me->mnt_dir == "/" #part ""sv) #define SETMIR(b, part) sprintf(b, "%s/" MIRRDIR "/" #part, MAGISKTMP.data()) @@ -308,7 +40,7 @@ static int bind_mount(const char *from, const char *to, bool log) { mknod(buf2, (st.st_mode & S_IFMT) | 0600, st.st_rdev); \ xmkdir(buf1, 0755); \ xmount(buf2, buf1, me->mnt_type, flag, nullptr); \ - VLOGI("mount", buf2, buf1); \ + LOGI("mount: %s\n", buf1); \ } static bool magisk_env() { @@ -367,7 +99,7 @@ static bool magisk_env() { mount_mirror(system_root, MS_RDONLY); SETMIR(buf1, system); xsymlink("./system_root/system", buf1); - VLOGI("link", "./system_root/system", buf1); + LOGI("link: %s\n", buf1); system_as_root = true; } else if (!system_as_root && DIR_IS(system)) { mount_mirror(system, MS_RDONLY); @@ -387,17 +119,17 @@ static bool magisk_env() { if (access(buf1, F_OK) != 0 && access(buf2, F_OK) == 0) { // Pre-init mirrors xsymlink("./system_root/system", buf1); - VLOGI("link", "./system_root/system", buf1); + LOGI("link: %s\n", buf1); } SETMIR(buf1, vendor); if (access(buf1, F_OK) != 0) { xsymlink("./system/vendor", buf1); - VLOGI("link", "./system/vendor", buf1); + LOGI("link: %s\n", buf1); } SETMIR(buf1, product); if (access("/system/product", F_OK) == 0 && access(buf1, F_OK) != 0) { xsymlink("./system/product", buf1); - VLOGI("link", "./system/product", buf1); + LOGI("link: %s\n", buf1); } // Disable/remove magiskhide, resetprop @@ -419,35 +151,6 @@ static bool magisk_env() { return true; } -static void prepare_modules() { - // Upgrade modules - if (auto dir = open_dir(MODULEUPGRADE); dir) { - int ufd = dirfd(dir.get()); - int mfd = xopen(MODULEROOT, O_RDONLY | O_CLOEXEC); - for (dirent *entry; (entry = xreaddir(dir.get()));) { - if (entry->d_type == DT_DIR) { - if (entry->d_name == "."sv || entry->d_name == ".."sv) - continue; - // Cleanup old module if exists - if (faccessat(mfd, entry->d_name, F_OK, 0) == 0) { - frm_rf(xopenat(mfd, entry->d_name, O_RDONLY | O_CLOEXEC)); - unlinkat(mfd, entry->d_name, AT_REMOVEDIR); - } - LOGI("Upgrade / New module: %s\n", entry->d_name); - renameat(ufd, entry->d_name, mfd, entry->d_name); - } - } - close(mfd); - rm_rf(MODULEUPGRADE); - } - auto src = MAGISKTMP + "/" MIRRDIR "/" MODULEROOT; - auto dest = MAGISKTMP + "/" MODULEMNT; - bind_mount(src.data(), dest.data(), false); - - restorecon(); - chmod(SECURE_DIR, 0700); -} - static void reboot() { if (RECOVERY_MODE) exec_command_sync("/system/bin/reboot", "recovery"); @@ -472,94 +175,6 @@ void remove_modules() { reboot(); } -static void collect_modules() { - auto dir = xopen_dir(MODULEROOT); - int dfd = dirfd(dir.get()); - for (dirent *entry; (entry = xreaddir(dir.get()));) { - if (entry->d_type == DT_DIR) { - if (entry->d_name == "."sv || entry->d_name == ".."sv || entry->d_name == ".core"sv) - continue; - - int modfd = xopenat(dfd, entry->d_name, O_RDONLY); - run_finally f([=]{ close(modfd); }); - - if (faccessat(modfd, "remove", F_OK, 0) == 0) { - LOGI("%s: remove\n", entry->d_name); - auto uninstaller = MODULEROOT + "/"s + entry->d_name + "/uninstall.sh"; - if (access(uninstaller.data(), F_OK) == 0) - exec_script(uninstaller.data()); - frm_rf(xdup(modfd)); - unlinkat(dfd, entry->d_name, AT_REMOVEDIR); - continue; - } - - unlinkat(modfd, "update", 0); - - if (faccessat(modfd, "disable", F_OK, 0) != 0) - module_list.emplace_back(entry->d_name); - } - } -} - -static bool load_modules(node_entry *root) { - char buf1[4096]; - char buf2[4096]; - - LOGI("* Loading modules\n"); - bool has_modules = false; - for (const auto &m : module_list) { - auto module = m.data(); - char *b1 = buf1 + sprintf(buf1, MODULEROOT "/%s/", module); - - // Read props - strcpy(b1, "system.prop"); - if (access(buf1, F_OK) == 0) { - LOGI("%s: loading [system.prop]\n", module); - load_prop_file(buf1, false); - } - - // Copy sepolicy rules - strcpy(b1, "sepolicy.rule"); - char *b2 = buf2 + sprintf(buf2, "%s/" MIRRDIR "/persist", MAGISKTMP.data()); - if (access(buf2, F_OK) == 0 && access(buf1, F_OK) == 0) { - b2 += sprintf(b2, "/magisk/%s", module); - xmkdirs(buf2, 0755); - strcpy(b2, "/sepolicy.rule"); - cp_afc(buf1, buf2); - } - - // Check whether skip mounting - strcpy(b1, "skip_mount"); - if (access(buf1, F_OK) == 0) - continue; - - // Double check whether the system folder exists - strcpy(b1, "system"); - if (access(buf1, F_OK) != 0) - continue; - - // Construct structure - has_modules = true; - LOGI("%s: constructing magic mount structure\n", module); - // If /system/vendor exists in module, create a link outside - strcpy(b1, "system/vendor"); - if (node_entry::vendor_root && access(buf1, F_OK) == 0) { - sprintf(buf2, MODULEROOT "/%s/vendor", module); - unlink(buf2); - xsymlink("./system/vendor", buf2); - } - // If /system/product exists in module, create a link outside - strcpy(b1, "system/product"); - if (node_entry::product_root && access(buf1, F_OK) == 0) { - sprintf(buf2, MODULEROOT "/%s/product", module); - unlink(buf2); - xsymlink("./system/product", buf2); - } - root->create_module_tree(module); - } - return has_modules; -} - static bool check_data() { bool mnt = false; bool data = false; @@ -682,45 +297,11 @@ void post_fs_data(int client) { LOGI("* Running post-fs-data.d scripts\n"); exec_common_script("post-fs-data"); - prepare_modules(); - // Core only mode if (access(DISABLEFILE, F_OK) == 0) core_only(); - collect_modules(); - - // Execute module scripts - LOGI("* Running module post-fs-data scripts\n"); - exec_module_script("post-fs-data", module_list); - - // Recollect modules - module_list.clear(); - collect_modules(); - - node_entry::vendor_root = access("/vendor", F_OK) == 0; - node_entry::product_root = access("/product", F_OK) == 0; - - // Create the system root entry - auto sys_root = new node_entry("system"); - - if (load_modules(sys_root)) { - // Pull out special nodes if exist - node_entry *special; - if (node_entry::vendor_root && (special = sys_root->extract("vendor"))) { - special->magic_mount(); - delete special; - } - if (node_entry::product_root && (special = sys_root->extract("product"))) { - special->magic_mount(); - delete special; - } - - sys_root->magic_mount(); - } - - // Cleanup memory - delete sys_root; + handle_modules(); core_only(); } diff --git a/native/jni/core/module.cpp b/native/jni/core/module.cpp new file mode 100644 index 000000000..74e45f065 --- /dev/null +++ b/native/jni/core/module.cpp @@ -0,0 +1,619 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace std; + +#define SKIP_DOTS {\ +if (entry->d_name == "."sv || entry->d_name == ".."sv) \ + continue;\ +} + +#ifdef MAGISK_DEBUG +#define VLOGI(tag, from, to) LOGI("%-8s: %s <- %s\n", tag, to, from) +#else +#define VLOGI(tag, from, to) LOGI("%-8s: %s\n", tag, to) +#endif + +#define TYPE_INTER (1 << 0) /* intermediate node */ +#define TYPE_MIRROR (1 << 1) /* mount from mirror */ +#define TYPE_SKEL (1 << 2) /* replace with tmpfs */ +#define TYPE_MODULE (1 << 3) /* mount from module */ +#define TYPE_ROOT (1 << 4) /* partition root */ +#define TYPE_DIR (TYPE_INTER|TYPE_MIRROR|TYPE_SKEL|TYPE_ROOT) + +vector module_list; + +class node_entry; +class dir_node; +class inter_node; +class mirror_node; +class skel_node; +class module_node; +class root_node; + +static void merge_node(node_entry *a, node_entry *b); +template static bool isa(node_entry *node); +static int bind_mount(const char *from, const char *to) { + int ret = xmount(from, to, nullptr, MS_BIND, nullptr); + if (ret == 0) + VLOGI("bind_mnt", from, to); + return ret; +} + +template uint8_t type_id() { return 0; } +template<> uint8_t type_id() { return TYPE_DIR; } +template<> uint8_t type_id() { return TYPE_INTER; } +template<> uint8_t type_id() { return TYPE_MIRROR; } +template<> uint8_t type_id() { return TYPE_SKEL; } +template<> uint8_t type_id() { return TYPE_MODULE; } +template<> uint8_t type_id() { return TYPE_ROOT; } + +class node_entry { +public: + virtual ~node_entry() = default; + + bool is_dir() { return file_type() == DT_DIR; } + bool is_lnk() { return file_type() == DT_LNK; } + bool is_reg() { return file_type() == DT_REG; } + + uint8_t type() { return node_type; } + const string &name() { return _name; } + + dir_node *parent() { return _parent; } + + virtual void mount() = 0; + + static string module_mnt; + static string mirror_dir; + +protected: + template + node_entry(const char *name, uint8_t file_type, T*) + : _name(name), _file_type(file_type), node_type(type_id()) {} + + template + node_entry(T*) : node_type(type_id()) {} + + // Paths + const string &node_path(); + string mirror_path() { return mirror_dir + node_path(); } + + void create_and_mount(const string &src); + + /* Use top bit of _file_type for node exist status */ + bool exist() { return static_cast(_file_type & (1 << 7)); } + void set_exist() { _file_type |= (1 << 7); } + uint8_t file_type() { return static_cast(_file_type & ~(1 << 7)); } + +private: + friend void merge_node(node_entry *a, node_entry *b); + friend class dir_node; + + bool need_clone(); + + // Node properties + string _name; + uint8_t _file_type; + uint8_t node_type; + + dir_node *_parent = nullptr; + + // Cache + string _node_path; +}; + +class dir_node : public node_entry { +public: + friend void merge_node(node_entry *a, node_entry *b); + + typedef map map_type; + typedef map_type::iterator map_iter; + + ~dir_node() override; + + // Return false to indicate need to upgrade to module + bool collect_files(const char *module, int dfd); + + // Return false to indicate need to upgrade to skeleton + bool prepare(); + + // Default directory mount logic + void mount() override { + for (auto &pair : children) + pair.second->mount(); + } + + /*************** + * Tree Methods + ***************/ + + bool is_empty() { return children.empty(); } + + template + T *child(string_view name) { return iter_to_node(children.find(name)); } + + // Lazy val + root_node *root() { + if (!_root) + _root = _parent->root(); + return _root; + } + + // Return child with name or nullptr + node_entry *extract(string_view name); + + // Return false if rejected + bool insert(node_entry *node) { + return node + ? iter_to_node(insert(children.find(node->_name), node->node_type, + [=](auto _) { return node; })) != nullptr + : false; + } + + // Return inserted node or null if rejected + template + T *emplace(string_view name, Args &...args) { + return iter_to_node(insert(children.find(name), type_id(), + [&](auto _) { return new T(std::forward(args)...); })); + } + + // Return inserted node, existing node with same rank, or null + template + T *emplace_or_get(string_view name, Args &...args) { + return iter_to_node(insert(children.find(name), type_id(), + [&](auto _) { return new T(std::forward(args)...); }, true)); + } + + // Return upgraded node or null if rejected + template + T *upgrade(string_view name, Args &...args) { + return iter_to_node(upgrade(children.find(name), args...)); + } + +protected: + template + dir_node(const char *name, uint8_t file_type, T *self) : node_entry(name, file_type, self) { + if constexpr (std::is_same_v) + _root = self; + } + + template + dir_node(node_entry *node, T *self) : node_entry(self) { + merge_node(this, node); + if constexpr (std::is_same_v) + _root = self; + } + + template + dir_node(const char *name, T *self) : dir_node(name, DT_DIR, self) {} + +private: + // Root node lookup cache + root_node *_root; + + // dir nodes host children + map_type children; + + template + T *iter_to_node(const map_iter &it) { + return reinterpret_cast(it == children.end() ? nullptr : it->second); + } + + /* fn signature: (node_ent *&) -> node_ent * + * fn gets reference to the existing node, may be null. + * If fn consumes input, need to set reference to null. + * Returns new node or null to reject the insertion. */ + + template + map_iter insert(map_iter it, uint8_t type, Func fn, bool allow_same = false); + + template + map_iter upgrade(map_iter it, Args &...args) { + return insert(it, type_id(), [&](node_entry *&ex) -> node_entry * { + if (!ex) + return nullptr; + if constexpr (!std::is_same_v) { + // Type check if specific type is selected + if (!isa(ex)) + return nullptr; + } + auto node = new To(reinterpret_cast(ex), std::forward(args)...); + ex = nullptr; + return node; + }); + } +}; + +class root_node : public dir_node { +public: + root_node(const char *name) : dir_node(name, this), prefix("") {} + root_node(node_entry *node) : dir_node(node, this), prefix("/system") {} + const char * const prefix; +}; + +class inter_node : public dir_node { +public: + inter_node(const char *name, const char *module) : dir_node(name, this), module(module) {} +private: + const char *module; + friend class module_node; +}; + +void node_entry::create_and_mount(const string &src) { + const string &dest = node_path(); + if (is_lnk()) { + VLOGI("cp_link", src.data(), dest.data()); + cp_afc(src.data(), dest.data()); + } else { + if (is_dir()) + xmkdir(dest.data(), 0); + else if (is_reg()) + close(xopen(dest.data(), O_RDONLY | O_CREAT | O_CLOEXEC, 0)); + else + return; + bind_mount(src.data(), dest.data()); + } +} + +class module_node : public node_entry { +public: + module_node(const char *module, dirent *entry) + : node_entry(entry->d_name, entry->d_type, this), module(module) {} + + module_node(node_entry *node, const char *module) : node_entry(this), module(module) { + merge_node(this, node); + } + + module_node(inter_node *node) : module_node(node, node->module) {} + + void mount() override { + string src = module_mnt + module + parent()->root()->prefix + node_path(); + if (exist()) + clone_attr(mirror_path().data(), src.data()); + if (isa(parent())) + create_and_mount(src); + else if (is_dir() || is_reg()) + bind_mount(src.data(), node_path().data()); + } + +private: + const char *module; +}; + +class mirror_node : public dir_node { +public: + mirror_node(dirent *entry) : dir_node(entry->d_name, entry->d_type, this) {} + + void mount() override { + create_and_mount(mirror_path()); + dir_node::mount(); + } +}; + +class skel_node : public dir_node { +public: + skel_node(node_entry *node) : dir_node(node, this) {} + + void mount() override { + string src = mirror_path(); + const string &dest = node_path(); + file_attr a; + getattr(src.data(), &a); + xmount("tmpfs", dest.data(), "tmpfs", 0, nullptr); + VLOGI("mnt_tmp", "tmpfs", dest.data()); + setattr(dest.data(), &a); + dir_node::mount(); + } +}; + +// Poor man's dynamic cast without RTTI +template +static bool isa(node_entry *node) { + return node && (node->type() & type_id()); +} +template +static T *dyn_cast(node_entry *node) { + return isa(node) ? reinterpret_cast(node) : nullptr; +} + +// Merge b -> a, b will be deleted +static void merge_node(node_entry *a, node_entry *b) { + a->_name.swap(b->_name); + a->_file_type = b->_file_type; + a->_parent = b->_parent; + + // Merge children if both is dir + if (auto aa = dyn_cast(a); aa) { + if (auto bb = dyn_cast(b); bb) { + aa->children.merge(bb->children); + for (auto &pair : aa->children) + pair.second->_parent = aa; + } + } + delete b; +} + +string node_entry::module_mnt; +string node_entry::mirror_dir; + +const string &node_entry::node_path() { + if (_parent && _node_path.empty()) + _node_path = _parent->node_path() + '/' + _name; + return _node_path; +} + +dir_node::~dir_node() { + for (auto &it : children) + delete it.second; + children.clear(); +} + +template +dir_node::map_iter dir_node::insert(dir_node::map_iter it, uint8_t type, Func fn, bool allow_same) { + node_entry *node = nullptr; + if (it != children.end()) { + if (it->second->node_type < type) { + // Upgrade existing node only if higher precedence + node = fn(it->second); + if (!node) + return children.end(); + if (it->second) + merge_node(node, it->second); + it = children.erase(it); + // Minor optimization to make insert O(1) by using hint + if (it == children.begin()) + it = children.emplace(node->_name, node).first; + else + it = children.emplace_hint(--it, node->_name, node); + } else { + if (allow_same && it->second->node_type == type) + return it; + return children.end(); + } + } else { + node = fn(node); + if (!node) + return it; + node->_parent = this; + it = children.emplace(node->_name, node).first; + } + return it; +} + +node_entry* dir_node::extract(string_view name) { + auto it = children.find(name); + if (it != children.end()) { + auto ret = it->second; + children.erase(it); + return ret; + } + return nullptr; +} + +bool node_entry::need_clone() { + /* We need to upgrade parent to skeleton if: + * - Target does not exist + * - Source or target is a symlink */ + bool clone = false; + struct stat st; + if (lstat(node_path().data(), &st) != 0) { + clone = true; + } else { + set_exist(); // cache exist result + if (is_lnk() || S_ISLNK(st.st_mode)) + clone = true; + } + return clone; +} + +bool dir_node::prepare() { + bool upgrade_skel = false; + for (auto it = children.begin(); it != children.end();) { + auto child = it->second; + if (child->need_clone()) { + if (node_type > type_id()) { + // Upgrade will fail, remove unsupported child node + delete child; + it = children.erase(it); + continue; + } + // Tell parent to upgrade self to skel + upgrade_skel = true; + // If child is inter_node, upgrade to module + if (auto nit = upgrade(it); nit != children.end()) { + it = nit; + goto increment; + } + } + if (auto dn = dyn_cast(child); dn && dn->is_dir() && !dn->prepare()) { + // Upgrade child to skeleton (shall always success) + it = upgrade(it); + auto skel = iter_to_node(it); + string mirror = skel->mirror_path(); + auto dir = xopen_dir(mirror.data()); + for (dirent *entry; (entry = xreaddir(dir.get()));) { + SKIP_DOTS + // Insert mirror nodes + skel->emplace(entry->d_name, entry); + } + } +increment: + ++it; + } + return !upgrade_skel; +} + +bool dir_node::collect_files(const char *module, int dfd) { + auto dir = xopen_dir(xopenat(dfd, _name.data(), O_RDONLY | O_CLOEXEC)); + if (!dir) + return true; + + for (dirent *entry; (entry = xreaddir(dir.get()));) { + SKIP_DOTS + if (entry->d_name == ".replace"sv) { + // Stop traversing and tell parent to upgrade self to module + return false; + } + + if (entry->d_type == DT_DIR) { + // Need check cause emplace could fail due to previous module dir replace + if (auto dn = emplace_or_get(entry->d_name, entry->d_name, module); dn && + !dn->collect_files(module, dirfd(dir.get()))) { + // Upgrade node to module due to '.replace' + upgrade(dn->name(), module); + } + } else { + emplace(entry->d_name, module, entry); + } + } + return true; +} + +static void mount_modules() { + node_entry::mirror_dir = MAGISKTMP + "/" MIRRDIR; + node_entry::module_mnt = MAGISKTMP + "/" MODULEMNT "/"; + + auto root = make_unique(""); + auto system = new root_node("system"); + root->insert(system); + + char buf1[4096]; + char buf2[4096]; + LOGI("* Loading modules\n"); + for (const auto &m : module_list) { + auto module = m.data(); + char *b1 = buf1 + sprintf(buf1, MODULEROOT "/%s/", module); + + // Read props + strcpy(b1, "system.prop"); + if (access(buf1, F_OK) == 0) { + LOGI("%s: loading [system.prop]\n", module); + load_prop_file(buf1, false); + } + + // Copy sepolicy rules + strcpy(b1, "sepolicy.rule"); + char *b2 = buf2 + sprintf(buf2, "%s/" MIRRDIR "/persist", MAGISKTMP.data()); + if (access(buf1, F_OK) == 0 && access(buf2, F_OK) == 0) { + b2 += sprintf(b2, "/magisk/%s", module); + xmkdirs(buf2, 0755); + strcpy(b2, "/sepolicy.rule"); + cp_afc(buf1, buf2); + } + + // Check whether skip mounting + strcpy(b1, "skip_mount"); + if (access(buf1, F_OK) == 0) + continue; + + // Double check whether the system folder exists + strcpy(b1, "system"); + if (access(buf1, F_OK) != 0) + continue; + + LOGI("%s: loading mount files\n", module); + b1[-1] = '\0'; + int fd = xopen(buf1, O_RDONLY | O_CLOEXEC); + system->collect_files(module, fd); + close(fd); + } + if (system->is_empty()) + return; + + // Handle special read-only partitions + for (const char *part : { "/vendor", "/product", "/system_ext" }) { + struct stat st; + if (lstat(part, &st) == 0 && S_ISDIR(st.st_mode)) { + if (auto old = system->extract(part + 1); old) { + auto new_node = new root_node(old); + root->insert(new_node); + } + } + } + + root->prepare(); + root->mount(); +} + +static void prepare_modules() { + // Upgrade modules + if (auto dir = open_dir(MODULEUPGRADE); dir) { + int ufd = dirfd(dir.get()); + int mfd = xopen(MODULEROOT, O_RDONLY | O_CLOEXEC); + for (dirent *entry; (entry = xreaddir(dir.get()));) { + if (entry->d_type == DT_DIR) { + if (entry->d_name == "."sv || entry->d_name == ".."sv) + continue; + // Cleanup old module if exists + if (faccessat(mfd, entry->d_name, F_OK, 0) == 0) { + frm_rf(xopenat(mfd, entry->d_name, O_RDONLY | O_CLOEXEC)); + unlinkat(mfd, entry->d_name, AT_REMOVEDIR); + } + LOGI("Upgrade / New module: %s\n", entry->d_name); + renameat(ufd, entry->d_name, mfd, entry->d_name); + } + } + close(mfd); + rm_rf(MODULEUPGRADE); + } + + // Setup module mount (workaround nosuid selabel issue) + auto src = MAGISKTMP + "/" MIRRDIR MODULEROOT; + auto dest = MAGISKTMP + "/" MODULEMNT; + bind_mount(src.data(), dest.data()); + + restorecon(); + chmod(SECURE_DIR, 0700); +} + +static void collect_modules() { + auto dir = xopen_dir(MODULEROOT); + int dfd = dirfd(dir.get()); + for (dirent *entry; (entry = xreaddir(dir.get()));) { + if (entry->d_type == DT_DIR) { + if (entry->d_name == "."sv || entry->d_name == ".."sv || entry->d_name == ".core"sv) + continue; + + int modfd = xopenat(dfd, entry->d_name, O_RDONLY); + run_finally f([=]{ close(modfd); }); + + if (faccessat(modfd, "remove", F_OK, 0) == 0) { + LOGI("%s: remove\n", entry->d_name); + auto uninstaller = MODULEROOT + "/"s + entry->d_name + "/uninstall.sh"; + if (access(uninstaller.data(), F_OK) == 0) + exec_script(uninstaller.data()); + frm_rf(xdup(modfd)); + unlinkat(dfd, entry->d_name, AT_REMOVEDIR); + continue; + } + + unlinkat(modfd, "update", 0); + + if (faccessat(modfd, "disable", F_OK, 0) != 0) + module_list.emplace_back(entry->d_name); + } + } +} + +void handle_modules() { + prepare_modules(); + collect_modules(); + + // Execute module scripts + LOGI("* Running module post-fs-data scripts\n"); + exec_module_script("post-fs-data", module_list); + + // Recollect modules (module scripts could remove itself) + module_list.clear(); + collect_modules(); + + mount_modules(); +} diff --git a/native/jni/include/daemon.hpp b/native/jni/include/daemon.hpp index f231a178b..c9c03d5a9 100644 --- a/native/jni/include/daemon.hpp +++ b/native/jni/include/daemon.hpp @@ -41,6 +41,7 @@ void unlock_blocks(); void post_fs_data(int client); void late_start(int client); void boot_complete(int client); +void handle_modules(); void remove_modules(); /*************