From 589a270b8dfc66b0481dba9002d05cefacad4b35 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Sun, 3 Aug 2025 20:08:34 -0700 Subject: [PATCH] Migrate disable/remove modules to Rust --- native/src/core/daemon.rs | 6 +- native/src/core/include/core.hpp | 5 +- native/src/core/include/resetprop.hpp | 13 +- native/src/core/lib.rs | 48 ++--- native/src/core/module.cpp | 38 ---- native/src/core/module.rs | 263 ++++++++++++++---------- native/src/core/mount.rs | 3 +- native/src/core/resetprop/persist.rs | 2 +- native/src/core/resetprop/resetprop.cpp | 4 +- 9 files changed, 184 insertions(+), 198 deletions(-) diff --git a/native/src/core/daemon.rs b/native/src/core/daemon.rs index e8643a58b..e2eac2722 100644 --- a/native/src/core/daemon.rs +++ b/native/src/core/daemon.rs @@ -1,15 +1,15 @@ use crate::consts::{MAGISK_FULL_VER, MAGISK_PROC_CON, MAIN_CONFIG, ROOTMNT, ROOTOVL, SECURE_DIR}; use crate::db::Sqlite3; use crate::ffi::{ - DbEntryKey, ModuleInfo, RequestCode, check_key_combo, disable_modules, exec_common_scripts, - exec_module_scripts, get_magisk_tmp, initialize_denylist, setup_magisk_env, + DbEntryKey, ModuleInfo, RequestCode, check_key_combo, exec_common_scripts, exec_module_scripts, + get_magisk_tmp, get_prop, initialize_denylist, set_prop, setup_magisk_env, }; use crate::logging::{magisk_logging, setup_logfile, start_log_daemon}; +use crate::module::disable_modules; use crate::mount::{clean_mounts, setup_preinit_dir}; use crate::package::ManagerInfo; use crate::selinux::restore_tmpcon; use crate::su::SuInfo; -use crate::{get_prop, set_prop}; use base::libc::{O_APPEND, O_CLOEXEC, O_RDONLY, O_WRONLY}; use base::{ AtomicArc, BufReadExt, FsPathBuilder, ResultExt, Utf8CStr, Utf8CStrBuf, cstr, error, info, libc, diff --git a/native/src/core/include/core.hpp b/native/src/core/include/core.hpp index c467abc24..cb18a0f8f 100644 --- a/native/src/core/include/core.hpp +++ b/native/src/core/include/core.hpp @@ -92,10 +92,6 @@ void exec_task(std::function &&task); // Daemon handlers void denylist_handler(int client, const sock_cred *cred); -// Module stuffs -void disable_modules(); -void remove_modules(); - // Scripting void install_apk(rust::Utf8CStr apk); void uninstall_pkg(rust::Utf8CStr pkg); @@ -125,3 +121,4 @@ static inline rust::Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); } static inline rust::String resolve_preinit_dir_rs(rust::Utf8CStr base_dir) { return resolve_preinit_dir(base_dir.c_str()); } +static inline void exec_script_rs(rust::Utf8CStr script) { exec_script(script.data()); } diff --git a/native/src/core/include/resetprop.hpp b/native/src/core/include/resetprop.hpp index 88e1d3e24..81244f867 100644 --- a/native/src/core/include/resetprop.hpp +++ b/native/src/core/include/resetprop.hpp @@ -20,12 +20,19 @@ private: }; // System properties -rust::String get_prop_rs(const char *name, bool persist); std::string get_prop(const char *name, bool persist = false); int delete_prop(const char *name, bool persist = false); int set_prop(const char *name, const char *value, bool skip_svc = false); void load_prop_file(const char *filename, bool skip_svc = false); -static inline void prop_cb_exec(prop_cb &cb, const char *name, const char *value, uint32_t serial) { - cb.exec(name, value, serial); +// Rust bindings +rust::String get_prop_rs(rust::Utf8CStr name, bool persist); +static inline int set_prop_rs(rust::Utf8CStr name, rust::Utf8CStr value, bool skip_svc) { + return set_prop(name.data(), value.data(), skip_svc); } +static inline void load_prop_file_rs(rust::Utf8CStr filename, bool skip_svc) { + load_prop_file(filename.data(), skip_svc); +} +static inline void prop_cb_exec(prop_cb &cb, rust::Utf8CStr name, rust::Utf8CStr value, uint32_t serial) { + cb.exec(name.data(), value.data(), serial); +} \ No newline at end of file diff --git a/native/src/core/lib.rs b/native/src/core/lib.rs index 1ca0ec307..7e9e8e251 100644 --- a/native/src/core/lib.rs +++ b/native/src/core/lib.rs @@ -7,11 +7,12 @@ use crate::ffi::SuRequest; use crate::socket::Encodable; -use base::{Utf8CStr, libc}; +use base::libc; use cxx::{ExternType, type_id}; use daemon::{MagiskD, daemon_entry}; use derive::Decodable; use logging::{android_logging, setup_logfile, zygisk_close_logd, zygisk_get_logd, zygisk_logging}; +use module::remove_modules; use mount::{find_preinit_device, revert_unmount}; use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop}; use selinux::{lgetfilecon, lsetfilecon, restorecon, setfilecon}; @@ -133,23 +134,6 @@ pub mod ffi { request: &'a SuRequest, } - extern "C++" { - include!("include/resetprop.hpp"); - - #[cxx_name = "prop_cb"] - type PropCb; - unsafe fn get_prop_rs(name: *const c_char, persist: bool) -> String; - #[cxx_name = "set_prop"] - unsafe fn set_prop_rs(name: *const c_char, value: *const c_char, skip_svc: bool) -> i32; - unsafe fn prop_cb_exec( - cb: Pin<&mut PropCb>, - name: *const c_char, - value: *const c_char, - serial: u32, - ); - unsafe fn load_prop_file(filename: *const c_char, skip_svc: bool); - } - unsafe extern "C++" { #[namespace = "rust"] #[cxx_name = "Utf8CStr"] @@ -165,7 +149,8 @@ pub mod ffi { fn resolve_preinit_dir(base_dir: Utf8CStrRef) -> String; fn setup_magisk_env() -> bool; fn check_key_combo() -> bool; - fn disable_modules(); + #[cxx_name = "exec_script_rs"] + fn exec_script(script: Utf8CStrRef); fn exec_common_scripts(stage: Utf8CStrRef); fn exec_module_scripts(state: Utf8CStrRef, modules: &Vec); fn install_apk(apk: Utf8CStrRef); @@ -193,6 +178,18 @@ pub mod ffi { fn get_text(self: &DbValues, index: i32) -> &str; fn bind_text(self: Pin<&mut DbStatement>, index: i32, val: &str) -> i32; fn bind_int64(self: Pin<&mut DbStatement>, index: i32, val: i64) -> i32; + + include!("include/resetprop.hpp"); + + #[cxx_name = "prop_cb"] + type PropCb; + #[cxx_name = "get_prop_rs"] + fn get_prop(name: Utf8CStrRef, persist: bool) -> String; + #[cxx_name = "set_prop_rs"] + fn set_prop(name: Utf8CStrRef, value: Utf8CStrRef, skip_svc: bool) -> i32; + #[cxx_name = "load_prop_file_rs"] + fn load_prop_file(filename: Utf8CStrRef, skip_svc: bool); + fn prop_cb_exec(cb: Pin<&mut PropCb>, name: Utf8CStrRef, value: Utf8CStrRef, serial: u32); } extern "Rust" { @@ -203,6 +200,7 @@ pub mod ffi { fn setup_logfile(); fn find_preinit_device() -> String; fn revert_unmount(pid: i32); + fn remove_modules(); fn zygisk_should_load_module(flags: u32) -> bool; unsafe fn persist_get_prop(name: Utf8CStrRef, prop_cb: Pin<&mut PropCb>); unsafe fn persist_get_props(prop_cb: Pin<&mut PropCb>); @@ -277,15 +275,3 @@ impl SuRequest { } } } - -pub fn get_prop(name: &Utf8CStr, persist: bool) -> String { - unsafe { ffi::get_prop_rs(name.as_ptr(), persist) } -} - -pub fn set_prop(name: &Utf8CStr, value: &Utf8CStr, skip_svc: bool) -> bool { - unsafe { ffi::set_prop_rs(name.as_ptr(), value.as_ptr(), skip_svc) == 0 } -} - -pub fn load_prop_file(filename: &Utf8CStr, skip_svc: bool) { - unsafe { ffi::load_prop_file(filename.as_ptr(), skip_svc) }; -} diff --git a/native/src/core/module.cpp b/native/src/core/module.cpp index c54b82ae8..a6967bcec 100644 --- a/native/src/core/module.cpp +++ b/native/src/core/module.cpp @@ -117,41 +117,3 @@ rust::Vec MagiskD::load_modules() const noexcept { } return list; } - -static int check_rules_dir(char *buf, size_t sz) { - int off = ssprintf(buf, sz, "%s/" PREINITMIRR, get_magisk_tmp()); - struct stat st1{}; - struct stat st2{}; - if (xstat(buf, &st1) < 0 || xstat(MODULEROOT, &st2) < 0) - return 0; - if (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) - return 0; - return off; -} - -void disable_modules() { - char buf[4096]; - int off = check_rules_dir(buf, sizeof(buf)); - foreach_module([&](int, dirent *entry, int modfd) { - close(xopenat(modfd, "disable", O_RDONLY | O_CREAT | O_CLOEXEC, 0)); - if (off) { - ssprintf(buf + off, sizeof(buf) - off, "/%s/sepolicy.rule", entry->d_name); - unlink(buf); - } - }); -} - -void remove_modules() { - char buf[4096]; - int off = check_rules_dir(buf, sizeof(buf)); - foreach_module([&](int, dirent *entry, int) { - auto uninstaller = MODULEROOT + "/"s + entry->d_name + "/uninstall.sh"; - if (access(uninstaller.data(), F_OK) == 0) - exec_script(uninstaller.data()); - if (off) { - ssprintf(buf + off, sizeof(buf) - off, "/%s/sepolicy.rule", entry->d_name); - unlink(buf); - } - }); - rm_rf(MODULEROOT); -} diff --git a/native/src/core/module.rs b/native/src/core/module.rs index f340b7a05..894ada01e 100644 --- a/native/src/core/module.rs +++ b/native/src/core/module.rs @@ -1,11 +1,11 @@ use crate::consts::{MODULEMNT, MODULEROOT, MODULEUPGRADE, WORKERDIR}; use crate::daemon::MagiskD; -use crate::ffi::{ModuleInfo, get_magisk_tmp, get_zygisk_lib_name}; -use crate::load_prop_file; +use crate::ffi::{ModuleInfo, exec_script, get_magisk_tmp, get_zygisk_lib_name, load_prop_file}; use crate::mount::setup_module_mount; use base::{ - Directory, FsPathBuilder, LibcReturn, LoggedResult, OsResultStatic, ResultExt, Utf8CStr, - Utf8CStrBuf, Utf8CString, WalkResult, clone_attr, cstr, debug, error, info, libc, warn, + DirEntry, Directory, FsPathBuilder, LibcReturn, LoggedResult, OsResultStatic, ResultExt, + Utf8CStr, Utf8CStrBuf, Utf8CString, WalkResult, clone_attr, cstr, debug, error, info, libc, + warn, }; use libc::{AT_REMOVEDIR, MS_RDONLY, O_CLOEXEC, O_CREAT, O_RDONLY}; use std::collections::BTreeMap; @@ -550,11 +550,119 @@ fn inject_zygisk_bins(system: &mut FsNode) { } } -fn prepare_modules() -> LoggedResult<()> { +fn apply_modules(zygisk: bool, module_list: &[ModuleInfo]) { + let mut system = FsNode::new_dir(); + + // Build all the base "prefix" paths + let mut root = cstr::buf::default().join_path("/"); + + let mut module_dir = cstr::buf::default().join_path(MODULEROOT); + + let mut module_mnt = cstr::buf::default() + .join_path(get_magisk_tmp()) + .join_path(MODULEMNT); + + let mut worker = cstr::buf::default() + .join_path(get_magisk_tmp()) + .join_path(WORKERDIR); + + // Create a collection of all relevant paths + let mut root_paths = FilePaths { + real: PathTracker::from(&mut root), + worker: PathTracker::from(&mut worker), + module_mnt: PathTracker::from(&mut module_mnt), + module_root: PathTracker::from(&mut module_dir), + }; + + // Step 1: Create virtual filesystem tree + // + // In this step, there is zero logic applied during tree construction; we simply collect and + // record the union of all module filesystem trees under each of their /system directory. + + for info in module_list { + let mut module_paths = root_paths.append(&info.name); + { + // Read props + let prop = module_paths.append("system.prop"); + if prop.module().exists() { + // Do NOT go through property service as it could cause boot lock + load_prop_file(prop.module(), true); + } + } + { + // Check whether skip mounting + let skip = module_paths.append("skip_mount"); + if skip.module().exists() { + continue; + } + } + { + // Double check whether the system folder exists + let sys = module_paths.append("system"); + if sys.module().exists() { + info!("{}: loading module files", &info.name); + system.collect(sys).log_ok(); + } + } + } + + // Step 2: Inject custom files + // + // Magisk provides some built-in functionality that requires augmenting the filesystem. + // We expose several cmdline tools (e.g. su) into PATH, and the zygisk shared library + // has to also be added into the default LD_LIBRARY_PATH for code injection. + // We directly inject file nodes into the virtual filesystem tree we built in the previous + // step, treating Magisk just like a special "module". + + if get_magisk_tmp() != "/sbin" || get_path_env().split(":").all(|s| s != "/sbin") { + inject_magisk_bins(&mut system); + } + if zygisk { + inject_zygisk_bins(&mut system); + } + + // Step 3: Extract all supported read-only partition roots + // + // For simplicity and backwards compatibility on older Android versions, when constructing + // Magisk modules, we always assume that there is only a single read-only partition mounted + // at /system. However, on modern Android there are actually multiple read-only partitions + // mounted at their respective paths. We need to extract these subtrees out of the main + // tree and treat them as individual trees. + + let mut roots = BTreeMap::new(); /* mapOf(partition_name -> FsNode) */ + if let FsNode::Directory { children } = &mut system { + for dir in SECONDARY_READ_ONLY_PARTITIONS { + // Only treat these nodes as root iff it is actually a directory in rootdir + if let Ok(attr) = dir.get_attr() + && attr.is_dir() + { + let name = dir.trim_start_matches('/'); + if let Some(root) = children.remove(name) { + roots.insert(name, root); + } + } + } + } + roots.insert("system", system); + + for (dir, mut root) in roots { + // Step 4: Convert virtual filesystem tree into concrete operations + // + // Compare the virtual filesystem tree we constructed against the real filesystem + // structure on-device to generate a series of "operations". + // The "core" of the logic is to decide which directories need to be rebuilt in the + // tmpfs worker directory, and real sub-nodes need to be mirrored inside it. + + let path = root_paths.append(dir); + root.commit(path, true).log_ok(); + } +} + +fn upgrade_modules() -> LoggedResult<()> { let mut upgrade = Directory::open(cstr!(MODULEUPGRADE))?; let ufd = upgrade.as_raw_fd(); let root = Directory::open(cstr!(MODULEROOT))?; - while let Some(ref e) = upgrade.read()? { + while let Some(e) = upgrade.read()? { if !e.is_dir() { continue; } @@ -590,120 +698,47 @@ fn prepare_modules() -> LoggedResult<()> { Ok(()) } -impl MagiskD { - fn apply_modules(&self, module_list: &[ModuleInfo]) { - let mut system = FsNode::new_dir(); - - // Build all the base "prefix" paths - let mut root = cstr::buf::default().join_path("/"); - - let mut module_dir = cstr::buf::default().join_path(MODULEROOT); - - let mut module_mnt = cstr::buf::default() - .join_path(get_magisk_tmp()) - .join_path(MODULEMNT); - - let mut worker = cstr::buf::default() - .join_path(get_magisk_tmp()) - .join_path(WORKERDIR); - - // Create a collection of all relevant paths - let mut root_paths = FilePaths { - real: PathTracker::from(&mut root), - worker: PathTracker::from(&mut worker), - module_mnt: PathTracker::from(&mut module_mnt), - module_root: PathTracker::from(&mut module_dir), - }; - - // Step 1: Create virtual filesystem tree - // - // In this step, there is zero logic applied during tree construction; we simply collect and - // record the union of all module filesystem trees under each of their /system directory. - - for info in module_list { - let mut module_paths = root_paths.append(&info.name); - { - // Read props - let prop = module_paths.append("system.prop"); - if prop.module().exists() { - // Do NOT go through property service as it could cause boot lock - load_prop_file(prop.module(), true); - } - } - { - // Check whether skip mounting - let skip = module_paths.append("skip_mount"); - if skip.module().exists() { - continue; - } - } - { - // Double check whether the system folder exists - let sys = module_paths.append("system"); - if sys.module().exists() { - info!("{}: loading module files", &info.name); - system.collect(sys).log_ok(); - } - } - } - - // Step 2: Inject custom files - // - // Magisk provides some built-in functionality that requires augmenting the filesystem. - // We expose several cmdline tools (e.g. su) into PATH, and the zygisk shared library - // has to also be added into the default LD_LIBRARY_PATH for code injection. - // We directly inject file nodes into the virtual filesystem tree we built in the previous - // step, treating Magisk just like a special "module". - - if get_magisk_tmp() != "/sbin" || get_path_env().split(":").all(|s| s != "/sbin") { - inject_magisk_bins(&mut system); - } - if self.zygisk_enabled() { - inject_zygisk_bins(&mut system); - } - - // Step 3: Extract all supported read-only partition roots - // - // For simplicity and backwards compatibility on older Android versions, when constructing - // Magisk modules, we always assume that there is only a single read-only partition mounted - // at /system. However, on modern Android there are actually multiple read-only partitions - // mounted at their respective paths. We need to extract these subtrees out of the main - // tree and treat them as individual trees. - - let mut roots = BTreeMap::new(); /* mapOf(partition_name -> FsNode) */ - if let FsNode::Directory { children } = &mut system { - for dir in SECONDARY_READ_ONLY_PARTITIONS { - // Only treat these nodes as root iff it is actually a directory in rootdir - if let Ok(attr) = dir.get_attr() - && attr.is_dir() - { - let name = dir.trim_start_matches('/'); - if let Some(root) = children.remove(name) { - roots.insert(name, root); - } - } - } - } - roots.insert("system", system); - - for (dir, mut root) in roots { - // Step 4: Convert virtual filesystem tree into concrete operations - // - // Compare the virtual filesystem tree we constructed against the real filesystem - // structure on-device to generate a series of "operations". - // The "core" of the logic is to decide which directories need to be rebuilt in the - // tmpfs worker directory, and real sub-nodes need to be mirrored inside it. - - let path = root_paths.append(dir); - root.commit(path, true).log_ok(); +fn for_each_module(func: impl Fn(&DirEntry) -> LoggedResult<()>) -> LoggedResult<()> { + let mut root = Directory::open(cstr!(MODULEROOT))?; + while let Some(ref e) = root.read()? { + if e.is_dir() && e.name() != ".core" { + func(e)?; } } + Ok(()) +} +pub fn disable_modules() { + for_each_module(|e| { + let dir = e.open_as_dir()?; + dir.open_as_file_at(cstr!("disable"), O_RDONLY | O_CREAT | O_CLOEXEC, 0)?; + Ok(()) + }) + .log_ok(); +} + +pub fn remove_modules() { + for_each_module(|e| { + let dir = e.open_as_dir()?; + if dir.contains_path(cstr!("uninstall.sh")) { + let script = cstr::buf::default() + .join_path(MODULEROOT) + .join_path(e.name()) + .join_path("uninstall.sh"); + exec_script(&script); + } + Ok(()) + }) + .log_ok(); + cstr!(MODULEROOT).remove_all().log_ok(); +} + +impl MagiskD { pub fn handle_modules(&self) { setup_module_mount(); - prepare_modules().ok(); + upgrade_modules().ok(); let modules = self.load_modules(); - self.apply_modules(&modules); + apply_modules(self.zygisk_enabled(), &modules); self.module_list.set(modules).ok(); } } diff --git a/native/src/core/mount.rs b/native/src/core/mount.rs index d0021d709..b568b05bd 100644 --- a/native/src/core/mount.rs +++ b/native/src/core/mount.rs @@ -12,8 +12,7 @@ use base::{ }; use crate::consts::{MODULEMNT, MODULEROOT, PREINITDEV, PREINITMIRR, WORKERDIR}; -use crate::ffi::{get_magisk_tmp, resolve_preinit_dir, switch_mnt_ns}; -use crate::get_prop; +use crate::ffi::{get_magisk_tmp, get_prop, resolve_preinit_dir, switch_mnt_ns}; pub fn setup_preinit_dir() { let magisk_tmp = get_magisk_tmp(); diff --git a/native/src/core/resetprop/persist.rs b/native/src/core/resetprop/persist.rs index 86eab0d1b..89f106c27 100644 --- a/native/src/core/resetprop/persist.rs +++ b/native/src/core/resetprop/persist.rs @@ -29,7 +29,7 @@ trait PropCbExec { impl PropCbExec for Pin<&mut PropCb> { fn exec(&mut self, name: &Utf8CStr, value: &Utf8CStr) { - unsafe { prop_cb_exec(self.as_mut(), name.as_ptr(), value.as_ptr(), u32::MAX) } + prop_cb_exec(self.as_mut(), name, value, u32::MAX) } } diff --git a/native/src/core/resetprop/resetprop.cpp b/native/src/core/resetprop/resetprop.cpp index 160c70c43..79aeafc84 100644 --- a/native/src/core/resetprop/resetprop.cpp +++ b/native/src/core/resetprop/resetprop.cpp @@ -429,8 +429,8 @@ static StringType get_prop_impl(const char *name, bool persist) { return get_prop(name, flags); } -rust::String get_prop_rs(const char *name, bool persist) { - return get_prop_impl(name, persist); +rust::String get_prop_rs(rust::Utf8CStr name, bool persist) { + return get_prop_impl(name.data(), persist); } string get_prop(const char *name, bool persist) {