7 Commits

Author SHA1 Message Date
Nullptr
bea5ed47b8 Bump to 0.7.0 2023-06-04 11:16:03 +08:00
Nullptr
954a712089 Use app profile 2023-06-04 01:31:12 +08:00
Nullptr
f6195ddb43 Don't spawn new process for companion 2023-05-21 20:14:36 +08:00
Nullptr
8b5e9db347 Make watchdog async 2023-05-21 19:38:24 +08:00
Nullptr
a04f636ac4 Refine code 2023-05-19 19:14:54 +08:00
5ec1cff
00f0a6e3fa several changes (#21)
* umount for ksu 10763

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* Add crashdump sepolicy

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* Add more information about debug

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* daemonize zygiskd companion

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* create zygiskd if crash

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* injector: use ANDROID_DLEXT_USE_LIBRARY_FD to load module

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* zygiskd: use file as module fd instead of memfd on debug build

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* use OwnedFd

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* dlopen: no need to create ns

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

---------

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>
2023-05-19 18:28:56 +08:00
Nullptr
f5bf82fa93 Set memfd name to module name on debug 2023-04-23 09:15:53 +08:00
17 changed files with 210 additions and 235 deletions

View File

@@ -2,7 +2,7 @@
Zygisk loader for KernelSU, allowing Zygisk modules to run without Magisk environment. Zygisk loader for KernelSU, allowing Zygisk modules to run without Magisk environment.
Also works as standalone loader for Magisk on purpose of getting rid of LD_PRELOAD. Also works as standalone loader for Magisk.
## Requirements ## Requirements
@@ -12,8 +12,8 @@ Also works as standalone loader for Magisk on purpose of getting rid of LD_PRELO
### KernelSU ### KernelSU
+ Minimal KernelSU version: 10654 + Minimal KernelSU version: 10940
+ Minimal ksud version: 10670 + Minimal ksud version: 10942
+ Kernel has full SELinux patch support + Kernel has full SELinux patch support
### Magisk ### Magisk

View File

@@ -31,10 +31,10 @@ val gitCommitHash = "git rev-parse --verify --short HEAD".execute()
val moduleId by extra("zygisksu") val moduleId by extra("zygisksu")
val moduleName by extra("Zygisk on KernelSU") val moduleName by extra("Zygisk on KernelSU")
val verName by extra("v4-0.6.5") val verName by extra("v4-0.7.0")
val verCode by extra(gitCommitCount) val verCode by extra(gitCommitCount)
val minKsuVersion by extra(10818) val minKsuVersion by extra(10940)
val minKsudVersion by extra(10818) val minKsudVersion by extra(10942)
val maxKsuVersion by extra(20000) val maxKsuVersion by extra(20000)
val minMagiskVersion by extra(25208) val minMagiskVersion by extra(25208)

View File

@@ -115,6 +115,7 @@ namespace zygiskd {
if (socket_utils::read_u8(fd) == 1) { if (socket_utils::read_u8(fd) == 1) {
return fd; return fd;
} else { } else {
close(fd);
return -1; return -1;
} }
} }

View File

@@ -44,8 +44,17 @@ void* DlopenExt(const char* path, int flags) {
return handle; return handle;
} }
void* DlopenMem(int memfd, int flags) { void* DlopenMem(int fd, int flags) {
char path[PATH_MAX]; auto info = android_dlextinfo{
sprintf(path, "/proc/self/fd/%d", memfd); .flags = ANDROID_DLEXT_USE_LIBRARY_FD,
return DlopenExt(path, flags); .library_fd = fd
};
auto* handle = android_dlopen_ext("/jit-cache", flags, &info);
if (handle) {
LOGD("dlopen fd %d: %p", fd, handle);
} else {
LOGE("dlopen fd %d: %s", fd, dlerror());
}
return handle;
} }

View File

@@ -20,7 +20,7 @@ public:
UniqueFd(Fd fd) : fd_(fd) {} UniqueFd(Fd fd) : fd_(fd) {}
~UniqueFd() { close(fd_); } ~UniqueFd() { if (fd_ >= 0) close(fd_); }
// Disallow copy // Disallow copy
UniqueFd(const UniqueFd&) = delete; UniqueFd(const UniqueFd&) = delete;

