Separate companion

This commit is contained in:
Nullptr
2023-02-08 20:17:56 +08:00
parent f75d15c6f6
commit 291599ffc8
10 changed files with 190 additions and 105 deletions

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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"

View File

@@ -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)
}
}

61
zygiskd/src/companion.rs Normal file
View File

@@ -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<Option<ZygiskCompanionEntryFn>> {
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))
}
}

View File

@@ -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";

View File

@@ -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());
}
}

View File

@@ -9,7 +9,14 @@ use std::{fs, thread};
static mut LOCK_FILE: Option<fs::File> = 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");
}

View File

@@ -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<ZygiskCompanionEntryFn>,
companion: Mutex<Option<UnixStream>>,
}
struct Context {
@@ -32,8 +31,9 @@ struct Context {
modules: Vec<Module>,
}
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<Vec<Module>> {
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<Memfd> {
Ok(memfd)
}
fn preload_module(memfd: &Memfd) -> Result<Option<ZygiskCompanionEntryFn>> {
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<UnixListener> {
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<UnixListener> {
Ok(listener)
}
fn spawn_companion(name: &str, memfd: &Memfd) -> Result<Option<UnixStream>> {
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)?;