diff --git a/.gitignore b/.gitignore index 563675f..fd9e84f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ .cxx build local.properties +target Cargo.lock diff --git a/zygiskd/Cargo.toml b/zygiskd/Cargo.toml index dd25ba5..b24c03a 100644 --- a/zygiskd/Cargo.toml +++ b/zygiskd/Cargo.toml @@ -7,7 +7,7 @@ rust-version = "1.67" [dependencies] android_logger = "0.12.0" -anyhow = "1.0.68" +anyhow = { version = "1.0.68", features = ["backtrace"] } 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 e3521cc..a160cea 100644 --- a/zygiskd/build.gradle.kts +++ b/zygiskd/build.gradle.kts @@ -10,10 +10,6 @@ cargo { targets = listOf("arm64", "arm", "x86", "x86_64") val isDebug = gradle.startParameter.taskNames.any { it.toLowerCase().contains("debug") } profile = if (isDebug) "debug" else "release" - targetDirectory = "build/intermediates/rust" - exec = { spec, _ -> - spec.environment("CARGO_TARGET_DIR", targetDirectory) - } } androidComponents.onVariants { variant -> @@ -25,11 +21,16 @@ androidComponents.onVariants { variant -> } } - task("pushAndRun$variantCapped") { + task("push$variantCapped") { group = "zygiskd" - dependsOn("build$variantCapped") + dependsOn("cargoBuildArm", "cargoBuildArm64") doLast { - commandLine("adb", "push", "target/") + val moduleDir = "/data/adb/ksu/modules/zygisksu" + exec { commandLine("adb", "push", "build/rustJniLibs/android/armeabi-v7a/zygiskd", "/data/local/tmp/zygiskd32") } + exec { commandLine("adb", "push", "build/rustJniLibs/android/arm64-v8a/zygiskd", "/data/local/tmp/zygiskd64") } + exec { commandLine("adb", "shell", "su", "-c", "mv /data/local/tmp/zygiskd32 $moduleDir/zygiskd32") } + exec { commandLine("adb", "shell", "su", "-c", "mv /data/local/tmp/zygiskd64 $moduleDir/zygiskd64") } + exec { commandLine("adb", "shell", "su", "-c", "ln -sf zygiskd64 $moduleDir/zygiskwd") } } } } diff --git a/zygiskd/src/constants.rs b/zygiskd/src/constants.rs index 55ca36e..3e7f780 100644 --- a/zygiskd/src/constants.rs +++ b/zygiskd/src/constants.rs @@ -3,12 +3,12 @@ use num_enum::TryFromPrimitive; pub const PROP_NATIVE_BRIDGE: &str = "ro.dalvik.vm.native.bridge"; -pub const KSU_MODULE_DIR: &str = "/data/adb/ksu/modules"; -pub const ZYGISKSU_DIR: &str = concatcp!(KSU_MODULE_DIR, "/zygisksu"); -pub const ZYGISKWD: &str = concatcp!(ZYGISKSU_DIR, "/zygiskwd"); -pub const ZYGISKD32: &str = concatcp!(ZYGISKSU_DIR, "/zygiskd32"); -pub const ZYGISKD64: &str = concatcp!(ZYGISKSU_DIR, "/zygiskd64"); -pub const DAEMON_LOCK: &str = concatcp!(ZYGISKSU_DIR, "/zygiskd.lock"); +pub const PATH_KSU_MODULE_DIR: &str = "/data/adb/ksu/modules"; +pub const PATH_ZYGISKSU_DIR: &str = concatcp!(PATH_KSU_MODULE_DIR, "/zygisksu"); +pub const PATH_ZYGISKWD: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskwd"); +pub const PATH_ZYGISKD32: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskd32"); +pub const PATH_ZYGISKD64: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskd64"); +pub const PATH_DAEMON_LOCK: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskd.lock"); #[derive(Debug, Eq, PartialEq, TryFromPrimitive)] #[repr(u8)] diff --git a/zygiskd/src/main.rs b/zygiskd/src/main.rs index 3eae20f..147814d 100644 --- a/zygiskd/src/main.rs +++ b/zygiskd/src/main.rs @@ -1,11 +1,11 @@ #![allow(dead_code)] -mod watchdog; mod constants; -mod zygisk; mod utils; +mod watchdog; +mod zygisk; -use anyhow::{anyhow, Result}; +use anyhow::{bail, Result}; use log::LevelFilter; use nix::libc; @@ -17,7 +17,7 @@ fn init_android_logger(tag: &str) { ); } -fn main() -> Result<()> { +fn entry() -> Result<()> { let process = std::env::args().next().unwrap(); let process = process.split('/').last().unwrap(); init_android_logger(process); @@ -39,7 +39,13 @@ fn main() -> Result<()> { unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL); } zygisk::start(true)?; } - _ => return Err(anyhow!("Unexpected process name: {process}")) + _ => bail!("Unexpected process name: {process}") } Ok(()) } + +fn main() { + if let Err(e) = entry() { + log::error!("Crashed: {}", e.backtrace()); + } +} diff --git a/zygiskd/src/watchdog.rs b/zygiskd/src/watchdog.rs index 125bb7f..491e071 100644 --- a/zygiskd/src/watchdog.rs +++ b/zygiskd/src/watchdog.rs @@ -1,5 +1,5 @@ use crate::constants; -use anyhow::{anyhow, Result}; +use anyhow::{bail, Result}; use nix::fcntl::{flock, FlockArg}; use nix::unistd::{getgid, getuid}; use std::os::unix::prelude::AsRawFd; @@ -13,17 +13,19 @@ pub fn check_permission() -> Result<()> { log::info!("Check permission"); let uid = getuid(); if uid.as_raw() != 0 { - return Err(anyhow!("UID is not 0")); + bail!("UID is not 0"); } let gid = getgid(); if gid.as_raw() != 0 { - return Err(anyhow!("GID is not 0")); + bail!("GID is not 0"); } let context = fs::read_to_string("/proc/self/attr/current")?; - if context != "u:r:su:s0" { - return Err(anyhow!("SELinux context is not u:r:su:s0")); + let context = context.trim_end_matches('\0'); + //TODO: remove magisk context after debug finished + if context != "u:r:su:s0" && context != "u:r:magisk:s0" { + bail!("SELinux context incorrect: {context}"); } Ok(()) @@ -31,28 +33,26 @@ pub fn check_permission() -> Result<()> { pub fn ensure_single_instance() -> Result<()> { log::info!("Ensure single instance"); - let metadata = fs::metadata(constants::ZYGISKSU_DIR); + let metadata = fs::metadata(constants::PATH_ZYGISKSU_DIR); if metadata.is_err() || !metadata.unwrap().is_dir() { - return Err(anyhow!("Zygisksu is not installed")); + bail!("Zygisksu is not installed"); } unsafe { - match fs::File::create(constants::DAEMON_LOCK) { + match fs::File::create(constants::PATH_DAEMON_LOCK) { Ok(file) => LOCK_FILE = Some(file), - Err(e) => return Err(anyhow!("Failed to open lock file: {e}")), + Err(e) => bail!("Failed to open lock file: {e}"), }; let fd = LOCK_FILE.as_ref().unwrap().as_raw_fd(); if let Err(e) = flock(fd, FlockArg::LockExclusiveNonblock) { - return Err(anyhow!( - "Failed to acquire lock: {e}. Maybe another instance is running?" - )); + bail!("Failed to acquire lock: {e}. Maybe another instance is running?"); } } Ok(()) } pub fn spawn_daemon() -> Result<()> { - let daemon32 = Command::new(constants::ZYGISKD32).spawn()?; - let daemon64 = Command::new(constants::ZYGISKD64).spawn()?; + let daemon32 = Command::new(constants::PATH_ZYGISKD32).spawn()?; + let daemon64 = Command::new(constants::PATH_ZYGISKD64).spawn()?; let (sender, receiver) = mpsc::channel(); let spawn = |mut daemon: Child| { let sender = sender.clone(); @@ -66,5 +66,5 @@ pub fn spawn_daemon() -> Result<()> { spawn(daemon32); spawn(daemon64); let _ = receiver.recv(); - Err(anyhow!("Daemon process died")) + bail!("Daemon process died"); } diff --git a/zygiskd/src/zygisk.rs b/zygiskd/src/zygisk.rs index f7978ce..0596d4e 100644 --- a/zygiskd/src/zygisk.rs +++ b/zygiskd/src/zygisk.rs @@ -1,26 +1,26 @@ use crate::constants::DaemonSocketAction; use crate::utils::{restore_native_bridge, UnixStreamExt}; use crate::{constants, utils}; -use anyhow::{anyhow, Result}; +use anyhow::{bail, Result}; use memfd::Memfd; use nix::{ libc::{self, dlsym}, unistd::getppid, }; use passfd::FdPassingExt; -use std::cell::Cell; use std::io::Write; use std::sync::Arc; use std::thread; -use std::{ - ffi::c_void, - fs, - os::unix::{ - net::{UnixListener, UnixStream}, - prelude::AsRawFd, - }, - path::PathBuf, +use std::ffi::c_void; +use std::fs; +use std::os::fd::FromRawFd; +use std::os::unix::{ + net::{UnixListener, UnixStream}, + prelude::AsRawFd, }; +use std::path::PathBuf; +use std::process::Command; +use nix::sys::socket::{AddressFamily, SockFlag, SockType, UnixAddr}; type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32); @@ -33,11 +33,11 @@ struct Module { struct Context { native_bridge: String, modules: Arc>, - stream: Cell, + listener: UnixListener, } pub fn start(is64: bool) -> Result<()> { - // check_parent()?; + check_parent()?; let arch = get_arch(is64)?; log::debug!("Daemon architecture: {arch}"); @@ -47,56 +47,44 @@ pub fn start(is64: bool) -> Result<()> { log::info!("Create socket"); let listener = create_daemon_socket(is64)?; - log::info!("Waiting for connection"); - let (stream, _) = listener.accept()?; - drop(listener); - let context = Context { native_bridge: utils::get_native_bridge(), modules: Arc::new(modules), - stream: Cell::new(stream), + listener, }; - log::info!("Connection established"); - restore_native_bridge()?; + log::info!("Start to listen zygote connections"); handle_daemon_actions(context)?; Ok(()) } fn check_parent() -> Result<()> { - let parent = fs::read_link(format!("/proc/{}/exe", getppid().as_raw()))?; - let parent = parent.file_name().unwrap().to_str().unwrap(); - if parent != constants::ZYGISKWD { - return Err(anyhow!("zygiskd is not started by watchdog")); + 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 ro.product.cpu.abi").output()?; - // let system_arch = String::from_utf8(output.stdout)?; - let system_arch = "x86_64"; // DEBUGGING + let output = Command::new("getprop").arg("ro.product.cpu.abi").output()?; + let system_arch = String::from_utf8(output.stdout)?; let is_arm = system_arch.contains("arm"); let is_x86 = system_arch.contains("x86"); - if is64 { - match (is_arm, is_x86) { - (true, _) => Ok("arm64-v8a"), - (_, true) => Ok("x86_64"), - _ => Err(anyhow!("Unsupported system architecture: {}", system_arch)), - } - } else { - match (is_arm, is_x86) { - (true, _) => Ok("armeabi-v7a"), - (_, true) => Ok("x86"), - _ => Err(anyhow!("Unsupported system architecture: {}", system_arch)), - } + match (is_arm, is_x86, is64) { + (true, _, false) => Ok("armeabi-v7a"), + (true, _, true) => Ok("arm64-v8a"), + (_, true, false) => Ok("x86"), + (_, true, true) => Ok("x86_64"), + _ => bail!("Unsupported system architecture: {}", system_arch), } } fn load_modules(arch: &str) -> Result> { let mut modules = Vec::new(); - let dir = match fs::read_dir(constants::KSU_MODULE_DIR) { + let dir = match fs::read_dir(constants::PATH_KSU_MODULE_DIR) { Ok(dir) => dir, Err(e) => { log::warn!("Failed reading modules directory: {}", e); @@ -119,7 +107,7 @@ fn load_modules(arch: &str) -> Result> { } }; let companion_entry = match preload_module(&memfd) { - Ok(companion_entry) => companion_entry, + Ok(entry) => entry, Err(e) => { log::warn!(" Failed to preload `{name}`: {e}"); continue; @@ -136,7 +124,7 @@ fn load_modules(arch: &str) -> Result> { Ok(modules) } -fn create_memfd(name: &str, so_path: &PathBuf) -> Result { +fn create_memfd(name: &str, so_path: &PathBuf) -> Result { let opts = memfd::MemfdOptions::default().allow_sealing(true); let memfd = opts.create(name)?; @@ -164,7 +152,7 @@ fn preload_module(memfd: &Memfd) -> Result> { let e = std::ffi::CStr::from_ptr(libc::dlerror()) .to_string_lossy() .into_owned(); - return Err(anyhow!("dlopen failed: {}", e)); + bail!("dlopen failed: {}", e); } let symbol = std::ffi::CString::new("zygisk_companion_entry")?; let entry = dlsym(handle, symbol.as_ptr()); @@ -178,17 +166,25 @@ fn preload_module(memfd: &Memfd) -> Result> { fn create_daemon_socket(is64: bool) -> Result { utils::set_socket_create_context("u:r:zygote:s0")?; - let socket_name = if is64 { "zygiskd64" } else { "zygiskd32" }; - let listener = UnixListener::bind(socket_name)?; + let name = if is64 { "zygiskd64" } else { "zygiskd32" }; + // TODO: Replace with SockAddrExt::from_abstract_name when it's stable + let addr = UnixAddr::new_abstract(name.as_bytes())?; + let socket = nix::sys::socket::socket(AddressFamily::Unix, SockType::Stream, SockFlag::empty(), None)?; + nix::sys::socket::bind(socket, &addr)?; + log::debug!("Listening on {}", addr); + log::debug!("Socket fd: {}", socket); + let listener = unsafe { UnixListener::from_raw_fd(socket) }; Ok(listener) } -fn handle_daemon_actions(mut context: Context) -> Result<()> { - let stream = context.stream.get_mut(); +fn handle_daemon_actions(context: Context) -> Result<()> { loop { + let (mut stream, _) = context.listener.accept()?; let action = stream.read_u8()?; match DaemonSocketAction::try_from(action) { + // First connection from zygote Ok(DaemonSocketAction::ReadNativeBridge) => { + restore_native_bridge()?; stream.write_usize(context.native_bridge.len())?; stream.write_all(context.native_bridge.as_bytes())?; } @@ -210,9 +206,7 @@ fn handle_daemon_actions(mut context: Context) -> Result<()> { } }); } - Err(_) => { - return Err(anyhow!("Invalid action code: {action}")); - } + Err(_) => bail!("Invalid action code: {action}") } } } @@ -226,15 +220,13 @@ fn create_companion(mut server: UnixStream, modules: &Vec) -> Result<()> let module = &modules[index]; log::debug!("New companion request from module {}", module.name); - unsafe { - match module.companion_entry { - Some(entry) => { - let (sock_app, sock_companion) = UnixStream::pair()?; - server.send_fd(sock_app.as_raw_fd())?; - entry(sock_companion.as_raw_fd()); - } - None => (), + match module.companion_entry { + Some(entry) => { + let (sock_app, sock_companion) = UnixStream::pair()?; + server.send_fd(sock_app.as_raw_fd())?; + unsafe { entry(sock_companion.as_raw_fd()); } } + None => (), } } }