View File

@@ -46,7 +46,7 @@ androidComponents.onVariants { variant ->
expand( expand(
"moduleId" to moduleId, "moduleId" to moduleId,
"moduleName" to moduleName, "moduleName" to moduleName,
"versionName" to "$verName ($verCode)", "versionName" to "$verName ($verCode-$variantLowered)",
"versionCode" to verCode, "versionCode" to verCode,
) )
} }

View File

@@ -80,6 +80,11 @@ extract "$ZIPFILE" 'customize.sh' "$TMPDIR/.vunzip"
extract "$ZIPFILE" 'verify.sh' "$TMPDIR/.vunzip" extract "$ZIPFILE" 'verify.sh' "$TMPDIR/.vunzip"
extract "$ZIPFILE" 'sepolicy.rule' "$TMPDIR" extract "$ZIPFILE" 'sepolicy.rule' "$TMPDIR"
if [ "$DEBUG" = true ]; then
ui_print "- Add debug SELinux policy"
echo "allow crash_dump adb_data_file dir search" >> "$TMPDIR/sepolicy.rule"
fi
if [ "$KSU" ]; then if [ "$KSU" ]; then
ui_print "- Checking SELinux patches" ui_print "- Checking SELinux patches"
if ! check_sepolicy "$TMPDIR/sepolicy.rule"; then if ! check_sepolicy "$TMPDIR/sepolicy.rule"; then

View File

@@ -3,22 +3,25 @@ name = "zygiskd"
authors = ["Nullptr"] authors = ["Nullptr"]
version = "1.0.0" version = "1.0.0"
edition = "2021" edition = "2021"
rust-version = "1.67" rust-version = "1.69"
[dependencies] [dependencies]
android_logger = "0.13.0" android_logger = "0.13"
anyhow = { version = "1.0.68", features = ["backtrace"] } anyhow = { version = "1.0", features = ["backtrace"] }
clap = { version = "4.1.4", features = ["derive"] } bitflags = { version = "2.3" }
const_format = "0.2.5" clap = { version = "4", features = ["derive"] }
konst = "0.3.4" const_format = "0.2"
lazy_static = "1.4.0" futures = "0.3"
log = "0.4.17" konst = "0.3"
memfd = "0.6.2" lazy_static = "1.4"
nix = "0.26.2" log = "0.4"
num_enum = "0.5.9" memfd = "0.6"
once_cell = "1.17.1" nix = { version = "0.26", features = ["process","poll"] }
passfd = "0.1.5" num_enum = "0.5"
rand = "0.8.5" once_cell = "1.17"
passfd = "0.1"
rand = "0.8"
tokio = { version = "1.28", features = ["full"] }
binder = { git = "https://github.com/Kernel-SU/binder_rs" } binder = { git = "https://github.com/Kernel-SU/binder_rs" }

View File

@@ -1,61 +0,0 @@
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,3 +1,4 @@
use bitflags::bitflags;
use const_format::concatcp; use const_format::concatcp;
use konst::primitive::parse_i32; use konst::primitive::parse_i32;
use konst::unwrap_ctx; use konst::unwrap_ctx;
@@ -46,8 +47,13 @@ pub enum DaemonSocketAction {
} }
// Zygisk process flags // Zygisk process flags
pub const PROCESS_GRANTED_ROOT: u32 = 1 << 0; bitflags! {
pub const PROCESS_ON_DENYLIST: u32 = 1 << 1; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub const PROCESS_ROOT_IS_KSU: u32 = 1 << 29; pub struct ProcessFlags: u32 {
pub const PROCESS_ROOT_IS_MAGISK: u32 = 1 << 30; const PROCESS_GRANTED_ROOT = 1 << 0;
pub const PROCESS_IS_SYSUI: u32 = 1 << 31; const PROCESS_ON_DENYLIST = 1 << 1;
const PROCESS_ROOT_IS_KSU = 1 << 29;
const PROCESS_ROOT_IS_MAGISK = 1 << 30;
const PROCESS_IS_SYSUI = 1 << 31;
}
}

View File

