Set zygisk properties in Rust

This commit is contained in:
topjohnwu
2025-08-20 10:39:47 -07:00
committed by John Wu
parent 49fdc1addb
commit 8d10ab89f2
8 changed files with 247 additions and 244 deletions

View File

@@ -218,7 +218,7 @@ static void handle_request_sync(int client, int code) {
denylist_handler(-1, nullptr);
// Restore native bridge property
restore_zygisk_prop();
MagiskD::Get().restore_zygisk_prop();
write_int(client, 0);

View File

@@ -10,15 +10,15 @@ use crate::mount::{clean_mounts, setup_preinit_dir};
use crate::package::ManagerInfo;
use crate::selinux::restore_tmpcon;
use crate::su::SuInfo;
use crate::zygisk::ZygiskState;
use base::libc::{O_APPEND, O_CLOEXEC, O_RDONLY, O_WRONLY};
use base::{
AtomicArc, BufReadExt, FsPathBuilder, ResultExt, Utf8CStr, Utf8CStrBuf, cstr, error, info, libc,
};
use std::fmt::Write as FmtWrite;
use std::io::{BufReader, Write};
use std::os::unix::net::UnixStream;
use std::process::Command;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, OnceLock};
// Global magiskd singleton
@@ -66,9 +66,8 @@ pub struct MagiskD {
pub manager_info: Mutex<ManagerInfo>,
boot_stage_lock: Mutex<BootStateFlags>,
pub module_list: OnceLock<Vec<ModuleInfo>>,
pub zygiskd_sockets: Mutex<(Option<UnixStream>, Option<UnixStream>)>,
pub zygisk_enabled: AtomicBool,
pub zygote_start_count: AtomicU32,
pub zygisk: Mutex<ZygiskState>,
pub cached_su_info: AtomicArc<SuInfo>,
sdk_int: i32,
pub is_emulator: bool,
@@ -80,10 +79,6 @@ impl MagiskD {
unsafe { MAGISKD.get().unwrap_unchecked() }
}
pub fn zygisk_enabled(&self) -> bool {
self.zygisk_enabled.load(Ordering::Acquire)
}
pub fn sdk_int(&self) -> i32 {
self.sdk_int
}
@@ -175,7 +170,7 @@ impl MagiskD {
setup_preinit_dir();
self.ensure_manager();
self.zygisk_reset(true)
self.zygisk.lock().unwrap().reset(true);
}
pub fn boot_stage_handler(&self, client: i32, code: i32) {
@@ -319,7 +314,6 @@ pub fn daemon_entry() {
sdk_int,
is_emulator,
is_recovery,
zygote_start_count: AtomicU32::new(1),
..Default::default()
};
MAGISKD.set(magiskd).ok();

View File

@@ -35,11 +35,6 @@ void unlock_blocks();
bool setup_magisk_env();
bool check_key_combo();
// Zygisk daemon
rust::Str get_zygisk_lib_name();
void set_zygisk_prop();
void restore_zygisk_prop();
// Sockets
struct sock_cred : public ucred {
std::string context;

View File

@@ -3,6 +3,7 @@
#![feature(fn_traits)]
#![feature(unix_socket_ancillary_data)]
#![feature(unix_socket_peek)]
#![feature(default_field_values)]
#![allow(clippy::missing_safety_doc)]
use crate::ffi::SuRequest;
@@ -148,9 +149,6 @@ pub mod ffi {
fn uninstall_pkg(apk: Utf8CStrRef);
fn update_deny_flags(uid: i32, process: &str, flags: &mut u32);
fn initialize_denylist();
fn get_zygisk_lib_name() -> &'static str;
fn set_zygisk_prop();
fn restore_zygisk_prop();
fn switch_mnt_ns(pid: i32) -> i32;
fn exec_root_shell(client: i32, pid: i32, req: &mut SuRequest, mode: MntNsMode);
@@ -228,6 +226,7 @@ pub mod ffi {
fn boot_stage_handler(&self, client: i32, code: i32);
fn zygisk_handler(&self, client: i32);
fn zygisk_reset(&self, restore: bool);
fn restore_zygisk_prop(&self);
fn prune_su_access(&self);
fn su_daemon_handler(&self, client: i32, cred: &UCred);
#[cxx_name = "get_manager"]

View File

@@ -1,9 +1,6 @@
use crate::consts::{MODULEMNT, MODULEROOT, MODULEUPGRADE, WORKERDIR};
use crate::daemon::MagiskD;
use crate::ffi::{
ModuleInfo, exec_module_scripts, exec_script, get_magisk_tmp, get_zygisk_lib_name,
load_prop_file, set_zygisk_prop,
};
use crate::ffi::{ModuleInfo, exec_module_scripts, exec_script, get_magisk_tmp, load_prop_file};
use crate::mount::setup_module_mount;
use base::{
DirEntry, Directory, FsPathBuilder, LibcReturn, LoggedResult, OsResultStatic, ResultExt,
@@ -502,9 +499,7 @@ fn inject_magisk_bins(system: &mut FsNode, is_emulator: bool) {
}
}
fn inject_zygisk_bins(system: &mut FsNode) {
let name = get_zygisk_lib_name();
fn inject_zygisk_bins(name: &str, system: &mut FsNode) {
#[cfg(target_pointer_width = "64")]
let has_32_bit = cstr!("/system/bin/linker").exists();
@@ -560,114 +555,6 @@ fn inject_zygisk_bins(system: &mut FsNode) {
}
}
fn apply_modules(zygisk: bool, module_list: &[ModuleInfo], is_emulator: bool) {
let mut system = FsNode::new_dir();
// Build all the base "prefix" paths
let mut root = cstr::buf::default().join_path("/");
let mut module_dir = cstr::buf::default().join_path(MODULEROOT);
let mut module_mnt = cstr::buf::default()
.join_path(get_magisk_tmp())
.join_path(MODULEMNT);
let mut worker = cstr::buf::default()
.join_path(get_magisk_tmp())
.join_path(WORKERDIR);
// Create a collection of all relevant paths
let mut root_paths = FilePaths {
real: PathTracker::from(&mut root),
worker: PathTracker::from(&mut worker),
module_mnt: PathTracker::from(&mut module_mnt),
module_root: PathTracker::from(&mut module_dir),
};
// Step 1: Create virtual filesystem tree
//
// In this step, there is zero logic applied during tree construction; we simply collect and
// record the union of all module filesystem trees under each of their /system directory.
for info in module_list {
let mut module_paths = root_paths.append(&info.name);
{
// Read props
let prop = module_paths.append("system.prop");
if prop.module().exists() {
// Do NOT go through property service as it could cause boot lock
load_prop_file(prop.module(), true);
}
}
{
// Check whether skip mounting
let skip = module_paths.append("skip_mount");
if skip.module().exists() {
continue;
}
}
{
// Double check whether the system folder exists
let sys = module_paths.append("system");
if sys.module().exists() {
info!("{}: loading module files", &info.name);
system.collect(sys).log_ok();
}
}
}
// Step 2: Inject custom files
//
// Magisk provides some built-in functionality that requires augmenting the filesystem.
// We expose several cmdline tools (e.g. su) into PATH, and the zygisk shared library
// has to also be added into the default LD_LIBRARY_PATH for code injection.
// We directly inject file nodes into the virtual filesystem tree we built in the previous
// step, treating Magisk just like a special "module".
if get_magisk_tmp() != "/sbin" || get_path_env().split(":").all(|s| s != "/sbin") {
inject_magisk_bins(&mut system, is_emulator);
}
if zygisk {
inject_zygisk_bins(&mut system);
}
// Step 3: Extract all supported read-only partition roots
//
// For simplicity and backwards compatibility on older Android versions, when constructing
// Magisk modules, we always assume that there is only a single read-only partition mounted
// at /system. However, on modern Android there are actually multiple read-only partitions
// mounted at their respective paths. We need to extract these subtrees out of the main
// tree and treat them as individual trees.
let mut roots = BTreeMap::new(); /* mapOf(partition_name -> FsNode) */
if let FsNode::Directory { children } = &mut system {
for dir in SECONDARY_READ_ONLY_PARTITIONS {
// Only treat these nodes as root iff it is actually a directory in rootdir
if let Ok(attr) = dir.get_attr()
&& attr.is_dir()
{
let name = dir.trim_start_matches('/');
if let Some(root) = children.remove(name) {
roots.insert(name, root);
}
}
}
}
roots.insert("system", system);
for (dir, mut root) in roots {
// Step 4: Convert virtual filesystem tree into concrete operations
//
// Compare the virtual filesystem tree we constructed against the real filesystem
// structure on-device to generate a series of "operations".
// The "core" of the logic is to decide which directories need to be rebuilt in the
// tmpfs worker directory, and real sub-nodes need to be mirrored inside it.
let path = root_paths.append(dir);
root.commit(path, true).log_ok();
}
}
fn upgrade_modules() -> LoggedResult<()> {
let mut upgrade = Directory::open(cstr!(MODULEUPGRADE))?;
let ufd = upgrade.as_raw_fd();
@@ -877,11 +764,120 @@ impl MagiskD {
// Recollect modules (module scripts could remove itself)
let modules = collect_modules(zygisk, true);
if zygisk {
set_zygisk_prop();
}
apply_modules(zygisk, &modules, self.is_emulator);
self.apply_modules(&modules);
self.module_list.set(modules).ok();
}
fn apply_modules(&self, module_list: &[ModuleInfo]) {
let mut system = FsNode::new_dir();
// Build all the base "prefix" paths
let mut root = cstr::buf::default().join_path("/");
let mut module_dir = cstr::buf::default().join_path(MODULEROOT);
let mut module_mnt = cstr::buf::default()
.join_path(get_magisk_tmp())
.join_path(MODULEMNT);
let mut worker = cstr::buf::default()
.join_path(get_magisk_tmp())
.join_path(WORKERDIR);
// Create a collection of all relevant paths
let mut root_paths = FilePaths {
real: PathTracker::from(&mut root),
worker: PathTracker::from(&mut worker),
module_mnt: PathTracker::from(&mut module_mnt),
module_root: PathTracker::from(&mut module_dir),
};
// Step 1: Create virtual filesystem tree
//
// In this step, there is zero logic applied during tree construction; we simply collect and
// record the union of all module filesystem trees under each of their /system directory.
for info in module_list {
let mut module_paths = root_paths.append(&info.name);
{
// Read props
let prop = module_paths.append("system.prop");
if prop.module().exists() {
// Do NOT go through property service as it could cause boot lock
load_prop_file(prop.module(), true);
}
}
{
// Check whether skip mounting
let skip = module_paths.append("skip_mount");
if skip.module().exists() {
continue;
}
}
{
// Double check whether the system folder exists
let sys = module_paths.append("system");
if sys.module().exists() {
info!("{}: loading module files", &info.name);
system.collect(sys).log_ok();
}
}
}
// Step 2: Inject custom files
//
// Magisk provides some built-in functionality that requires augmenting the filesystem.
// We expose several cmdline tools (e.g. su) into PATH, and the zygisk shared library
// has to also be added into the default LD_LIBRARY_PATH for code injection.
// We directly inject file nodes into the virtual filesystem tree we built in the previous
// step, treating Magisk just like a special "module".
if get_magisk_tmp() != "/sbin" || get_path_env().split(":").all(|s| s != "/sbin") {
inject_magisk_bins(&mut system, self.is_emulator);
}
// Handle zygisk
if self.zygisk_enabled.load(Ordering::Acquire) {
let mut zygisk = self.zygisk.lock().unwrap();
zygisk.set_prop();
inject_zygisk_bins(&zygisk.lib_name, &mut system);
}
// Step 3: Extract all supported read-only partition roots
//
// For simplicity and backwards compatibility on older Android versions, when constructing
// Magisk modules, we always assume that there is only a single read-only partition mounted
// at /system. However, on modern Android there are actually multiple read-only partitions
// mounted at their respective paths. We need to extract these subtrees out of the main
// tree and treat them as individual trees.
let mut roots = BTreeMap::new(); /* mapOf(partition_name -> FsNode) */
if let FsNode::Directory { children } = &mut system {
for dir in SECONDARY_READ_ONLY_PARTITIONS {
// Only treat these nodes as root iff it is actually a directory in rootdir
if let Ok(attr) = dir.get_attr()
&& attr.is_dir()
{
let name = dir.trim_start_matches('/');
if let Some(root) = children.remove(name) {
roots.insert(name, root);
}
}
}
}
roots.insert("system", system);
for (dir, mut root) in roots {
// Step 4: Convert virtual filesystem tree into concrete operations
//
// Compare the virtual filesystem tree we constructed against the real filesystem
// structure on-device to generate a series of "operations".
// The "core" of the logic is to decide which directories need to be rebuilt in the
// tmpfs worker directory, and real sub-nodes need to be mirrored inside it.
let path = root_paths.append(dir);
root.commit(path, true).log_ok();
}
}
}

View File

@@ -1,12 +1,12 @@
use crate::consts::MODULEROOT;
use crate::daemon::{MagiskD, to_user_id};
use crate::ffi::{
ZygiskRequest, ZygiskStateFlags, get_magisk_tmp, restore_zygisk_prop, set_zygisk_prop, update_deny_flags,
ZygiskRequest, ZygiskStateFlags, get_magisk_tmp, get_prop, set_prop, update_deny_flags,
};
use crate::socket::{IpcRead, UnixSocketExt};
use base::libc::{O_CLOEXEC, O_CREAT, O_RDONLY, STDOUT_FILENO};
use base::{
Directory, FsPathBuilder, LoggedError, LoggedResult, ResultExt, WriteExt, cstr, error,
Directory, FsPathBuilder, LoggedError, LoggedResult, ResultExt, Utf8CStr, WriteExt, cstr,
fork_dont_care, libc, raw_cstr, warn,
};
use std::fmt::Write;
@@ -15,6 +15,8 @@ use std::os::unix::net::UnixStream;
use std::ptr;
use std::sync::atomic::Ordering;
const NBPROP: &Utf8CStr = cstr!("ro.dalvik.vm.native.bridge");
const ZYGISKLDR: &str = "libzygisk.so";
const UNMOUNT_MASK: u32 =
ZygiskStateFlags::ProcessOnDenyList.repr | ZygiskStateFlags::DenyListEnforced.repr;
@@ -56,6 +58,105 @@ fn exec_zygiskd(is_64_bit: bool, remote: UnixStream) {
}
}
#[derive(Default)]
pub struct ZygiskState {
pub lib_name: String,
sockets: (Option<UnixStream>, Option<UnixStream>),
start_count: u32 = 1,
}
impl ZygiskState {
fn connect_zygiskd(&mut self, mut client: UnixStream, daemon: &MagiskD) -> LoggedResult<()> {
let is_64_bit: bool = client.read_decodable()?;
let socket = if is_64_bit {
&mut self.sockets.1
} else {
&mut self.sockets.0
};
if let Some(fd) = socket {
// Make sure the socket is still valid
let mut pfd = libc::pollfd {
fd: fd.as_raw_fd(),
events: 0,
revents: 0,
};
if unsafe { libc::poll(&mut pfd, 1, 0) } != 0 || pfd.revents != 0 {
// Any revent means error
*socket = None;
}
}
let socket = if let Some(fd) = socket {
fd
} else {
// Create a new socket pair and fork zygiskd process
let (local, remote) = UnixStream::pair()?;
if fork_dont_care() == 0 {
exec_zygiskd(is_64_bit, remote);
}
*socket = Some(local);
let local = socket.as_mut().unwrap();
if let Some(module_fds) = daemon.get_module_fds(is_64_bit) {
local.send_fds(&module_fds)?;
}
if local.read_decodable::<i32>()? != 0 {
Err(LoggedError::default())?;
}
local
};
socket.send_fds(&[client.as_raw_fd()])?;
Ok(())
}
pub fn reset(&mut self, mut restore: bool) {
if restore {
self.start_count = 1;
} else {
self.sockets = (None, None);
self.start_count += 1;
if self.start_count > 3 {
warn!("zygote crashed too many times, rolling-back");
restore = true;
}
}
if restore {
self.restore_prop();
} else {
self.set_prop();
}
}
pub fn set_prop(&mut self) {
if !self.lib_name.is_empty() {
return;
}
let orig = get_prop(NBPROP, false);
self.lib_name = if orig.is_empty() || orig == "0" {
ZYGISKLDR.to_string()
} else {
orig + ZYGISKLDR
};
set_prop(NBPROP, Utf8CStr::from_string(&mut self.lib_name), false);
// Whether Huawei's Maple compiler is enabled.
// If so, system server will be created by a special Zygote which ignores the native bridge
// and make system server out of our control. Avoid it by disabling.
if get_prop(cstr!("ro.maple.enable"), false) == "1" {
set_prop(cstr!("ro.maple.enable"), cstr!("0"), false);
}
}
fn restore_prop(&mut self) {
let mut orig = "0".to_string();
if self.lib_name.len() > ZYGISKLDR.len() {
orig = self.lib_name[ZYGISKLDR.len()..].to_string();
}
set_prop(NBPROP, Utf8CStr::from_string(&mut orig), false);
self.lib_name.clear();
}
}
impl MagiskD {
pub fn zygisk_handler(&self, client: i32) {
let mut client = unsafe { UnixStream::from_raw_fd(client) };
@@ -65,35 +166,18 @@ impl MagiskD {
};
match code {
ZygiskRequest::GetInfo => self.get_process_info(client)?,
ZygiskRequest::ConnectCompanion => self.connect_zygiskd(client),
ZygiskRequest::ConnectCompanion => self
.zygisk
.lock()
.unwrap()
.connect_zygiskd(client, self)
.log_with_msg(|w| w.write_str("zygiskd startup error"))?,
ZygiskRequest::GetModDir => self.get_mod_dir(client)?,
_ => {}
}
};
}
pub fn zygisk_reset(&self, mut restore: bool) {
if !self.zygisk_enabled.load(Ordering::Acquire) {
return;
}
if restore {
self.zygote_start_count.store(1, Ordering::Release);
} else {
*self.zygiskd_sockets.lock().unwrap() = (None, None);
if self.zygote_start_count.fetch_add(1, Ordering::AcqRel) > 3 {
warn!("zygote crashes too many times, rolling-back");
restore = true;
}
}
if restore {
restore_zygisk_prop();
} else {
set_zygisk_prop();
}
}
fn get_module_fds(&self, is_64_bit: bool) -> Option<Vec<RawFd>> {
self.module_list.get().map(|module_list| {
module_list
@@ -107,54 +191,6 @@ impl MagiskD {
})
}
fn connect_zygiskd(&self, mut client: UnixStream) {
let mut zygiskd_sockets = self.zygiskd_sockets.lock().unwrap();
let result: LoggedResult<()> = try {
let is_64_bit: bool = client.read_decodable()?;
let socket = if is_64_bit {
&mut zygiskd_sockets.1
} else {
&mut zygiskd_sockets.0
};
if let Some(fd) = socket {
// Make sure the socket is still valid
let mut pfd = libc::pollfd {
fd: fd.as_raw_fd(),
events: 0,
revents: 0,
};
if unsafe { libc::poll(&mut pfd, 1, 0) } != 0 || pfd.revents != 0 {
// Any revent means error
*socket = None;
}
}
let socket = if let Some(fd) = socket {
fd
} else {
// Create a new socket pair and fork zygiskd process
let (local, remote) = UnixStream::pair()?;
if fork_dont_care() == 0 {
exec_zygiskd(is_64_bit, remote);
}
*socket = Some(local);
let local = socket.as_mut().unwrap();
if let Some(module_fds) = self.get_module_fds(is_64_bit) {
local.send_fds(&module_fds)?;
}
if local.read_decodable::<i32>()? != 0 {
Err(LoggedError::default())?;
}
local
};
socket.send_fds(&[client.as_raw_fd()])?;
};
if result.is_err() {
error!("zygiskd startup error");
}
}
fn get_process_info(&self, mut client: UnixStream) -> LoggedResult<()> {
let uid: i32 = client.read_decodable()?;
let process: String = client.read_decodable()?;
@@ -214,3 +250,18 @@ impl MagiskD {
Ok(())
}
}
// FFI to C++
impl MagiskD {
pub fn zygisk_enabled(&self) -> bool {
self.zygisk_enabled.load(Ordering::Acquire)
}
pub fn zygisk_reset(&self, restore: bool) {
self.zygisk.lock().unwrap().reset(restore);
}
pub fn restore_zygisk_prop(&self) {
self.zygisk.lock().unwrap().restore_prop();
}
}

View File

@@ -10,8 +10,6 @@
using namespace std;
static string zygisk_lib_name;
static void zygiskd(int socket) {
if (getuid() != 0 || fcntl(socket, F_GETFD) < 0)
exit(-1);
@@ -109,33 +107,3 @@ extern "C" [[maybe_unused]] NativeBridgeCallbacks NativeBridgeItf {
return false;
},
};
rust::Str get_zygisk_lib_name() {
return zygisk_lib_name;
}
void set_zygisk_prop() {
if (!zygisk_lib_name.empty())
return;
string native_bridge_orig = get_prop(NBPROP);
if (native_bridge_orig.empty()) {
native_bridge_orig = "0";
}
zygisk_lib_name = native_bridge_orig == "0" ? ZYGISKLDR : ZYGISKLDR + native_bridge_orig;
set_prop(NBPROP, zygisk_lib_name.data());
// Whether Huawei's Maple compiler is enabled.
// If so, system server will be created by a special Zygote which ignores the native bridge
// and make system server out of our control. Avoid it by disabling.
if (get_prop("ro.maple.enable") == "1") {
set_prop("ro.maple.enable", "0");
}
}
void restore_zygisk_prop() {
string native_bridge_orig = "0";
if (zygisk_lib_name.length() > strlen(ZYGISKLDR)) {
native_bridge_orig = zygisk_lib_name.substr(strlen(ZYGISKLDR));
}
set_prop(NBPROP, native_bridge_orig.data());
zygisk_lib_name.clear();
}

View File

@@ -1,3 +1,3 @@
mod daemon;
pub use daemon::zygisk_should_load_module;
pub use daemon::{ZygiskState, zygisk_should_load_module};