From 15a605765ccf33e172be43c41459e08c48369268 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Thu, 30 Jan 2025 00:47:01 +0800 Subject: [PATCH] Fully implement daemon side of Zygisk in Rust --- native/src/base/lib.rs | 2 + native/src/core/bootstages.cpp | 8 +- native/src/core/daemon.cpp | 8 +- native/src/core/daemon.rs | 20 ++++- native/src/core/deny/utils.cpp | 2 +- native/src/core/include/core.hpp | 1 - native/src/core/include/daemon.hpp | 2 + native/src/core/lib.rs | 10 +-- native/src/core/module.cpp | 11 +-- native/src/core/scripting.cpp | 2 +- native/src/core/zygisk/daemon.rs | 120 +++++++++++++++++++++++++++-- native/src/core/zygisk/entry.cpp | 75 ++---------------- 12 files changed, 160 insertions(+), 101 deletions(-) diff --git a/native/src/base/lib.rs b/native/src/base/lib.rs index e418a0042..585d7e464 100644 --- a/native/src/base/lib.rs +++ b/native/src/base/lib.rs @@ -8,6 +8,7 @@ use num_traits::FromPrimitive; pub use cstr::*; use cxx_extern::*; +pub use ffi::fork_dont_care; pub use files::*; pub use logging::*; pub use misc::*; @@ -42,6 +43,7 @@ pub mod ffi { type Utf8CStrRef<'a> = &'a crate::cstr::Utf8CStr; fn mut_u8_patch(buf: &mut [u8], from: &[u8], to: &[u8]) -> Vec; + fn fork_dont_care() -> i32; } extern "Rust" { diff --git a/native/src/core/bootstages.cpp b/native/src/core/bootstages.cpp index 5dfd97ec6..c21cd1c70 100644 --- a/native/src/core/bootstages.cpp +++ b/native/src/core/bootstages.cpp @@ -13,8 +13,6 @@ using namespace std; -bool zygisk_enabled = false; - /********* * Setup * *********/ @@ -175,10 +173,6 @@ bool MagiskD::post_fs_data() const noexcept { } exec_common_scripts("post-fs-data"); - zygisk_enabled = get_db_setting(DbEntryKey::ZygiskConfig); - initialize_denylist(); - setup_mounts(); - handle_modules(); return false; } @@ -206,5 +200,5 @@ void MagiskD::boot_complete() const noexcept { // Ensure manager exists get_manager(0, nullptr, true); - reset_zygisk(true); + zygisk_reset(true); } diff --git a/native/src/core/daemon.cpp b/native/src/core/daemon.cpp index e4e597fc9..c84e0e6fd 100644 --- a/native/src/core/daemon.cpp +++ b/native/src/core/daemon.cpp @@ -142,13 +142,15 @@ static void handle_request_async(int client, int code, const sock_cred &cred) { case +RequestCode::SUPERUSER: su_daemon_handler(client, &cred); break; - case +RequestCode::ZYGOTE_RESTART: + case +RequestCode::ZYGOTE_RESTART: { LOGI("** zygote restarted\n"); - MagiskD().prune_su_access(); + auto &daemon = MagiskD(); + daemon.prune_su_access(); scan_deny_apps(); - reset_zygisk(false); + daemon.zygisk_reset(false); close(client); break; + } case +RequestCode::SQLITE_CMD: MagiskD().db_exec(client); break; diff --git a/native/src/core/daemon.rs b/native/src/core/daemon.rs index cc86ffbae..f38b02f08 100644 --- a/native/src/core/daemon.rs +++ b/native/src/core/daemon.rs @@ -1,8 +1,9 @@ use crate::consts::{MAGISK_FULL_VER, MAIN_CONFIG}; use crate::db::Sqlite3; -use crate::ffi::{get_magisk_tmp, ModuleInfo, RequestCode}; +use crate::ffi::{get_magisk_tmp, initialize_denylist, DbEntryKey, ModuleInfo, RequestCode}; use crate::get_prop; use crate::logging::{magisk_logging, start_log_daemon}; +use crate::mount::setup_mounts; use crate::package::ManagerInfo; use base::libc::{O_CLOEXEC, O_RDONLY}; use base::{ @@ -17,6 +18,7 @@ use std::io::{BufReader, ErrorKind, IoSlice, IoSliceMut, Read, Write}; use std::mem::ManuallyDrop; use std::os::fd::{FromRawFd, IntoRawFd, OwnedFd, RawFd}; use std::os::unix::net::{AncillaryData, SocketAncillary, UnixStream}; +use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::{Mutex, OnceLock}; // Global magiskd singleton @@ -64,6 +66,9 @@ 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, sdk_int: i32, pub is_emulator: bool, is_recovery: bool, @@ -74,6 +79,10 @@ impl MagiskD { self.is_recovery } + pub fn zygisk_enabled(&self) -> bool { + self.zygisk_enabled.load(Ordering::Acquire) + } + pub fn sdk_int(&self) -> i32 { self.sdk_int } @@ -139,6 +148,14 @@ impl MagiskD { if check_data() && !state.contains(BootState::PostFsDataDone) { if self.post_fs_data() { state.set(BootState::SafeMode); + } else { + self.zygisk_enabled.store( + self.get_db_setting(DbEntryKey::ZygiskConfig) != 0, + Ordering::Release, + ); + initialize_denylist(); + setup_mounts(); + self.handle_modules(); } state.set(BootState::PostFsDataDone); } @@ -215,6 +232,7 @@ 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/deny/utils.cpp b/native/src/core/deny/utils.cpp index 6c05408d0..84585f947 100644 --- a/native/src/core/deny/utils.cpp +++ b/native/src/core/deny/utils.cpp @@ -370,7 +370,7 @@ int enable_deny() { denylist_enforced = true; - if (!zygisk_enabled) { + if (!MagiskD().zygisk_enabled()) { if (new_daemon_thread(&logcat)) { denylist_enforced = false; return DenyResponse::ERROR; diff --git a/native/src/core/include/core.hpp b/native/src/core/include/core.hpp index d058df893..275ee7f9b 100644 --- a/native/src/core/include/core.hpp +++ b/native/src/core/include/core.hpp @@ -26,7 +26,6 @@ enum class RespondCode : int { END }; -extern bool zygisk_enabled; extern std::string native_bridge; void reset_zygisk(bool restore); diff --git a/native/src/core/include/daemon.hpp b/native/src/core/include/daemon.hpp index 7a2cf7c76..c47a49106 100644 --- a/native/src/core/include/daemon.hpp +++ b/native/src/core/include/daemon.hpp @@ -6,6 +6,8 @@ const char *get_magisk_tmp(); void install_apk(rust::Utf8CStr apk); void uninstall_pkg(rust::Utf8CStr pkg); void update_deny_flags(int uid, rust::Str process, uint32_t &flags); +void initialize_denylist(); +void restore_zygisk_prop(); // Rust bindings static inline rust::Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); } diff --git a/native/src/core/lib.rs b/native/src/core/lib.rs index cc8b4aaed..39b1b5b3a 100644 --- a/native/src/core/lib.rs +++ b/native/src/core/lib.rs @@ -81,6 +81,8 @@ pub mod ffi { fn install_apk(apk: Utf8CStrRef); fn uninstall_pkg(apk: Utf8CStrRef); fn update_deny_flags(uid: i32, process: &str, flags: &mut u32); + fn initialize_denylist(); + fn restore_zygisk_prop(); fn switch_mnt_ns(pid: i32) -> i32; } @@ -186,7 +188,6 @@ pub mod ffi { fn zygisk_close_logd(); fn zygisk_get_logd() -> i32; fn setup_logfile(); - fn setup_mounts(); fn clean_mounts(); fn find_preinit_device() -> String; fn revert_unmount(pid: i32); @@ -209,15 +210,16 @@ pub mod ffi { type MagiskD; fn is_recovery(&self) -> bool; fn sdk_int(&self) -> i32; + fn zygisk_enabled(&self) -> bool; fn boot_stage_handler(&self, client: i32, code: i32); fn zygisk_handler(&self, client: i32); + fn zygisk_reset(&self, restore: bool); fn preserve_stub_apk(&self); fn prune_su_access(&self); #[cxx_name = "get_manager"] unsafe fn get_manager_for_cxx(&self, user: i32, ptr: *mut CxxString, install: bool) -> i32; fn set_module_list(&self, module_list: Vec); fn module_list(&self) -> &Vec; - fn get_module_fds(&self, is_64_bit: bool) -> Vec; #[cxx_name = "get_db_settings"] fn get_db_settings_for_cxx(&self, cfg: &mut DbSettings) -> bool; @@ -240,11 +242,9 @@ pub mod ffi { #[allow(dead_code)] fn reboot(self: &MagiskD); fn post_fs_data(self: &MagiskD) -> bool; + fn handle_modules(self: &MagiskD); fn late_start(self: &MagiskD); fn boot_complete(self: &MagiskD); - #[allow(dead_code)] - fn handle_modules(self: &MagiskD); - fn connect_zygiskd(self: &MagiskD, client: i32); } } diff --git a/native/src/core/module.cpp b/native/src/core/module.cpp index d246abf17..bd89ffac9 100644 --- a/native/src/core/module.cpp +++ b/native/src/core/module.cpp @@ -269,7 +269,7 @@ static void inject_zygisk_libs(root_node *system) { } } -static void load_modules(const rust::Vec &module_list) { +static void load_modules(bool zygisk_enabled, const rust::Vec &module_list) { node_entry::module_mnt = get_magisk_tmp() + "/"s MODULEMNT "/"; auto root = make_unique(""); @@ -392,7 +392,7 @@ static void foreach_module(Func fn) { } } -static rust::Vec collect_modules(bool open_zygisk) { +static rust::Vec collect_modules(bool zygisk_enabled, bool open_zygisk) { rust::Vec modules; foreach_module([&](int dfd, dirent *entry, int modfd) { if (faccessat(modfd, "remove", F_OK, 0) == 0) { @@ -475,11 +475,12 @@ static rust::Vec collect_modules(bool open_zygisk) { } void MagiskD::handle_modules() const noexcept { + bool zygisk = zygisk_enabled(); prepare_modules(); - exec_module_scripts("post-fs-data", collect_modules(false)); + exec_module_scripts("post-fs-data", collect_modules(zygisk, false)); // Recollect modules (module scripts could remove itself) - auto list = collect_modules(true); - load_modules(list); + auto list = collect_modules(zygisk, true); + load_modules(zygisk, list); set_module_list(std::move(list)); } diff --git a/native/src/core/scripting.cpp b/native/src/core/scripting.cpp index b24a84db4..5e99c216f 100644 --- a/native/src/core/scripting.cpp +++ b/native/src/core/scripting.cpp @@ -26,7 +26,7 @@ static void set_script_env() { char new_path[4096]; ssprintf(new_path, sizeof(new_path), "%s:%s", getenv("PATH"), get_magisk_tmp()); setenv("PATH", new_path, 1); - if (zygisk_enabled) + if (MagiskD().zygisk_enabled()) setenv("ZYGISK_ENABLED", "1", 1); }; diff --git a/native/src/core/zygisk/daemon.rs b/native/src/core/zygisk/daemon.rs index fdeb44290..7a106feb2 100644 --- a/native/src/core/zygisk/daemon.rs +++ b/native/src/core/zygisk/daemon.rs @@ -1,10 +1,18 @@ use crate::consts::MODULEROOT; use crate::daemon::{to_user_id, IpcRead, MagiskD, UnixSocketExt}; -use crate::ffi::{update_deny_flags, ZygiskRequest, ZygiskStateFlags}; +use crate::ffi::{ + get_magisk_tmp, restore_zygisk_prop, update_deny_flags, ZygiskRequest, ZygiskStateFlags, +}; use base::libc::{O_CLOEXEC, O_CREAT, O_RDONLY}; -use base::{cstr, open_fd, Directory, FsPathBuf, LoggedResult, Utf8CStrBufArr, WriteExt}; +use base::{ + cstr, error, fork_dont_care, libc, open_fd, raw_cstr, warn, Directory, FsPathBuf, LoggedError, + LoggedResult, Utf8CStrBufArr, WriteExt, +}; +use std::fmt::Write; use std::os::fd::{AsRawFd, FromRawFd, RawFd}; use std::os::unix::net::UnixStream; +use std::ptr; +use std::sync::atomic::Ordering; const UNMOUNT_MASK: u32 = ZygiskStateFlags::ProcessOnDenyList.repr | ZygiskStateFlags::DenyListEnforced.repr; @@ -22,21 +30,119 @@ impl MagiskD { }; match code { ZygiskRequest::GetInfo => self.get_process_info(client)?, - ZygiskRequest::ConnectCompanion => self.connect_zygiskd(client.as_raw_fd()), + ZygiskRequest::ConnectCompanion => self.connect_zygiskd(client), ZygiskRequest::GetModDir => self.get_mod_dir(client)?, _ => {} } }; } - pub fn get_module_fds(&self, is_64_bit: bool) -> Vec { - if let Some(module_list) = self.module_list.get() { + 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(); + } + } + + fn get_module_fds(&self, is_64_bit: bool) -> Option> { + self.module_list.get().map(|module_list| { module_list .iter() .map(|m| if is_64_bit { m.z64 } else { m.z32 }) .collect() - } else { - Vec::new() + }) + } + + fn exec_zygiskd(is_64_bit: bool, remote: UnixStream) { + // This fd has to survive exec + unsafe { + libc::fcntl(remote.as_raw_fd(), libc::F_SETFD, 0); + } + + // Start building the exec arguments + let mut exe = Utf8CStrBufArr::<64>::new(); + + #[cfg(target_pointer_width = "64")] + let magisk = if is_64_bit { "magisk" } else { "magisk32" }; + + #[cfg(target_pointer_width = "32")] + let magisk = "magisk"; + + let exe = FsPathBuf::new(&mut exe).join(get_magisk_tmp()).join(magisk); + + let mut fd_str = Utf8CStrBufArr::<16>::new(); + write!(fd_str, "{}", remote.as_raw_fd()).ok(); + unsafe { + libc::execl( + exe.as_ptr(), + raw_cstr!(""), + raw_cstr!("zygisk"), + raw_cstr!("companion"), + fd_str.as_ptr(), + ptr::null() as *const libc::c_char, + ); + libc::exit(-1); + } + } + + fn connect_zygiskd(&self, mut client: UnixStream) { + let mut zygiskd_sockets = self.zygiskd_sockets.lock().unwrap(); + let result: LoggedResult<()> = try { + let is_64_bit = client.ipc_read_int()? != 0; + 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 { + Self::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.ipc_read_int()? != 0 { + Err(LoggedError::default())?; + } + local + }; + socket.send_fds(&[client.as_raw_fd()])?; + }; + if result.is_err() { + error!("zygiskd startup error"); } } diff --git a/native/src/core/zygisk/entry.cpp b/native/src/core/zygisk/entry.cpp index a49a9a1d6..63cb45401 100644 --- a/native/src/core/zygisk/entry.cpp +++ b/native/src/core/zygisk/entry.cpp @@ -30,75 +30,10 @@ extern "C" [[maybe_unused]] NativeBridgeCallbacks NativeBridgeItf { // The following code runs in magiskd -static pthread_mutex_t zygiskd_lock = PTHREAD_MUTEX_INITIALIZER; -static int zygiskd_sockets[] = { -1, -1 }; -#define zygiskd_socket zygiskd_sockets[is_64_bit] - -void MagiskD::connect_zygiskd(int client) const noexcept { - mutex_guard g(zygiskd_lock); - - bool is_64_bit = read_int(client); - if (zygiskd_socket >= 0) { - // Make sure the socket is still valid - pollfd pfd = { zygiskd_socket, 0, 0 }; - poll(&pfd, 1, 0); - if (pfd.revents) { - // Any revent means error - close(zygiskd_socket); - zygiskd_socket = -1; - } - } - if (zygiskd_socket < 0) { - int fds[2]; - socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds); - zygiskd_socket = fds[0]; - if (fork_dont_care() == 0) { - char exe[64]; -#if defined(__LP64__) - ssprintf(exe, sizeof(exe), "%s/magisk%s", get_magisk_tmp(), (is_64_bit ? "" : "32")); -#else - ssprintf(exe, sizeof(exe), "%s/magisk", get_magisk_tmp()); -#endif - // This fd has to survive exec - fcntl(fds[1], F_SETFD, 0); - char buf[16]; - ssprintf(buf, sizeof(buf), "%d", fds[1]); - execl(exe, "", "zygisk", "companion", buf, (char *) nullptr); - exit(-1); - } - close(fds[1]); - rust::Vec module_fds = get_module_fds(is_64_bit); - send_fds(zygiskd_socket, rust::Slice(module_fds)); - // Wait for ack - if (read_int(zygiskd_socket) != 0) { - LOGE("zygiskd startup error\n"); - return; - } - } - send_fd(zygiskd_socket, client); -} - -void reset_zygisk(bool restore) { - if (!zygisk_enabled) return; - static atomic_uint zygote_start_count{1}; - if (!restore) { - close(zygiskd_sockets[0]); - close(zygiskd_sockets[1]); - zygiskd_sockets[0] = zygiskd_sockets[1] = -1; - } - if (restore) { - zygote_start_count = 1; - } else if (zygote_start_count.fetch_add(1) > 3) { - LOGW("zygote crashes too many times, rolling-back\n"); - restore = true; - } - if (restore) { - string native_bridge_orig = "0"; - if (native_bridge.length() > strlen(ZYGISKLDR)) { - native_bridge_orig = native_bridge.substr(strlen(ZYGISKLDR)); - } - set_prop(NBPROP, native_bridge_orig.data()); - } else { - set_prop(NBPROP, native_bridge.data()); +void restore_zygisk_prop() { + string native_bridge_orig = "0"; + if (native_bridge.length() > strlen(ZYGISKLDR)) { + native_bridge_orig = native_bridge.substr(strlen(ZYGISKLDR)); } + set_prop(NBPROP, native_bridge_orig.data()); }