@@ -1,6 +1,5 @@
#![allow(dead_code)] #![allow(dead_code)]
mod companion;
mod constants; mod constants;
mod dl; mod dl;
mod magic; mod magic;
@@ -25,8 +24,6 @@ enum Commands {
Watchdog, Watchdog,
/// Start zygisk daemon /// Start zygisk daemon
Daemon, Daemon,
/// Start zygisk companion
Companion { fd: i32 },
} }
@@ -38,24 +35,24 @@ fn init_android_logger(tag: &str) {
); );
} }
fn start() -> Result<()> { async fn start() -> Result<()> {
root_impl::setup(); root_impl::setup();
magic::setup()?; magic::setup()?;
let cli = Args::parse(); let cli = Args::parse();
match cli.command { match cli.command {
Commands::Watchdog => watchdog::entry()?, Commands::Watchdog => watchdog::entry().await?,
Commands::Daemon => zygiskd::entry()?, Commands::Daemon => zygiskd::entry()?,
Commands::Companion { fd } => companion::entry(fd)?,
}; };
Ok(()) Ok(())
} }
fn main() { #[tokio::main]
async fn main() {
let process = std::env::args().next().unwrap(); let process = std::env::args().next().unwrap();
let nice_name = process.split('/').last().unwrap(); let nice_name = process.split('/').last().unwrap();
init_android_logger(nice_name); init_android_logger(nice_name);
if let Err(e) = start() { if let Err(e) = start().await {
log::error!("Crashed: {}\n{}", e, e.backtrace()); log::error!("Crashed: {}\n{}", e, e.backtrace());
} }
} }

View File

@@ -4,7 +4,8 @@ use crate::constants::{MIN_KSU_VERSION, MAX_KSU_VERSION};
const KERNEL_SU_OPTION: i32 = 0xdeadbeefu32 as i32; const KERNEL_SU_OPTION: i32 = 0xdeadbeefu32 as i32;
const CMD_GET_VERSION: usize = 2; const CMD_GET_VERSION: usize = 2;
const CMD_GET_ALLOW_LIST: usize = 5; const CMD_UID_GRANTED_ROOT: usize = 12;
const CMD_UID_SHOULD_UMOUNT: usize = 13;
pub enum Version { pub enum Version {
Supported, Supported,
@@ -23,16 +24,14 @@ pub fn get_kernel_su() -> Option<Version> {
} }
} }
#[inline(never)] pub fn uid_granted_root(uid: i32) -> bool {
pub fn uid_on_allowlist(uid: i32) -> bool { let mut granted = false;
let mut size = 1024u32; unsafe { prctl(KERNEL_SU_OPTION, CMD_UID_GRANTED_ROOT, uid, &mut granted as *mut bool) };
let mut uids = vec![0; size as usize]; granted
unsafe { prctl(KERNEL_SU_OPTION, CMD_GET_ALLOW_LIST, uids.as_mut_ptr(), &mut size as *mut u32) };
uids.resize(size as usize, 0);
uids.contains(&uid)
} }
#[inline(never)] pub fn uid_should_umount(uid: i32) -> bool {
pub fn uid_on_denylist(uid: i32) -> bool { let mut umount = false;
false unsafe { prctl(KERNEL_SU_OPTION, CMD_UID_SHOULD_UMOUNT, uid, &mut umount as *mut bool) };
umount
} }

View File

@@ -23,8 +23,7 @@ pub fn get_magisk() -> Option<Version> {
}) })
} }
#[inline(never)] pub fn uid_granted_root(uid: i32) -> bool {
pub fn uid_on_allowlist(uid: i32) -> bool {
let output: Option<String> = Command::new("magisk") let output: Option<String> = Command::new("magisk")
.arg("--sqlite") .arg("--sqlite")
.arg("select uid from policies where policy=2") .arg("select uid from policies where policy=2")
@@ -41,8 +40,7 @@ pub fn uid_on_allowlist(uid: i32) -> bool {
}) })
} }
#[inline(never)] pub fn uid_should_umount(uid: i32) -> bool {
pub fn uid_on_denylist(uid: i32) -> bool { // TODO: uid_should_umount
// TODO: uid_on_denylist
return false; return false;
} }

View File

