diff --git a/README.md b/README.md index 3cc87b4..09df288 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,7 @@ Warning: The current version of Zygisksu is UNSTABLE. You may suffer boot loop o ## Compatibility -- [x] LSPosed -- [x] Storage Isolation -- [ ] IFW Enhance -- [ ] Universal SafetyNet Fix -- [ ] Shamiko +Should work with everything except those rely on Magisk internal behaviors. ## Development road map @@ -24,8 +20,8 @@ Warning: The current version of Zygisksu is UNSTABLE. You may suffer boot loop o - [x] [Inject] Stabilize injector - [x] [Inject] Unload - [x] [Daemon] Linker namespace -- [ ] [Daemon] Separate zygiskd process -- [ ] [Daemon] Handle 64 bit only devices +- [x] [Daemon] Separate zygiskd process +- [x] [Daemon] Handle 64 bit only devices - [ ] [Daemon] Handle zygote death ## Running on Magisk diff --git a/loader/src/common/daemon.cpp b/loader/src/common/daemon.cpp index 82a69e3..97b0dcf 100644 --- a/loader/src/common/daemon.cpp +++ b/loader/src/common/daemon.cpp @@ -98,10 +98,6 @@ namespace zygiskd { } socket_utils::write_u8(fd, (uint8_t) SocketAction::GetModuleDir); socket_utils::write_usize(fd, index); - if (socket_utils::read_u8(fd) == 1) { - return fd; - } else { - return -1; - } + return socket_utils::recv_fd(fd); } } diff --git a/module/src/daemon.sh b/module/src/daemon.sh index 333ad36..f4c1eb9 100644 --- a/module/src/daemon.sh +++ b/module/src/daemon.sh @@ -9,4 +9,4 @@ export NATIVE_BRIDGE=$(getprop ro.dalvik.vm.native.bridge) log -p i -t "zygisksu" "Start watchdog" resetprop ro.dalvik.vm.native.bridge libzygiskloader.so -exec "$MODDIR/bin/zygiskwd" >/dev/null 2>&1 +exec "$MODDIR/bin/zygiskwd" "watchdog" >/dev/null 2>&1 diff --git a/zygiskd/Cargo.toml b/zygiskd/Cargo.toml index 17c2811..0f40088 100644 --- a/zygiskd/Cargo.toml +++ b/zygiskd/Cargo.toml @@ -8,6 +8,7 @@ rust-version = "1.67" [dependencies] android_logger = "0.12.0" anyhow = { version = "1.0.68", features = ["backtrace"] } +clap = { version = "4.1.4", features = ["derive"] } const_format = "0.2.5" log = "0.4.17" memfd = "0.6.2" diff --git a/zygiskd/build.gradle.kts b/zygiskd/build.gradle.kts index 313284b..751633a 100644 --- a/zygiskd/build.gradle.kts +++ b/zygiskd/build.gradle.kts @@ -3,6 +3,9 @@ plugins { id("org.mozilla.rust-android-gradle.rust-android") } +val verName: String by rootProject.extra +val verCode: Int by rootProject.extra + android.buildFeatures { androidResources = false buildConfig = false @@ -16,4 +19,8 @@ cargo { targetDirectory = "build/intermediates/rust" val isDebug = gradle.startParameter.taskNames.any { it.toLowerCase().contains("debug") } profile = if (isDebug) "debug" else "release" + exec = { spec, _ -> + spec.environment("VERSION_CODE", verCode) + spec.environment("VERSION_NAME", verName) + } } diff --git a/zygiskd/src/companion.rs b/zygiskd/src/companion.rs new file mode 100644 index 0000000..2df5a41 --- /dev/null +++ b/zygiskd/src/companion.rs @@ -0,0 +1,61 @@ +use std::ffi::c_void; +use std::os::fd::{FromRawFd, RawFd}; +use std::os::unix::net::UnixStream; +use std::thread; +use anyhow::Result; +use nix::libc; +use passfd::FdPassingExt; +use crate::utils::UnixStreamExt; +use crate::dl; + +type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32); + +pub fn entry(fd: i32) -> Result<()> { + unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) }; + let mut stream = unsafe { UnixStream::from_raw_fd(fd) }; + let name = stream.read_string()?; + let library = stream.recv_fd()?; + let entry = load_module(library)?; + unsafe { libc::close(library) }; + + let entry = match entry { + Some(entry) => { + log::debug!("Companion process created for `{name}`"); + stream.write_u8(1)?; + entry + } + None => { + log::debug!("No companion entry for `{name}`"); + stream.write_u8(0)?; + return Ok(()); + } + }; + + loop { + let fd = stream.recv_fd()?; + log::trace!("New companion request from module `{name}`"); + thread::spawn(move || { + unsafe { + let mut s = UnixStream::from_raw_fd(fd); + match s.write_u8(1) { // Ack + Ok(_) => entry(fd), + Err(_) => log::warn!("Ack failed?"), + } + }; + }); + } +} + +fn load_module(fd: RawFd) -> Result> { + unsafe { + let path = format!("/proc/self/fd/{fd}"); + let handle = dl::dlopen(&path, libc::RTLD_NOW)?; + let symbol = std::ffi::CString::new("zygisk_companion_entry")?; + let entry = libc::dlsym(handle, symbol.as_ptr()); + if entry.is_null() { + return Ok(None); + } + let fnptr = std::mem::transmute::<*mut c_void, ZygiskCompanionEntryFn>(entry); + Ok(Some(fnptr)) + } +} diff --git a/zygiskd/src/constants.rs b/zygiskd/src/constants.rs index 49a4b11..3bd9108 100644 --- a/zygiskd/src/constants.rs +++ b/zygiskd/src/constants.rs @@ -1,6 +1,27 @@ use const_format::concatcp; +use log::LevelFilter; use num_enum::TryFromPrimitive; +pub const VERSION_NAME: &str = env!("VERSION_NAME"); +pub const VERSION_CODE: &str = env!("VERSION_CODE"); +pub const VERSION_FULL: &str = concatcp!(VERSION_NAME, " (", VERSION_CODE, ")"); + +#[cfg(debug_assertions)] +pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Trace; +#[cfg(not(debug_assertions))] +pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Info; + +#[cfg(target_pointer_width = "64")] +#[macro_export] +macro_rules! lp_select { + ($lp32:expr, $lp64:expr) => { $lp64 }; +} +#[cfg(target_pointer_width = "32")] +#[macro_export] +macro_rules! lp_select { + ($lp32:expr, $lp64:expr) => { $lp32 }; +} + pub const PROP_NATIVE_BRIDGE: &str = "ro.dalvik.vm.native.bridge"; pub const SOCKET_PLACEHOLDER: &str = "socket_placeholder"; diff --git a/zygiskd/src/main.rs b/zygiskd/src/main.rs index 8010f3f..aef46c9 100644 --- a/zygiskd/src/main.rs +++ b/zygiskd/src/main.rs @@ -1,57 +1,53 @@ #![allow(dead_code)] +mod companion; mod constants; mod dl; mod utils; mod watchdog; -mod zygisk; +mod zygiskd; -use anyhow::{bail, Result}; -use log::LevelFilter; -use nix::libc; +use clap::{Subcommand, Parser}; + +#[derive(Parser, Debug)] +#[command(author, version = constants::VERSION_FULL, about, long_about = None)] +struct Args { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand, Debug)] +enum Commands { + /// Start zygisk watchdog + Watchdog, + /// Start zygisk daemon + Daemon, + /// Start zygisk companion + Companion { fd: i32 }, +} -#[cfg(debug_assertions)] -static MAX_LOG_LEVEL: LevelFilter = LevelFilter::Trace; -#[cfg(not(debug_assertions))] -static MAX_LOG_LEVEL: LevelFilter = LevelFilter::Info; fn init_android_logger(tag: &str) { android_logger::init_once( android_logger::Config::default() - .with_max_level(MAX_LOG_LEVEL) + .with_max_level(constants::MAX_LOG_LEVEL) .with_tag(tag), ); } -fn entry() -> Result<()> { - let process = std::env::args().next().unwrap(); - let process = process.split('/').last().unwrap(); - init_android_logger(process); - match process { - "zygiskwd" => { - log::info!("Start zygisksu watchdog"); - watchdog::check_permission()?; - watchdog::ensure_single_instance()?; - watchdog::spawn_daemon()?; - } - "zygiskd32" => { - log::info!("Start zygiskd32"); - unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL); } - zygisk::start(false)?; - loop {} - } - "zygiskd64" => { - log::info!("Start zygiskd64"); - unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL); } - zygisk::start(true)?; - } - _ => bail!("Unexpected process name: {process}") - } - Ok(()) -} - fn main() { - if let Err(e) = entry() { + let process = std::env::args().next().unwrap(); + let nice_name = process.split('/').last().unwrap(); + init_android_logger(nice_name); + + let cli = Args::parse(); + let result = match cli.command { + Commands::Watchdog => watchdog::entry(), + Commands::Daemon => zygiskd::entry(lp_select!(false, true)), + Commands::Companion { fd } => companion::entry(fd), + }; + + if let Err(e) = &result { log::error!("Crashed: {}\n{}", e, e.backtrace()); } } diff --git a/zygiskd/src/watchdog.rs b/zygiskd/src/watchdog.rs index 491e071..54dc7d7 100644 --- a/zygiskd/src/watchdog.rs +++ b/zygiskd/src/watchdog.rs @@ -9,7 +9,14 @@ use std::{fs, thread}; static mut LOCK_FILE: Option = None; -pub fn check_permission() -> Result<()> { +pub fn entry() -> Result<()> { + log::info!("Start zygisksu watchdog"); + check_permission()?; + ensure_single_instance()?; + spawn_daemon() +} + +fn check_permission() -> Result<()> { log::info!("Check permission"); let uid = getuid(); if uid.as_raw() != 0 { @@ -31,7 +38,7 @@ pub fn check_permission() -> Result<()> { Ok(()) } -pub fn ensure_single_instance() -> Result<()> { +fn ensure_single_instance() -> Result<()> { log::info!("Ensure single instance"); let metadata = fs::metadata(constants::PATH_ZYGISKSU_DIR); if metadata.is_err() || !metadata.unwrap().is_dir() { @@ -50,9 +57,9 @@ pub fn ensure_single_instance() -> Result<()> { Ok(()) } -pub fn spawn_daemon() -> Result<()> { - let daemon32 = Command::new(constants::PATH_ZYGISKD32).spawn()?; - let daemon64 = Command::new(constants::PATH_ZYGISKD64).spawn()?; +fn spawn_daemon() -> Result<()> { + let daemon32 = Command::new(constants::PATH_ZYGISKD32).arg("daemon").spawn(); + let daemon64 = Command::new(constants::PATH_ZYGISKD64).arg("daemon").spawn(); let (sender, receiver) = mpsc::channel(); let spawn = |mut daemon: Child| { let sender = sender.clone(); @@ -63,8 +70,8 @@ pub fn spawn_daemon() -> Result<()> { sender.send(()).unwrap(); }); }; - spawn(daemon32); - spawn(daemon64); + if let Ok(it) = daemon32 { spawn(it) } + if let Ok(it) = daemon64 { spawn(it) } let _ = receiver.recv(); bail!("Daemon process died"); } diff --git a/zygiskd/src/zygisk.rs b/zygiskd/src/zygiskd.rs similarity index 76% rename from zygiskd/src/zygisk.rs rename to zygiskd/src/zygiskd.rs index 1ef263a..1e000a3 100644 --- a/zygiskd/src/zygisk.rs +++ b/zygiskd/src/zygiskd.rs @@ -1,30 +1,29 @@ use crate::constants::DaemonSocketAction; use crate::utils::{restore_native_bridge, UnixStreamExt}; -use crate::{constants, dl, utils}; +use crate::{constants, utils}; use anyhow::{bail, Result}; use memfd::Memfd; use nix::{ - libc::{self, dlsym}, - unistd::getppid, + fcntl::{fcntl, FcntlArg, FdFlag}, + libc::self, }; use passfd::FdPassingExt; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::thread; -use std::ffi::{c_char, c_void}; +use std::ffi::c_char; use std::fs; use std::os::unix::{ net::{UnixListener, UnixStream}, prelude::AsRawFd, }; +use std::os::unix::process::CommandExt; use std::path::PathBuf; use std::process::Command; -type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32); - struct Module { name: String, memfd: Memfd, - companion_entry: Option, + companion: Mutex>, } struct Context { @@ -32,8 +31,9 @@ struct Context { modules: Vec, } -pub fn start(is64: bool) -> Result<()> { - check_parent()?; +pub fn entry(is64: bool) -> Result<()> { + unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) }; + let arch = get_arch(is64)?; log::debug!("Daemon architecture: {arch}"); @@ -63,15 +63,6 @@ pub fn start(is64: bool) -> Result<()> { Ok(()) } -fn check_parent() -> Result<()> { - let parent = fs::read_to_string(format!("/proc/{}/cmdline", getppid().as_raw()))?; - let parent = parent.split('/').last().unwrap().trim_end_matches('\0'); - if parent != "zygiskwd" { - bail!("Daemon is not started by watchdog: {parent}"); - } - Ok(()) -} - fn get_arch(is64: bool) -> Result<&'static str> { let output = Command::new("getprop").arg("ro.product.cpu.abi").output()?; let system_arch = String::from_utf8(output.stdout)?; @@ -111,18 +102,16 @@ fn load_modules(arch: &str) -> Result> { continue; } }; - let companion_entry = match preload_module(&memfd) { - Ok(entry) => entry, + let companion = match spawn_companion(&name, &memfd) { + Ok(companion) => companion, Err(e) => { - log::warn!(" Failed to preload `{name}`: {e}"); + log::warn!(" Failed to spawn companion for `{name}`: {e}"); continue; } }; - let module = Module { - name, - memfd, - companion_entry, - }; + + let companion = Mutex::new(companion); + let module = Module { name, memfd, companion }; modules.push(module); } @@ -148,20 +137,6 @@ fn create_memfd(name: &str, so_path: &PathBuf) -> Result { Ok(memfd) } -fn preload_module(memfd: &Memfd) -> Result> { - unsafe { - let path = format!("/proc/self/fd/{}", memfd.as_raw_fd()); - let handle = dl::dlopen(&path, libc::RTLD_NOW)?; - let symbol = std::ffi::CString::new("zygisk_companion_entry")?; - let entry = dlsym(handle, symbol.as_ptr()); - if entry.is_null() { - return Ok(None); - } - let fnptr = std::mem::transmute::<*mut c_void, ZygiskCompanionEntryFn>(entry); - Ok(Some(fnptr)) - } -} - fn create_daemon_socket(is64: bool) -> Result { utils::set_socket_create_context("u:r:zygote:s0")?; let suffix = if is64 { "zygiskd64" } else { "zygiskd32" }; @@ -171,6 +146,29 @@ fn create_daemon_socket(is64: bool) -> Result { Ok(listener) } +fn spawn_companion(name: &str, memfd: &Memfd) -> Result> { + let (mut daemon, companion) = UnixStream::pair()?; + // Remove FD_CLOEXEC flag + fcntl(companion.as_raw_fd(), FcntlArg::F_SETFD(FdFlag::empty()))?; + + let process = std::env::args().next().unwrap(); + let nice_name = process.split('/').last().unwrap(); + Command::new(&process) + .arg0(format!("{}-{}", nice_name, name)) + .arg("companion") + .arg(format!("{}", companion.as_raw_fd())) + .spawn()?; + drop(companion); + + daemon.write_string(name)?; + daemon.send_fd(memfd.as_raw_fd())?; + match daemon.read_u8()? { + 0 => Ok(None), + 1 => Ok(Some(daemon)), + _ => bail!("Invalid companion response"), + } +} + fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()> { let action = stream.read_u8()?; let action = DaemonSocketAction::try_from(action)?; @@ -207,13 +205,15 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()> DaemonSocketAction::RequestCompanionSocket => { let index = stream.read_usize()?; let module = &context.modules[index]; - log::debug!("New companion request from module {}", module.name); - - // FIXME: Spawn a new process - match module.companion_entry { - Some(entry) => { - stream.write_u8(1)?; - unsafe { entry(stream.as_raw_fd()); } + let mut companion = module.companion.lock().unwrap(); + match companion.as_ref() { + Some(sock) => { + if let Err(_) = sock.send_fd(stream.as_raw_fd()) { + log::error!("Companion of module `{}` crashed", module.name); + companion.take(); + stream.write_u8(0)?; + } + // Ok: Send by companion } None => { stream.write_u8(0)?;