diff --git a/zygiskd/Cargo.toml b/zygiskd/Cargo.toml index 49bb074..26b5aee 100644 --- a/zygiskd/Cargo.toml +++ b/zygiskd/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" rust-version = "1.67" [dependencies] -android_logger = "0.12.0" +android_logger = "0.13.0" anyhow = { version = "1.0.68", features = ["backtrace"] } clap = { version = "4.1.4", features = ["derive"] } const_format = "0.2.5" diff --git a/zygiskd/src/constants.rs b/zygiskd/src/constants.rs index d87ba9c..e274ba6 100644 --- a/zygiskd/src/constants.rs +++ b/zygiskd/src/constants.rs @@ -16,26 +16,25 @@ 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 PROP_SVC_ZYGOTE: &str = "init.svc.zygote"; +pub const PROP_CTL_RESTART: &str = "ctl.restart"; pub const ZYGISK_LOADER: &str = "libzygiskloader.so"; pub const SOCKET_PLACEHOLDER: &str = "socket_placeholder"; -pub const PATH_MODULE_DIR: &str = ".."; +pub const PATH_MODULES_DIR: &str = ".."; +pub const PATH_MODULE_PROP: &str = "module.prop"; pub const PATH_ZYGISKD32: &str = "bin/zygiskd32"; pub const PATH_ZYGISKD64: &str = "bin/zygiskd64"; +pub const PATH_TMP_DIR: &str = concatcp!("/dev/", SOCKET_PLACEHOLDER); +pub const PATH_TMP_PROP: &str = concatcp!("/dev/", SOCKET_PLACEHOLDER, "/module.prop"); + +pub const STATUS_LOADED: &str = "😋 Zygisksu is loaded"; +pub const STATUS_CRASHED: &str = "❌ Zygiskd has crashed"; +pub const STATUS_ROOT_IMPL_NONE: &str = "❌ Unknown root implementation"; +pub const STATUS_ROOT_IMPL_TOO_OLD: &str = "❌ Root implementation version too old"; +pub const STATUS_ROOT_IMPL_ABNORMAL: &str = "❌ Abnormal root implementation version"; +pub const STATUS_ROOT_IMPL_MULTIPLE: &str = "❌ Multiple root implementations installed"; #[derive(Debug, Eq, PartialEq, TryFromPrimitive)] #[repr(u8)] diff --git a/zygiskd/src/main.rs b/zygiskd/src/main.rs index 4dd46db..bc165d9 100644 --- a/zygiskd/src/main.rs +++ b/zygiskd/src/main.rs @@ -38,7 +38,7 @@ fn init_android_logger(tag: &str) { } fn start() -> Result<()> { - root_impl::setup()?; + root_impl::setup(); let cli = Args::parse(); match cli.command { Commands::Watchdog => watchdog::entry()?, diff --git a/zygiskd/src/root_impl/kernelsu.rs b/zygiskd/src/root_impl/kernelsu.rs index a34c148..cb0d2a2 100644 --- a/zygiskd/src/root_impl/kernelsu.rs +++ b/zygiskd/src/root_impl/kernelsu.rs @@ -1,4 +1,3 @@ -use anyhow::{bail, Result}; use nix::libc::prctl; use crate::constants::{MIN_KSU_VERSION, MAX_KSU_VERSION}; @@ -8,14 +7,20 @@ const CMD_GET_VERSION: usize = 2; const CMD_GET_ALLOW_LIST: usize = 5; const CMD_GET_DENY_LIST: usize = 6; -pub fn is_kernel_su() -> Result { +pub enum Version { + Supported, + TooOld, + Abnormal, +} + +pub fn get_kernel_su() -> Option { let mut version = 0; unsafe { prctl(KERNEL_SU_OPTION, CMD_GET_VERSION, &mut version as *mut i32) }; - return match version { - 0 => Ok(false), - MIN_KSU_VERSION..=MAX_KSU_VERSION => Ok(true), - 1..=MIN_KSU_VERSION => bail!("KernelSU version too old: {}", version), - _ => bail!("KernelSU version abnormal: {}", version) + match version { + 0 => None, + MIN_KSU_VERSION..=MAX_KSU_VERSION => Some(Version::Supported), + 1..=MIN_KSU_VERSION => Some(Version::TooOld), + _ => Some(Version::Abnormal) } } diff --git a/zygiskd/src/root_impl/magisk.rs b/zygiskd/src/root_impl/magisk.rs index 11641ee..c802d5e 100644 --- a/zygiskd/src/root_impl/magisk.rs +++ b/zygiskd/src/root_impl/magisk.rs @@ -1,8 +1,12 @@ -use anyhow::{bail, Result}; use std::process::{Command, Stdio}; use crate::constants::MIN_MAGISK_VERSION; -pub fn is_magisk() -> Result { +pub enum Version { + Supported, + TooOld, +} + +pub fn get_magisk() -> Option { let version: Option = Command::new("magisk") .arg("-V") .stdout(Stdio::piped()) @@ -10,13 +14,13 @@ pub fn is_magisk() -> Result { .and_then(|child| child.wait_with_output().ok()) .and_then(|output| String::from_utf8(output.stdout).ok()) .and_then(|output| output.trim().parse().ok()); - if let Some(version) = version { - if version < MIN_MAGISK_VERSION { - bail!("Magisk version too old: {}", version); + version.map(|version| { + if version >= MIN_MAGISK_VERSION { + Version::Supported + } else { + Version::TooOld } - return Ok(true); - } - Ok(false) + }) } pub fn uid_on_allowlist(uid: i32) -> bool { diff --git a/zygiskd/src/root_impl/mod.rs b/zygiskd/src/root_impl/mod.rs index 688654b..7dfb202 100644 --- a/zygiskd/src/root_impl/mod.rs +++ b/zygiskd/src/root_impl/mod.rs @@ -2,33 +2,52 @@ mod kernelsu; mod magisk; use once_cell::sync::OnceCell; -use anyhow::{bail, Result}; -enum RootImpl { +pub enum RootImpl { + None, + TooOld, + Abnormal, + Multiple, KernelSU, Magisk, } static ROOT_IMPL: OnceCell = OnceCell::new(); -pub fn setup() -> Result<()> { - if kernelsu::is_kernel_su()? { - if let Ok(true) = magisk::is_magisk() { - bail!("Multiple root implementation"); +pub fn setup() { + let ksu_version = kernelsu::get_kernel_su(); + let magisk_version = magisk::get_magisk(); + + let _ = match (ksu_version, magisk_version) { + (None, None) => ROOT_IMPL.set(RootImpl::None), + (Some(_), Some(_)) => ROOT_IMPL.set(RootImpl::Multiple), + (Some(ksu_version), None) => { + let val = match ksu_version { + kernelsu::Version::Supported => RootImpl::KernelSU, + kernelsu::Version::TooOld => RootImpl::TooOld, + kernelsu::Version::Abnormal => RootImpl::Abnormal, + }; + ROOT_IMPL.set(val) } - let _ = ROOT_IMPL.set(RootImpl::KernelSU); - } else if magisk::is_magisk()? { - let _ = ROOT_IMPL.set(RootImpl::Magisk); - } else { - bail!("Unknown root implementation"); - } - Ok(()) + (None, Some(magisk_version)) => { + let val = match magisk_version { + magisk::Version::Supported => RootImpl::Magisk, + magisk::Version::TooOld => RootImpl::TooOld, + }; + ROOT_IMPL.set(val) + } + }; +} + +pub fn get_impl() -> &'static RootImpl { + ROOT_IMPL.get().unwrap() } pub fn uid_on_allowlist(uid: i32) -> bool { match ROOT_IMPL.get().unwrap() { RootImpl::KernelSU => kernelsu::uid_on_allowlist(uid), RootImpl::Magisk => magisk::uid_on_allowlist(uid), + _ => unreachable!(), } } @@ -36,5 +55,6 @@ pub fn uid_on_denylist(uid: i32) -> bool { match ROOT_IMPL.get().unwrap() { RootImpl::KernelSU => kernelsu::uid_on_denylist(uid), RootImpl::Magisk => magisk::uid_on_denylist(uid), + _ => unreachable!(), } } diff --git a/zygiskd/src/utils.rs b/zygiskd/src/utils.rs index c30abc8..d65323d 100644 --- a/zygiskd/src/utils.rs +++ b/zygiskd/src/utils.rs @@ -6,6 +6,17 @@ use std::os::unix::net::UnixListener; use nix::sys::socket::{AddressFamily, SockFlag, SockType, UnixAddr}; use rand::distributions::{Alphanumeric, DistString}; +#[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 fn random_string() -> String { Alphanumeric.sample_string(&mut rand::thread_rng(), 8) } diff --git a/zygiskd/src/watchdog.rs b/zygiskd/src/watchdog.rs index 8798141..914c984 100644 --- a/zygiskd/src/watchdog.rs +++ b/zygiskd/src/watchdog.rs @@ -1,21 +1,35 @@ -use crate::{constants, utils}; +use crate::{constants, root_impl, utils}; use anyhow::{bail, Result}; use nix::unistd::{getgid, getuid, Pid}; use std::process::{Child, Command}; use std::sync::mpsc; -use std::{fs, thread}; +use std::{fs, io, thread}; +use std::ffi::CString; +use std::io::{BufRead, Write}; use std::os::unix::net::UnixListener; use std::time::Duration; use binder::IBinder; +use nix::errno::Errno; +use nix::libc; use nix::sys::signal::{kill, Signal}; +use once_cell::sync::OnceCell; -static mut LOCK: Option = None; +static LOCK: OnceCell = OnceCell::new(); +static PROP_SECTIONS: OnceCell<[String; 2]> = OnceCell::new(); pub fn entry() -> Result<()> { log::info!("Start zygisksu watchdog"); check_permission()?; ensure_single_instance()?; - spawn_daemon() + mount_prop()?; + if check_and_set_hint()? == false { + log::warn!("Requirements not met, exiting"); + utils::set_property(constants::PROP_NATIVE_BRIDGE, &utils::get_native_bridge())?; + return Ok(()) + } + let end = spawn_daemon(); + set_prop_hint(constants::STATUS_CRASHED)?; + end } fn check_permission() -> Result<()> { @@ -43,12 +57,83 @@ fn ensure_single_instance() -> Result<()> { log::info!("Ensure single instance"); let name = String::from("zygiskwd") + constants::SOCKET_PLACEHOLDER; match utils::abstract_namespace_socket(&name) { - Ok(socket) => unsafe { LOCK = Some(socket) }, + Ok(socket) => { let _ = LOCK.set(socket); } Err(e) => bail!("Failed to acquire lock: {e}. Maybe another instance is running?") } Ok(()) } +fn mount_prop() -> Result<()> { + let module_prop = fs::File::open(constants::PATH_MODULE_PROP)?; + let mut section = 0; + let mut sections: [String; 2] = [String::new(), String::new()]; + let lines = io::BufReader::new(module_prop).lines(); + for line in lines { + let line = line?; + if line.starts_with("description=") { + sections[0].push_str("description="); + sections[1].push_str(line.trim_start_matches("description=")); + sections[1].push('\n'); + section = 1; + } else { + sections[section].push_str(&line); + sections[section].push('\n'); + } + } + let _ = PROP_SECTIONS.set(sections); + + fs::create_dir(constants::PATH_TMP_DIR)?; + + // FIXME: sys_mount cannot be compiled on 32 bit + unsafe { + let r = libc::mount( + CString::new("tmpfs")?.as_ptr(), + CString::new(constants::PATH_TMP_DIR)?.as_ptr(), + CString::new("tmpfs")?.as_ptr(), + 0, + CString::new("mode=755")?.as_ptr() as *const libc::c_void, + ); + Errno::result(r)?; + let _file = fs::File::create(constants::PATH_TMP_PROP)?; + let r = libc::mount( + CString::new(constants::PATH_TMP_PROP)?.as_ptr(), + CString::new(constants::PATH_MODULE_PROP)?.as_ptr(), + std::ptr::null(), + libc::MS_BIND, + std::ptr::null(), + ); + Errno::result(r)?; + } + + Ok(()) +} + +fn set_prop_hint(hint: &str) -> Result<()> { + let mut file = fs::File::create(constants::PATH_TMP_PROP)?; + let sections = PROP_SECTIONS.get().unwrap(); + file.write_all(sections[0].as_bytes())?; + file.write_all(b"[")?; + file.write_all(hint.as_bytes())?; + file.write_all(b"] ")?; + file.write_all(sections[1].as_bytes())?; + Ok(()) +} + +fn check_and_set_hint() -> Result { + let root_impl = root_impl::get_impl(); + match root_impl { + root_impl::RootImpl::None => set_prop_hint(constants::STATUS_ROOT_IMPL_NONE)?, + root_impl::RootImpl::TooOld => set_prop_hint(constants::STATUS_ROOT_IMPL_TOO_OLD)?, + root_impl::RootImpl::Abnormal => set_prop_hint(constants::STATUS_ROOT_IMPL_ABNORMAL)?, + root_impl::RootImpl::Multiple => set_prop_hint(constants::STATUS_ROOT_IMPL_MULTIPLE)?, + _ => { + set_prop_hint(constants::STATUS_LOADED)?; + return Ok(true); + } + } + Ok(false) +} + fn spawn_daemon() -> Result<()> { let mut lives = 5; loop { @@ -99,7 +184,6 @@ fn spawn_daemon() -> Result<()> { log::error!("Restarting zygote..."); utils::set_property(constants::PROP_NATIVE_BRIDGE, constants::ZYGISK_LOADER)?; - utils::set_property(constants::PROP_SVC_ZYGOTE, "restart")?; - thread::sleep(Duration::from_secs(2)); + utils::set_property(constants::PROP_CTL_RESTART, "zygote")?; } } diff --git a/zygiskd/src/zygiskd.rs b/zygiskd/src/zygiskd.rs index 7476dbd..29dfbe4 100644 --- a/zygiskd/src/zygiskd.rs +++ b/zygiskd/src/zygiskd.rs @@ -77,7 +77,7 @@ fn get_arch() -> Result<&'static str> { fn load_modules(arch: &str) -> Result> { let mut modules = Vec::new(); - let dir = match fs::read_dir(constants::PATH_MODULE_DIR) { + let dir = match fs::read_dir(constants::PATH_MODULES_DIR) { Ok(dir) => dir, Err(e) => { log::warn!("Failed reading modules directory: {}", e); @@ -232,7 +232,7 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()> DaemonSocketAction::GetModuleDir => { let index = stream.read_usize()?; let module = &context.modules[index]; - let dir = format!("{}/{}", constants::PATH_MODULE_DIR, module.name); + let dir = format!("{}/{}", constants::PATH_MODULES_DIR, module.name); let dir = fs::File::open(dir)?; stream.send_fd(dir.as_raw_fd())?; }