@@ -41,21 +41,18 @@ pub fn get_impl() -> &'static RootImpl {
unsafe { &ROOT_IMPL } unsafe { &ROOT_IMPL }
} }
// FIXME: Without #[inline(never)], this function will lag forever pub fn uid_granted_root(uid: i32) -> bool {
#[inline(never)]
pub fn uid_on_allowlist(uid: i32) -> bool {
match get_impl() { match get_impl() {
RootImpl::KernelSU => kernelsu::uid_on_allowlist(uid), RootImpl::KernelSU => kernelsu::uid_granted_root(uid),
RootImpl::Magisk => magisk::uid_on_allowlist(uid), RootImpl::Magisk => magisk::uid_granted_root(uid),
_ => unreachable!(), _ => unreachable!(),
} }
} }
#[inline(never)] pub fn uid_should_umount(uid: i32) -> bool {
pub fn uid_on_denylist(uid: i32) -> bool {
match get_impl() { match get_impl() {
RootImpl::KernelSU => kernelsu::uid_on_denylist(uid), RootImpl::KernelSU => kernelsu::uid_should_umount(uid),
RootImpl::Magisk => magisk::uid_on_denylist(uid), RootImpl::Magisk => magisk::uid_should_umount(uid),
_ => unreachable!(), _ => unreachable!(),
} }
} }

View File

@@ -19,6 +19,17 @@ macro_rules! lp_select {
($lp32:expr, $lp64:expr) => { $lp32 }; ($lp32:expr, $lp64:expr) => { $lp32 };
} }
#[cfg(debug_assertions)]
#[macro_export]
macro_rules! debug_select {
($debug:expr, $release:expr) => { $debug };
}
#[cfg(not(debug_assertions))]
#[macro_export]
macro_rules! debug_select {
($debug:expr, $release:expr) => { $release };
}
pub struct LateInit<T> { pub struct LateInit<T> {
cell: OnceCell<T>, cell: OnceCell<T>,
} }

View File

