diff --git a/native/src/core/daemon.cpp b/native/src/core/daemon.cpp index f3c096536..ae9778df4 100644 --- a/native/src/core/daemon.cpp +++ b/native/src/core/daemon.cpp @@ -218,7 +218,7 @@ static void handle_request_sync(int client, int code) { denylist_handler(-1, nullptr); // Restore native bridge property - restore_zygisk_prop(); + MagiskD::Get().restore_zygisk_prop(); write_int(client, 0); diff --git a/native/src/core/daemon.rs b/native/src/core/daemon.rs index e2eac2722..16407f89e 100644 --- a/native/src/core/daemon.rs +++ b/native/src/core/daemon.rs @@ -10,15 +10,15 @@ use crate::mount::{clean_mounts, setup_preinit_dir}; use crate::package::ManagerInfo; use crate::selinux::restore_tmpcon; use crate::su::SuInfo; +use crate::zygisk::ZygiskState; use base::libc::{O_APPEND, O_CLOEXEC, O_RDONLY, O_WRONLY}; use base::{ AtomicArc, BufReadExt, FsPathBuilder, ResultExt, Utf8CStr, Utf8CStrBuf, cstr, error, info, libc, }; use std::fmt::Write as FmtWrite; use std::io::{BufReader, Write}; -use std::os::unix::net::UnixStream; use std::process::Command; -use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Mutex, OnceLock}; // Global magiskd singleton @@ -66,9 +66,8 @@ pub struct MagiskD { pub manager_info: Mutex, boot_stage_lock: Mutex, pub module_list: OnceLock>, - pub zygiskd_sockets: Mutex<(Option, Option)>, pub zygisk_enabled: AtomicBool, - pub zygote_start_count: AtomicU32, + pub zygisk: Mutex, pub cached_su_info: AtomicArc, sdk_int: i32, pub is_emulator: bool, @@ -80,10 +79,6 @@ impl MagiskD { unsafe { MAGISKD.get().unwrap_unchecked() } } - pub fn zygisk_enabled(&self) -> bool { - self.zygisk_enabled.load(Ordering::Acquire) - } - pub fn sdk_int(&self) -> i32 { self.sdk_int } @@ -175,7 +170,7 @@ impl MagiskD { setup_preinit_dir(); self.ensure_manager(); - self.zygisk_reset(true) + self.zygisk.lock().unwrap().reset(true); } pub fn boot_stage_handler(&self, client: i32, code: i32) { @@ -319,7 +314,6 @@ pub fn daemon_entry() { sdk_int, is_emulator, is_recovery, - zygote_start_count: AtomicU32::new(1), ..Default::default() }; MAGISKD.set(magiskd).ok(); diff --git a/native/src/core/include/core.hpp b/native/src/core/include/core.hpp index 3421614fc..920731634 100644 --- a/native/src/core/include/core.hpp +++ b/native/src/core/include/core.hpp @@ -35,11 +35,6 @@ void unlock_blocks(); bool setup_magisk_env(); bool check_key_combo(); -// Zygisk daemon -rust::Str get_zygisk_lib_name(); -void set_zygisk_prop(); -void restore_zygisk_prop(); - // Sockets struct sock_cred : public ucred { std::string context; diff --git a/native/src/core/lib.rs b/native/src/core/lib.rs index 905965475..eb11a0696 100644 --- a/native/src/core/lib.rs +++ b/native/src/core/lib.rs @@ -3,6 +3,7 @@ #![feature(fn_traits)] #![feature(unix_socket_ancillary_data)] #![feature(unix_socket_peek)] +#![feature(default_field_values)] #![allow(clippy::missing_safety_doc)] use crate::ffi::SuRequest; @@ -148,9 +149,6 @@ pub mod ffi { fn uninstall_pkg(apk: Utf8CStrRef); fn update_deny_flags(uid: i32, process: &str, flags: &mut u32); fn initialize_denylist(); - fn get_zygisk_lib_name() -> &'static str; - fn set_zygisk_prop(); - fn restore_zygisk_prop(); fn switch_mnt_ns(pid: i32) -> i32; fn exec_root_shell(client: i32, pid: i32, req: &mut SuRequest, mode: MntNsMode); @@ -228,6 +226,7 @@ pub mod ffi { fn boot_stage_handler(&self, client: i32, code: i32); fn zygisk_handler(&self, client: i32); fn zygisk_reset(&self, restore: bool); + fn restore_zygisk_prop(&self); fn prune_su_access(&self); fn su_daemon_handler(&self, client: i32, cred: &UCred); #[cxx_name = "get_manager"] diff --git a/native/src/core/module.rs b/native/src/core/module.rs index d87e2daaf..7c32e68c1 100644 --- a/native/src/core/module.rs +++ b/native/src/core/module.rs @@ -1,9 +1,6 @@ use crate::consts::{MODULEMNT, MODULEROOT, MODULEUPGRADE, WORKERDIR}; use crate::daemon::MagiskD; -use crate::ffi::{ - ModuleInfo, exec_module_scripts, exec_script, get_magisk_tmp, get_zygisk_lib_name, - load_prop_file, set_zygisk_prop, -}; +use crate::ffi::{ModuleInfo, exec_module_scripts, exec_script, get_magisk_tmp, load_prop_file}; use crate::mount::setup_module_mount; use base::{ DirEntry, Directory, FsPathBuilder, LibcReturn, LoggedResult, OsResultStatic, ResultExt, @@ -502,9 +499,7 @@ fn inject_magisk_bins(system: &mut FsNode, is_emulator: bool) { } } -fn inject_zygisk_bins(system: &mut FsNode) { - let name = get_zygisk_lib_name(); - +fn inject_zygisk_bins(name: &str, system: &mut FsNode) { #[cfg(target_pointer_width = "64")] let has_32_bit = cstr!("/system/bin/linker").exists(); @@ -560,114 +555,6 @@ fn inject_zygisk_bins(system: &mut FsNode) { } } -fn apply_modules(zygisk: bool, module_list: &[ModuleInfo], is_emulator: bool) { - 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, is_emulator); - } - 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(); @@ -877,11 +764,120 @@ impl MagiskD { // Recollect modules (module scripts could remove itself) let modules = collect_modules(zygisk, true); - if zygisk { - set_zygisk_prop(); - } - apply_modules(zygisk, &modules, self.is_emulator); + self.apply_modules(&modules); self.module_list.set(modules).ok(); } + + 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, self.is_emulator); + } + + // Handle zygisk + if self.zygisk_enabled.load(Ordering::Acquire) { + let mut zygisk = self.zygisk.lock().unwrap(); + zygisk.set_prop(); + inject_zygisk_bins(&zygisk.lib_name, &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(); + } + } } diff --git a/native/src/core/zygisk/daemon.rs b/native/src/core/zygisk/daemon.rs index c35af1ee7..8e4eb4dcc 100644 --- a/native/src/core/zygisk/daemon.rs +++ b/native/src/core/zygisk/daemon.rs @@ -1,12 +1,12 @@ use crate::consts::MODULEROOT; use crate::daemon::{MagiskD, to_user_id}; use crate::ffi::{ - ZygiskRequest, ZygiskStateFlags, get_magisk_tmp, restore_zygisk_prop, set_zygisk_prop, update_deny_flags, + ZygiskRequest, ZygiskStateFlags, get_magisk_tmp, get_prop, set_prop, update_deny_flags, }; use crate::socket::{IpcRead, UnixSocketExt}; use base::libc::{O_CLOEXEC, O_CREAT, O_RDONLY, STDOUT_FILENO}; use base::{ - Directory, FsPathBuilder, LoggedError, LoggedResult, ResultExt, WriteExt, cstr, error, + Directory, FsPathBuilder, LoggedError, LoggedResult, ResultExt, Utf8CStr, WriteExt, cstr, fork_dont_care, libc, raw_cstr, warn, }; use std::fmt::Write; @@ -15,6 +15,8 @@ use std::os::unix::net::UnixStream; use std::ptr; use std::sync::atomic::Ordering; +const NBPROP: &Utf8CStr = cstr!("ro.dalvik.vm.native.bridge"); +const ZYGISKLDR: &str = "libzygisk.so"; const UNMOUNT_MASK: u32 = ZygiskStateFlags::ProcessOnDenyList.repr | ZygiskStateFlags::DenyListEnforced.repr; @@ -56,6 +58,105 @@ fn exec_zygiskd(is_64_bit: bool, remote: UnixStream) { } } +#[derive(Default)] +pub struct ZygiskState { + pub lib_name: String, + sockets: (Option, Option), + start_count: u32 = 1, +} + +impl ZygiskState { + fn connect_zygiskd(&mut self, mut client: UnixStream, daemon: &MagiskD) -> LoggedResult<()> { + let is_64_bit: bool = client.read_decodable()?; + let socket = if is_64_bit { + &mut self.sockets.1 + } else { + &mut self.sockets.0 + }; + + if let Some(fd) = socket { + // Make sure the socket is still valid + let mut pfd = libc::pollfd { + fd: fd.as_raw_fd(), + events: 0, + revents: 0, + }; + if unsafe { libc::poll(&mut pfd, 1, 0) } != 0 || pfd.revents != 0 { + // Any revent means error + *socket = None; + } + } + + let socket = if let Some(fd) = socket { + fd + } else { + // Create a new socket pair and fork zygiskd process + let (local, remote) = UnixStream::pair()?; + if fork_dont_care() == 0 { + exec_zygiskd(is_64_bit, remote); + } + *socket = Some(local); + let local = socket.as_mut().unwrap(); + if let Some(module_fds) = daemon.get_module_fds(is_64_bit) { + local.send_fds(&module_fds)?; + } + if local.read_decodable::()? != 0 { + Err(LoggedError::default())?; + } + local + }; + socket.send_fds(&[client.as_raw_fd()])?; + Ok(()) + } + + pub fn reset(&mut self, mut restore: bool) { + if restore { + self.start_count = 1; + } else { + self.sockets = (None, None); + self.start_count += 1; + if self.start_count > 3 { + warn!("zygote crashed too many times, rolling-back"); + restore = true; + } + } + + if restore { + self.restore_prop(); + } else { + self.set_prop(); + } + } + + pub fn set_prop(&mut self) { + if !self.lib_name.is_empty() { + return; + } + let orig = get_prop(NBPROP, false); + self.lib_name = if orig.is_empty() || orig == "0" { + ZYGISKLDR.to_string() + } else { + orig + ZYGISKLDR + }; + set_prop(NBPROP, Utf8CStr::from_string(&mut self.lib_name), false); + // Whether Huawei's Maple compiler is enabled. + // If so, system server will be created by a special Zygote which ignores the native bridge + // and make system server out of our control. Avoid it by disabling. + if get_prop(cstr!("ro.maple.enable"), false) == "1" { + set_prop(cstr!("ro.maple.enable"), cstr!("0"), false); + } + } + + fn restore_prop(&mut self) { + let mut orig = "0".to_string(); + if self.lib_name.len() > ZYGISKLDR.len() { + orig = self.lib_name[ZYGISKLDR.len()..].to_string(); + } + set_prop(NBPROP, Utf8CStr::from_string(&mut orig), false); + self.lib_name.clear(); + } +} + impl MagiskD { pub fn zygisk_handler(&self, client: i32) { let mut client = unsafe { UnixStream::from_raw_fd(client) }; @@ -65,35 +166,18 @@ impl MagiskD { }; match code { ZygiskRequest::GetInfo => self.get_process_info(client)?, - ZygiskRequest::ConnectCompanion => self.connect_zygiskd(client), + ZygiskRequest::ConnectCompanion => self + .zygisk + .lock() + .unwrap() + .connect_zygiskd(client, self) + .log_with_msg(|w| w.write_str("zygiskd startup error"))?, ZygiskRequest::GetModDir => self.get_mod_dir(client)?, _ => {} } }; } - pub fn zygisk_reset(&self, mut restore: bool) { - if !self.zygisk_enabled.load(Ordering::Acquire) { - return; - } - - if restore { - self.zygote_start_count.store(1, Ordering::Release); - } else { - *self.zygiskd_sockets.lock().unwrap() = (None, None); - if self.zygote_start_count.fetch_add(1, Ordering::AcqRel) > 3 { - warn!("zygote crashes too many times, rolling-back"); - restore = true; - } - } - - if restore { - restore_zygisk_prop(); - } else { - set_zygisk_prop(); - } - } - fn get_module_fds(&self, is_64_bit: bool) -> Option> { self.module_list.get().map(|module_list| { module_list @@ -107,54 +191,6 @@ impl MagiskD { }) } - fn connect_zygiskd(&self, mut client: UnixStream) { - let mut zygiskd_sockets = self.zygiskd_sockets.lock().unwrap(); - let result: LoggedResult<()> = try { - let is_64_bit: bool = client.read_decodable()?; - let socket = if is_64_bit { - &mut zygiskd_sockets.1 - } else { - &mut zygiskd_sockets.0 - }; - - if let Some(fd) = socket { - // Make sure the socket is still valid - let mut pfd = libc::pollfd { - fd: fd.as_raw_fd(), - events: 0, - revents: 0, - }; - if unsafe { libc::poll(&mut pfd, 1, 0) } != 0 || pfd.revents != 0 { - // Any revent means error - *socket = None; - } - } - - let socket = if let Some(fd) = socket { - fd - } else { - // Create a new socket pair and fork zygiskd process - let (local, remote) = UnixStream::pair()?; - if fork_dont_care() == 0 { - exec_zygiskd(is_64_bit, remote); - } - *socket = Some(local); - let local = socket.as_mut().unwrap(); - if let Some(module_fds) = self.get_module_fds(is_64_bit) { - local.send_fds(&module_fds)?; - } - if local.read_decodable::()? != 0 { - Err(LoggedError::default())?; - } - local - }; - socket.send_fds(&[client.as_raw_fd()])?; - }; - if result.is_err() { - error!("zygiskd startup error"); - } - } - fn get_process_info(&self, mut client: UnixStream) -> LoggedResult<()> { let uid: i32 = client.read_decodable()?; let process: String = client.read_decodable()?; @@ -214,3 +250,18 @@ impl MagiskD { Ok(()) } } + +// FFI to C++ +impl MagiskD { + pub fn zygisk_enabled(&self) -> bool { + self.zygisk_enabled.load(Ordering::Acquire) + } + + pub fn zygisk_reset(&self, restore: bool) { + self.zygisk.lock().unwrap().reset(restore); + } + + pub fn restore_zygisk_prop(&self) { + self.zygisk.lock().unwrap().restore_prop(); + } +} diff --git a/native/src/core/zygisk/entry.cpp b/native/src/core/zygisk/entry.cpp index ec20f0f8b..15cdf9bff 100644 --- a/native/src/core/zygisk/entry.cpp +++ b/native/src/core/zygisk/entry.cpp @@ -10,8 +10,6 @@ using namespace std; -static string zygisk_lib_name; - static void zygiskd(int socket) { if (getuid() != 0 || fcntl(socket, F_GETFD) < 0) exit(-1); @@ -109,33 +107,3 @@ extern "C" [[maybe_unused]] NativeBridgeCallbacks NativeBridgeItf { return false; }, }; - -rust::Str get_zygisk_lib_name() { - return zygisk_lib_name; -} - -void set_zygisk_prop() { - if (!zygisk_lib_name.empty()) - return; - string native_bridge_orig = get_prop(NBPROP); - if (native_bridge_orig.empty()) { - native_bridge_orig = "0"; - } - zygisk_lib_name = native_bridge_orig == "0" ? ZYGISKLDR : ZYGISKLDR + native_bridge_orig; - set_prop(NBPROP, zygisk_lib_name.data()); - // Whether Huawei's Maple compiler is enabled. - // If so, system server will be created by a special Zygote which ignores the native bridge - // and make system server out of our control. Avoid it by disabling. - if (get_prop("ro.maple.enable") == "1") { - set_prop("ro.maple.enable", "0"); - } -} - -void restore_zygisk_prop() { - string native_bridge_orig = "0"; - if (zygisk_lib_name.length() > strlen(ZYGISKLDR)) { - native_bridge_orig = zygisk_lib_name.substr(strlen(ZYGISKLDR)); - } - set_prop(NBPROP, native_bridge_orig.data()); - zygisk_lib_name.clear(); -} diff --git a/native/src/core/zygisk/mod.rs b/native/src/core/zygisk/mod.rs index aca428a64..bfd2e148b 100644 --- a/native/src/core/zygisk/mod.rs +++ b/native/src/core/zygisk/mod.rs @@ -1,3 +1,3 @@ mod daemon; -pub use daemon::zygisk_should_load_module; +pub use daemon::{ZygiskState, zygisk_should_load_module};