@@ -1,33 +1,36 @@
use crate::{constants, magic, root_impl, utils}; use crate::{constants, magic, root_impl, utils};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use nix::unistd::{getgid, getuid, Pid}; use nix::unistd::{getgid, getuid, Pid};
use std::process::{Child, Command}; use std::{fs, io};
use std::sync::mpsc;
use std::{fs, io, thread};
use std::ffi::CString; use std::ffi::CString;
use std::future::Future;
use std::io::{BufRead, Write}; use std::io::{BufRead, Write};
use std::os::unix::net::UnixListener; use std::os::unix::net::UnixListener;
use std::pin::Pin;
use std::time::Duration; use std::time::Duration;
use binder::IBinder; use binder::IBinder;
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use nix::errno::Errno; use nix::errno::Errno;
use nix::libc; use nix::libc;
use nix::sys::signal::{kill, Signal}; use nix::sys::signal::{kill, Signal};
use tokio::process::{Child, Command};
use crate::utils::LateInit; use crate::utils::LateInit;
static LOCK: LateInit<UnixListener> = LateInit::new(); static LOCK: LateInit<UnixListener> = LateInit::new();
static PROP_SECTIONS: LateInit<[String; 2]> = LateInit::new(); static PROP_SECTIONS: LateInit<[String; 2]> = LateInit::new();
pub fn entry() -> Result<()> { pub async fn entry() -> Result<()> {
log::info!("Start zygisksu watchdog"); log::info!("Start zygisksu watchdog");
check_permission()?; check_permission()?;
ensure_single_instance()?; ensure_single_instance()?;
mount_prop()?; mount_prop().await?;
if check_and_set_hint()? == false { if check_and_set_hint()? == false {
log::warn!("Requirements not met, exiting"); log::warn!("Requirements not met, exiting");
utils::set_property(constants::PROP_NATIVE_BRIDGE, &utils::get_native_bridge())?; utils::set_property(constants::PROP_NATIVE_BRIDGE, &utils::get_native_bridge())?;
return Ok(()); return Ok(());
} }
let end = spawn_daemon(); let end = spawn_daemon().await;
set_prop_hint(constants::STATUS_CRASHED)?; set_prop_hint(constants::STATUS_CRASHED)?;
end end
} }
@@ -63,9 +66,9 @@ fn ensure_single_instance() -> Result<()> {
Ok(()) Ok(())
} }
fn mount_prop() -> Result<()> { async fn mount_prop() -> Result<()> {
let module_prop = if let root_impl::RootImpl::Magisk = root_impl::get_impl() { let module_prop = if let root_impl::RootImpl::Magisk = root_impl::get_impl() {
let magisk_path = Command::new("magisk").arg("--path").output()?; let magisk_path = Command::new("magisk").arg("--path").output().await?;
let mut magisk_path = String::from_utf8(magisk_path.stdout)?; let mut magisk_path = String::from_utf8(magisk_path.stdout)?;
magisk_path.pop(); // Removing '\n' magisk_path.pop(); // Removing '\n'
let cwd = std::env::current_dir()?; let cwd = std::env::current_dir()?;
@@ -136,46 +139,57 @@ fn check_and_set_hint() -> Result<bool> {
Ok(false) Ok(false)
} }
fn spawn_daemon() -> Result<()> { async fn spawn_daemon() -> Result<()> {
let mut lives = 5; let mut lives = 5;
loop { loop {
let mut futures = FuturesUnordered::<Pin<Box<dyn Future<Output=Result<()>>>>>::new();
let mut child_ids = vec![];
let daemon32 = Command::new(constants::PATH_ZYGISKD32).arg("daemon").spawn(); let daemon32 = Command::new(constants::PATH_ZYGISKD32).arg("daemon").spawn();
let daemon64 = Command::new(constants::PATH_ZYGISKD64).arg("daemon").spawn(); let daemon64 = Command::new(constants::PATH_ZYGISKD64).arg("daemon").spawn();
let mut child_ids = vec![]; async fn spawn_daemon(mut daemon: Child) -> Result<()> {
let (sender, receiver) = mpsc::channel(); let result = daemon.wait().await?;
let mut spawn = |mut daemon: Child| { log::error!("Daemon process {} died: {}", daemon.id().unwrap(), result);
child_ids.push(daemon.id()); Ok(())
let sender = sender.clone();
thread::spawn(move || {
let result = daemon.wait().unwrap();
log::error!("Daemon process {} died: {}", daemon.id(), result);
drop(daemon);
let _ = sender.send(());
});
};
if let Ok(it) = daemon32 { spawn(it) }
if let Ok(it) = daemon64 { spawn(it) }
let mut binder = loop {
if receiver.try_recv().is_ok() {
bail!("Daemon died before system server ready");
}
match binder::get_service("activity") {
Some(binder) => break binder,
None => {
log::trace!("System server not ready, wait for 1s...");
thread::sleep(Duration::from_secs(1));
}
};
};
log::info!("System server ready, restore native bridge");
utils::set_property(constants::PROP_NATIVE_BRIDGE, &utils::get_native_bridge())?;
loop {
if receiver.try_recv().is_ok() || binder.ping_binder().is_err() { break; }
thread::sleep(Duration::from_secs(1))
} }
if let Ok(it) = daemon32 {
child_ids.push(it.id().unwrap());
futures.push(Box::pin(spawn_daemon(it)));
}
if let Ok(it) = daemon64 {
child_ids.push(it.id().unwrap());
futures.push(Box::pin(spawn_daemon(it)));
}
async fn binder_listener() -> Result<()> {
let mut binder = loop {
match binder::get_service("activity") {
Some(binder) => break binder,
None => {
log::trace!("System server not ready, wait for 1s...");
tokio::time::sleep(Duration::from_secs(1)).await;
}
};
};
log::info!("System server ready, restore native bridge");
utils::set_property(constants::PROP_NATIVE_BRIDGE, &utils::get_native_bridge())?;
loop {
if binder.ping_binder().is_err() { break; }
tokio::time::sleep(Duration::from_secs(1)).await;
}
log::error!("System server died");
Ok(())
}
futures.push(Box::pin(binder_listener()));
if let Err(e) = futures.next().await.unwrap() {
log::error!("{}", e);
}
for child in child_ids { for child in child_ids {
log::debug!("Killing child process {}", child);
let _ = kill(Pid::from_raw(child as i32), Signal::SIGKILL); let _ = kill(Pid::from_raw(child as i32), Signal::SIGKILL);
} }

View File

@@ -1,28 +1,28 @@
use crate::constants::DaemonSocketAction; use std::ffi::c_void;
use crate::constants::{DaemonSocketAction, ProcessFlags};
use crate::utils::UnixStreamExt; use crate::utils::UnixStreamExt;
use crate::{constants, lp_select, magic, root_impl, utils}; use crate::{constants, dl, lp_select, magic, root_impl, utils};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use memfd::Memfd;
use nix::{
fcntl::{fcntl, FcntlArg, FdFlag},
libc::self,
};
use passfd::FdPassingExt; use passfd::FdPassingExt;
use std::sync::{Arc, Mutex}; use std::sync::Arc;
use std::thread; use std::thread;
use std::fs; use std::fs;
use std::os::fd::{IntoRawFd, OwnedFd};
use std::os::unix::{ use std::os::unix::{
net::{UnixListener, UnixStream}, net::{UnixListener, UnixStream},
prelude::AsRawFd, prelude::AsRawFd,
}; };
use std::os::unix::process::CommandExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use nix::libc;
use nix::sys::stat::fstat;
use nix::unistd::close;
type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32);
struct Module { struct Module {
name: String, name: String,
memfd: Memfd, lib_fd: OwnedFd,
companion: Mutex<Option<UnixStream>>, entry: Option<ZygiskCompanionEntryFn>,
} }
struct Context { struct Context {
@@ -82,8 +82,8 @@ fn load_modules(arch: &str) -> Result<Vec<Module>> {
return Ok(modules); return Ok(modules);
} }
}; };
for entry_result in dir.into_iter() { for entry in dir.into_iter() {
let entry = entry_result?; let entry = entry?;
let name = entry.file_name().into_string().unwrap(); let name = entry.file_name().into_string().unwrap();
let so_path = entry.path().join(format!("zygisk/{arch}.so")); let so_path = entry.path().join(format!("zygisk/{arch}.so"));
let disabled = entry.path().join("disable"); let disabled = entry.path().join("disable");
@@ -91,30 +91,28 @@ fn load_modules(arch: &str) -> Result<Vec<Module>> {
continue; continue;
} }
log::info!(" Loading module `{name}`..."); log::info!(" Loading module `{name}`...");
let memfd = match create_memfd(&so_path) { let lib_fd = match create_library_fd(&so_path) {
Ok(memfd) => memfd, Ok(fd) => fd,
Err(e) => { Err(e) => {
log::warn!(" Failed to create memfd for `{name}`: {e}"); log::warn!(" Failed to create memfd for `{name}`: {e}");
continue; continue;
} }
}; };
let companion = match spawn_companion(&name, &memfd) { let entry = resolve_module(&so_path.to_string_lossy())?;
Ok(companion) => companion, let module = Module { name, lib_fd, entry };
Err(e) => {
log::warn!(" Failed to spawn companion for `{name}`: {e}");
continue;
}
};
let companion = Mutex::new(companion);
let module = Module { name, memfd, companion };
modules.push(module); modules.push(module);
} }
Ok(modules) Ok(modules)
} }
fn create_memfd(so_path: &PathBuf) -> Result<Memfd> { #[cfg(debug_assertions)]
fn create_library_fd(so_path: &PathBuf) -> Result<OwnedFd> {
Ok(OwnedFd::from(fs::File::open(so_path)?))
}
#[cfg(not(debug_assertions))]
fn create_library_fd(so_path: &PathBuf) -> Result<OwnedFd> {
let opts = memfd::MemfdOptions::default().allow_sealing(true); let opts = memfd::MemfdOptions::default().allow_sealing(true);
let memfd = opts.create("jit-cache")?; let memfd = opts.create("jit-cache")?;
let file = fs::File::open(so_path)?; let file = fs::File::open(so_path)?;
@@ -129,7 +127,7 @@ fn create_memfd(so_path: &PathBuf) -> Result<Memfd> {
seals.insert(memfd::FileSeal::SealSeal); seals.insert(memfd::FileSeal::SealSeal);
memfd.add_seals(&seals)?; memfd.add_seals(&seals)?;
Ok(memfd) Ok(OwnedFd::from(memfd.into_file()))
} }
fn create_daemon_socket() -> Result<UnixListener> { fn create_daemon_socket() -> Result<UnixListener> {
@@ -141,26 +139,16 @@ fn create_daemon_socket() -> Result<UnixListener> {
Ok(listener) Ok(listener)
} }
fn spawn_companion(name: &str, memfd: &Memfd) -> Result<Option<UnixStream>> { fn resolve_module(path: &str) -> Result<Option<ZygiskCompanionEntryFn>> {
let (mut daemon, companion) = UnixStream::pair()?; unsafe {
// Remove FD_CLOEXEC flag let handle = dl::dlopen(path, libc::RTLD_NOW)?;
fcntl(companion.as_raw_fd(), FcntlArg::F_SETFD(FdFlag::empty()))?; let symbol = std::ffi::CString::new("zygisk_companion_entry")?;
let entry = libc::dlsym(handle, symbol.as_ptr());
let process = std::env::args().next().unwrap(); if entry.is_null() {
let nice_name = process.split('/').last().unwrap(); return Ok(None);
Command::new(&process) }
.arg0(format!("{}-{}", nice_name, name)) let fnptr = std::mem::transmute::<*mut c_void, ZygiskCompanionEntryFn>(entry);
.arg("companion") Ok(Some(fnptr))
.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"),
} }
} }
@@ -188,43 +176,51 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()>
} }
DaemonSocketAction::GetProcessFlags => { DaemonSocketAction::GetProcessFlags => {
let uid = stream.read_u32()? as i32; let uid = stream.read_u32()? as i32;
let mut flags = 0u32; let mut flags = ProcessFlags::empty();
if root_impl::uid_on_allowlist(uid) { if root_impl::uid_granted_root(uid) {
flags |= constants::PROCESS_GRANTED_ROOT; flags |= ProcessFlags::PROCESS_GRANTED_ROOT;
} }
if root_impl::uid_on_denylist(uid) { if root_impl::uid_should_umount(uid) {
flags |= constants::PROCESS_ON_DENYLIST; flags |= ProcessFlags::PROCESS_ON_DENYLIST;
} }
match root_impl::get_impl() { match root_impl::get_impl() {
root_impl::RootImpl::KernelSU => flags |= constants::PROCESS_ROOT_IS_KSU, root_impl::RootImpl::KernelSU => flags |= ProcessFlags::PROCESS_ROOT_IS_KSU,
root_impl::RootImpl::Magisk => flags |= constants::PROCESS_ROOT_IS_MAGISK, root_impl::RootImpl::Magisk => flags |= ProcessFlags::PROCESS_ROOT_IS_MAGISK,
_ => unreachable!(), _ => unreachable!(),
} }
// TODO: PROCESS_IS_SYSUI? log::trace!("Uid {} granted root: {}", uid, flags.contains(ProcessFlags::PROCESS_GRANTED_ROOT));
stream.write_u32(flags)?; log::trace!("Uid {} on denylist: {}", uid, flags.contains(ProcessFlags::PROCESS_ON_DENYLIST));
stream.write_u32(flags.bits())?;
} }
DaemonSocketAction::ReadModules => { DaemonSocketAction::ReadModules => {
stream.write_usize(context.modules.len())?; stream.write_usize(context.modules.len())?;
for module in context.modules.iter() { for module in context.modules.iter() {
stream.write_string(&module.name)?; stream.write_string(&module.name)?;
stream.send_fd(module.memfd.as_raw_fd())?; stream.send_fd(module.lib_fd.as_raw_fd())?;
} }
} }
DaemonSocketAction::RequestCompanionSocket => { DaemonSocketAction::RequestCompanionSocket => {
let index = stream.read_usize()?; let index = stream.read_usize()?;
let module = &context.modules[index]; let module = &context.modules[index];
let mut companion = module.companion.lock().unwrap(); match module.entry {
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 => { None => {
stream.write_u8(0)?; stream.write_u8(0)?;
return Ok(());
}
Some(companion) => {
stream.write_u8(1)?;
let fd = stream.into_raw_fd();
let st0 = fstat(fd)?;
unsafe { companion(fd); }
// Only close client if it is the same file so we don't
// accidentally close a re-used file descriptor.
// This check is required because the module companion
// handler could've closed the file descriptor already.
if let Ok(st1) = fstat(fd) {
if st0.st_dev == st1.st_dev && st0.st_ino == st1.st_ino {
close(fd)?;
}
}
} }
} }
} }