From 8b49eda85a91b4bac55434414e98a7d73889562b Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Mon, 15 Sep 2025 12:37:29 -0700 Subject: [PATCH] Migrate daemon_entry to Rust --- native/src/Android.mk | 1 - native/src/base/lib.rs | 7 ++ native/src/core/daemon.cpp | 123 +------------------- native/src/core/daemon.rs | 186 +++++++++++++++++++++++++------ native/src/core/include/core.hpp | 23 +--- native/src/core/lib.rs | 27 ++--- native/src/core/logging.rs | 168 ++++++++++++++-------------- native/src/core/su/connect.rs | 12 +- native/src/core/su/daemon.rs | 19 ++-- native/src/core/thread.cpp | 98 ---------------- native/src/core/thread.rs | 98 ++++++++++++++++ native/src/core/zygisk/daemon.rs | 5 +- native/src/core/zygisk/entry.cpp | 21 +--- native/src/core/zygisk/mod.rs | 25 +++++ native/src/include/consts.hpp | 7 -- native/src/include/consts.rs | 1 + 16 files changed, 409 insertions(+), 412 deletions(-) delete mode 100644 native/src/core/thread.cpp create mode 100644 native/src/core/thread.rs diff --git a/native/src/Android.mk b/native/src/Android.mk index 3e366896a..5cad7ba47 100644 --- a/native/src/Android.mk +++ b/native/src/Android.mk @@ -20,7 +20,6 @@ LOCAL_SRC_FILES := \ core/daemon.cpp \ core/scripting.cpp \ core/sqlite.cpp \ - core/thread.cpp \ core/core-rs.cpp \ core/resetprop/sys.cpp \ core/su/su.cpp \ diff --git a/native/src/base/lib.rs b/native/src/base/lib.rs index 44189dc87..4f781a6ac 100644 --- a/native/src/base/lib.rs +++ b/native/src/base/lib.rs @@ -74,3 +74,10 @@ mod ffi { fn map_fd_for_cxx(fd: i32, sz: usize, rw: bool) -> &'static mut [u8]; } } + +// In Rust, we do not want to deal with raw pointers, so we change the +// signature of all *mut c_void to usize for new_daemon_thread. +pub type ThreadEntry = extern "C" fn(usize) -> usize; +unsafe extern "C" { + pub fn new_daemon_thread(entry: ThreadEntry, arg: usize); +} diff --git a/native/src/core/daemon.cpp b/native/src/core/daemon.cpp index 1c65d928b..31dc4fd0a 100644 --- a/native/src/core/daemon.cpp +++ b/native/src/core/daemon.cpp @@ -12,10 +12,6 @@ using namespace std; -int SDK_INT = -1; - -static struct stat self_st; - bool read_string(int fd, std::string &str) { str.clear(); int len = read_int(fd); @@ -35,118 +31,6 @@ void write_string(int fd, string_view str) { xwrite(fd, str.data(), str.size()); } -static bool is_client(pid_t pid) { - // Verify caller is the same as server - char path[32]; - sprintf(path, "/proc/%d/exe", pid); - struct stat st{}; - return !(stat(path, &st) || st.st_dev != self_st.st_dev || st.st_ino != self_st.st_ino); -} - -static void handle_request(owned_fd client) { - // Verify client credentials - ucred cred{}; - - socklen_t len = sizeof(cred); - // Client died - if (getsockopt(client, SOL_SOCKET, SO_PEERCRED, &cred, &len) != 0) - return; - char context[256]; - len = sizeof(context); - if (getsockopt(client, SOL_SOCKET, SO_PEERSEC, context, &len) != 0) - len = 0; - context[len] = '\0'; - - bool is_root = cred.uid == AID_ROOT; - bool is_zygote = context == "u:r:zygote:s0"sv; - - if (!is_root && !is_zygote && !is_client(cred.pid)) { - // Unsupported client state - write_int(client, +RespondCode::ACCESS_DENIED); - return; - } - - int code = read_int(client); - if (code < 0 || code >= +RequestCode::END || - code == +RequestCode::_SYNC_BARRIER_ || - code == +RequestCode::_STAGE_BARRIER_) { - // Unknown request code - return; - } - - // Check client permissions - switch (code) { - case +RequestCode::POST_FS_DATA: - case +RequestCode::LATE_START: - case +RequestCode::BOOT_COMPLETE: - case +RequestCode::ZYGOTE_RESTART: - case +RequestCode::SQLITE_CMD: - case +RequestCode::DENYLIST: - case +RequestCode::STOP_DAEMON: - if (!is_root) { - write_int(client, +RespondCode::ROOT_REQUIRED); - return; - } - break; - case +RequestCode::REMOVE_MODULES: - if (!is_root && cred.uid != AID_SHELL) { - write_int(client, +RespondCode::ACCESS_DENIED); - return; - } - break; - case +RequestCode::ZYGISK: - if (!is_zygote) { - // Invalid client context - write_int(client, +RespondCode::ACCESS_DENIED); - return; - } - break; - default: - break; - } - - write_int(client, +RespondCode::OK); - - if (code < +RequestCode::_SYNC_BARRIER_) { - MagiskD::Get().handle_request_sync(client.release(), code); - } else if (code < +RequestCode::_STAGE_BARRIER_) { - exec_task([=, fd = client.release()] { - MagiskD::Get().handle_request_async(fd, code, cred); - }); - } else { - exec_task([=, fd = client.release()] { - MagiskD::Get().boot_stage_handler(fd, code); - }); - } -} - -[[noreturn]] static void daemon_entry() { - // Change process name - set_nice_name("magiskd"); - - rust::daemon_entry(); - SDK_INT = MagiskD::Get().sdk_int(); - - // Get self stat - xstat("/proc/self/exe", &self_st); - - int sockfd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); - sockaddr_un addr = {.sun_family = AF_LOCAL}; - ssprintf(addr.sun_path, sizeof(addr.sun_path), "%s/" MAIN_SOCKET, get_magisk_tmp()); - unlink(addr.sun_path); - if (xbind(sockfd, (sockaddr *) &addr, sizeof(addr))) - exit(1); - chmod(addr.sun_path, 0666); - setfilecon(addr.sun_path, MAGISK_FILE_CON); - xlisten(sockfd, 10); - - // Loop forever to listen for requests - for (;;) { - int client = xaccept4(sockfd, nullptr, nullptr, SOCK_CLOEXEC); - handle_request(client); - } -} - const char *get_magisk_tmp() { static const char *path = nullptr; if (path == nullptr) { @@ -166,9 +50,9 @@ int connect_daemon(int req, bool create) { sockaddr_un addr = {.sun_family = AF_LOCAL}; const char *tmp = get_magisk_tmp(); ssprintf(addr.sun_path, sizeof(addr.sun_path), "%s/" MAIN_SOCKET, tmp); - if (connect(fd, (sockaddr *) &addr, sizeof(addr))) { + if (connect(fd, reinterpret_cast(&addr), sizeof(addr))) { if (!create || getuid() != AID_ROOT) { - LOGE("No daemon is currently running!\n"); + PLOGE("Cannot connect daemon at %s,", addr.sun_path); close(fd); return -1; } @@ -183,10 +67,11 @@ int connect_daemon(int req, bool create) { if (fork_dont_care() == 0) { close(fd); + set_nice_name("magiskd"); daemon_entry(); } - while (connect(fd, (sockaddr *) &addr, sizeof(addr))) + while (connect(fd, reinterpret_cast(&addr), sizeof(addr))) usleep(10000); } write_int(fd, req); diff --git a/native/src/core/daemon.rs b/native/src/core/daemon.rs index 47b71373c..4e3acbca5 100644 --- a/native/src/core/daemon.rs +++ b/native/src/core/daemon.rs @@ -1,12 +1,12 @@ -use crate::UCred; use crate::consts::{ - APP_PACKAGE_NAME, BBPATH, DATABIN, MAGISK_FULL_VER, MAGISK_PROC_CON, MAGISK_VER_CODE, - MAGISK_VERSION, MAIN_CONFIG, MODULEROOT, ROOTMNT, ROOTOVL, SECURE_DIR, + APP_PACKAGE_NAME, BBPATH, DATABIN, MAGISK_FILE_CON, MAGISK_FULL_VER, MAGISK_PROC_CON, + MAGISK_VER_CODE, MAGISK_VERSION, MAIN_CONFIG, MAIN_SOCKET, MODULEROOT, ROOTMNT, ROOTOVL, + SECURE_DIR, }; use crate::db::Sqlite3; use crate::ffi::{ - DbEntryKey, ModuleInfo, RequestCode, check_key_combo, denylist_handler, exec_common_scripts, - exec_module_scripts, get_magisk_tmp, initialize_denylist, scan_deny_apps, + DbEntryKey, ModuleInfo, RequestCode, RespondCode, check_key_combo, denylist_handler, + exec_common_scripts, exec_module_scripts, get_magisk_tmp, initialize_denylist, scan_deny_apps, }; use crate::logging::{android_logging, magisk_logging, setup_logfile, start_log_daemon}; use crate::module::{disable_modules, remove_modules}; @@ -16,22 +16,24 @@ use crate::resetprop::{get_prop, set_prop}; use crate::selinux::{restore_tmpcon, restorecon}; use crate::socket::IpcWrite; use crate::su::SuInfo; +use crate::thread::ThreadPool; use crate::zygisk::ZygiskState; use base::const_format::concatcp; use base::{ - AtomicArc, BufReadExt, FsPathBuilder, ReadExt, ResultExt, Utf8CStr, Utf8CStrBuf, WriteExt, - cstr, error, info, libc, + AtomicArc, BufReadExt, FileAttr, FsPathBuilder, ReadExt, ResultExt, Utf8CStr, Utf8CStrBuf, + WriteExt, cstr, error, info, libc, }; use nix::{ fcntl::OFlag, mount::MsFlags, sys::signal::SigSet, - unistd::{dup2_stderr, dup2_stdin, dup2_stdout, setsid}, + unistd::{dup2_stderr, dup2_stdin, dup2_stdout, getpid, setsid}, }; -use std::fmt::Write as FmtWrite; -use std::fs::File; +use num_traits::AsPrimitive; +use std::fmt::Write as _; use std::io::{BufReader, Write}; -use std::os::fd::{AsFd, FromRawFd, IntoRawFd, OwnedFd}; +use std::os::fd::{AsFd, AsRawFd, IntoRawFd}; +use std::os::unix::net::{UCred, UnixListener, UnixStream}; use std::process::{Command, Stdio, exit}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Mutex, OnceLock}; @@ -87,6 +89,7 @@ pub struct MagiskD { sdk_int: i32, pub is_emulator: bool, is_recovery: bool, + exe_attr: FileAttr, } impl MagiskD { @@ -265,13 +268,10 @@ impl MagiskD { self.zygisk.lock().unwrap().reset(true); } - pub fn boot_stage_handler(&self, client: i32, code: i32) { - // Take ownership - let client = unsafe { OwnedFd::from_raw_fd(client) }; + fn boot_stage_handler(&self, client: UnixStream, code: RequestCode) { // Make sure boot stage execution is always serialized let mut state = self.boot_stage_lock.lock().unwrap(); - let code = RequestCode { repr: code }; match code { RequestCode::POST_FS_DATA => { if check_data() && !state.contains(BootState::PostFsDataDone) { @@ -300,10 +300,7 @@ impl MagiskD { } } - pub fn handle_request_sync(&self, client: i32, code: i32) { - // Take ownership - let mut client = unsafe { File::from_raw_fd(client) }; - let code = RequestCode { repr: code }; + fn handle_request_sync(&self, mut client: UnixStream, code: RequestCode) { match code { RequestCode::CHECK_VERSION => { #[cfg(debug_assertions)] @@ -335,10 +332,7 @@ impl MagiskD { } } - pub fn handle_request_async(&self, client: i32, code: i32, cred: &UCred) { - // Take ownership - let client = unsafe { OwnedFd::from_raw_fd(client) }; - let code = RequestCode { repr: code }; + fn handle_request_async(&self, mut client: UnixStream, code: RequestCode, cred: UCred) { match code { RequestCode::DENYLIST => { denylist_handler(client.into_raw_fd()); @@ -353,14 +347,13 @@ impl MagiskD { self.zygisk.lock().unwrap().reset(false); } RequestCode::SQLITE_CMD => { - self.db_exec_for_cli(client).ok(); + self.db_exec_for_cli(client.into()).ok(); } RequestCode::REMOVE_MODULES => { - let mut file = File::from(client); let mut do_reboot: i32 = 0; - file.read_pod(&mut do_reboot).log_ok(); + client.read_pod(&mut do_reboot).log_ok(); remove_modules(); - file.write_pod(&0).log_ok(); + client.write_pod(&0).log_ok(); if do_reboot != 0 { self.reboot(); } @@ -380,6 +373,106 @@ impl MagiskD { } .ok(); } + + fn is_client(&self, pid: i32) -> bool { + let mut buf = cstr::buf::new::<32>(); + write!(buf, "/proc/{pid}/exe").ok(); + if let Ok(attr) = buf.follow_link().get_attr() { + attr.st.st_dev == self.exe_attr.st.st_dev && attr.st.st_ino == self.exe_attr.st.st_ino + } else { + false + } + } + + fn handle_requests(&'static self, mut client: UnixStream) { + let Ok(cred) = client.peer_cred() else { + // Client died + return; + }; + + // There are no abstractions for SO_PEERSEC yet, call the raw C API. + let mut context = cstr::buf::new::<256>(); + unsafe { + let mut len: libc::socklen_t = context.capacity().as_(); + libc::getsockopt( + client.as_raw_fd(), + libc::SOL_SOCKET, + libc::SO_PEERSEC, + context.as_mut_ptr().cast(), + &mut len, + ); + } + context.rebuild().ok(); + + let is_root = cred.uid == 0; + let is_shell = cred.uid == 2000; + let is_zygote = &context == "u:r:zygote:s0"; + + if !is_root && !is_zygote && !self.is_client(cred.pid.unwrap_or(-1)) { + // Unsupported client state + client.write_pod(&RespondCode::ACCESS_DENIED.repr).log_ok(); + return; + } + + let mut code = -1; + client.read_pod(&mut code).ok(); + if !(0..RequestCode::END.repr).contains(&code) + || code == RequestCode::_SYNC_BARRIER_.repr + || code == RequestCode::_STAGE_BARRIER_.repr + { + // Unknown request code + return; + } + + let code = RequestCode { repr: code }; + + // Permission checks + match code { + RequestCode::POST_FS_DATA + | RequestCode::LATE_START + | RequestCode::BOOT_COMPLETE + | RequestCode::ZYGOTE_RESTART + | RequestCode::SQLITE_CMD + | RequestCode::DENYLIST + | RequestCode::STOP_DAEMON => { + if !is_root { + client.write_pod(&RespondCode::ROOT_REQUIRED.repr).log_ok(); + return; + } + } + RequestCode::REMOVE_MODULES => { + if !is_root && !is_shell { + // Only allow root and ADB shell to remove modules + client.write_pod(&RespondCode::ACCESS_DENIED.repr).log_ok(); + return; + } + } + RequestCode::ZYGISK => { + if !is_zygote { + // Invalid client context + client.write_pod(&RespondCode::ACCESS_DENIED.repr).log_ok(); + return; + } + } + _ => {} + } + + if client.write_pod(&RespondCode::OK.repr).is_err() { + return; + } + + if code.repr < RequestCode::_SYNC_BARRIER_.repr { + self.handle_request_sync(client, code) + } else if code.repr < RequestCode::_STAGE_BARRIER_.repr { + ThreadPool::exec_task(move || { + self.handle_request_async(client, code, cred); + }) + } else { + ThreadPool::exec_task(move || { + self.boot_stage_handler(client, code); + }) + } + } } pub fn daemon_entry() { @@ -448,12 +541,12 @@ pub fn daemon_entry() { .parse::() .unwrap_or(-1); } - info!("* Device API level: {}", sdk_int); + info!("* Device API level: {sdk_int}"); restore_tmpcon().log_ok(); // Escape from cgroup - let pid = unsafe { libc::getpid() }; + let pid = getpid().as_raw(); switch_cgroup("/acct", pid); switch_cgroup("/dev/cg2_bpf", pid); switch_cgroup("/sys/fs/cgroup", pid); @@ -489,13 +582,42 @@ pub fn daemon_entry() { tmp_path.remove_all().ok(); tmp_path.truncate(magisk_tmp.len()); - let magiskd = MagiskD { + let exe_attr = cstr!("/proc/self/exe") + .follow_link() + .get_attr() + .log() + .unwrap_or_default(); + + let daemon = MagiskD { sdk_int, is_emulator, is_recovery, + exe_attr, ..Default::default() }; - MAGISKD.set(magiskd).ok(); + MAGISKD.set(daemon).ok(); + + let sock_path = cstr::buf::new::<64>() + .join_path(get_magisk_tmp()) + .join_path(MAIN_SOCKET); + sock_path.remove().ok(); + + let Ok(sock) = UnixListener::bind(&sock_path).log() else { + exit(1); + }; + + sock_path.follow_link().chmod(0o666).log_ok(); + sock_path.set_secontext(cstr!(MAGISK_FILE_CON)).log_ok(); + + // Loop forever to listen for requests + let daemon = MagiskD::get(); + for client in sock.incoming() { + if let Ok(client) = client.log() { + daemon.handle_requests(client); + } else { + exit(1); + } + } } fn switch_cgroup(cgroup: &str, pid: i32) { @@ -507,7 +629,7 @@ fn switch_cgroup(cgroup: &str, pid: i32) { } if let Ok(mut file) = buf.open(OFlag::O_WRONLY | OFlag::O_APPEND | OFlag::O_CLOEXEC) { buf.clear(); - buf.write_fmt(format_args!("{pid}")).ok(); + write!(buf, "{pid}").ok(); file.write_all(buf.as_bytes()).log_ok(); } } diff --git a/native/src/core/include/core.hpp b/native/src/core/include/core.hpp index 0c6b432c8..6e53aa767 100644 --- a/native/src/core/include/core.hpp +++ b/native/src/core/include/core.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -17,20 +16,14 @@ #define to_app_id(uid) (uid % AID_USER_OFFSET) #define to_user_id(uid) (uid / AID_USER_OFFSET) +#define SDK_INT (MagiskD::Get().sdk_int()) +#define APP_DATA_DIR (SDK_INT >= 24 ? "/data/user_de" : "/data/user") + // Multi-call entrypoints int magisk_main(int argc, char *argv[]); int su_client_main(int argc, char *argv[]); int zygisk_main(int argc, char *argv[]); -// Return codes for daemon -enum class RespondCode : int { - ERROR = -1, - OK = 0, - ROOT_REQUIRED, - ACCESS_DENIED, - END -}; - struct ModuleInfo; // Daemon @@ -72,13 +65,6 @@ bool read_vector(int fd, std::vector &vec) { return xread(fd, vec.data(), size * sizeof(T)) == size * sizeof(T); } -// Thread pool -void init_thread_pool(); -void exec_task(std::function &&task); - -// Daemon handlers -void denylist_handler(int client); - // Scripting void install_apk(Utf8CStr apk); void uninstall_pkg(Utf8CStr pkg); @@ -91,6 +77,7 @@ void clear_pkg(const char *pkg, int user_id); // Denylist extern std::atomic denylist_enforced; int denylist_cli(int argc, char **argv); +void denylist_handler(int client); void initialize_denylist(); void scan_deny_apps(); bool is_deny_target(int uid, std::string_view process); @@ -105,4 +92,4 @@ static inline Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); } static inline rust::String resolve_preinit_dir_rs(Utf8CStr base_dir) { return resolve_preinit_dir(base_dir.c_str()); } -static inline void exec_script_rs(Utf8CStr script) { exec_script(script.data()); } +static inline void exec_script_rs(Utf8CStr script) { exec_script(script.c_str()); } diff --git a/native/src/core/lib.rs b/native/src/core/lib.rs index 34c9f81d9..5b16bea47 100644 --- a/native/src/core/lib.rs +++ b/native/src/core/lib.rs @@ -4,12 +4,11 @@ #![feature(unix_socket_ancillary_data)] #![feature(unix_socket_peek)] #![feature(default_field_values)] +#![feature(peer_credentials_unix_socket)] #![allow(clippy::missing_safety_doc)] use crate::ffi::SuRequest; use crate::socket::Encodable; -use base::libc; -use cxx::{ExternType, type_id}; use daemon::{MagiskD, daemon_entry}; use derive::Decodable; use logging::{android_logging, zygisk_close_logd, zygisk_get_logd, zygisk_logging}; @@ -36,6 +35,7 @@ mod resetprop; mod selinux; mod socket; mod su; +mod thread; mod zygisk; #[allow(clippy::needless_lifetimes)] @@ -66,6 +66,15 @@ pub mod ffi { END, } + #[repr(i32)] + enum RespondCode { + ERROR = -1, + OK = 0, + ROOT_REQUIRED, + ACCESS_DENIED, + END, + } + enum DbEntryKey { RootAccess, SuMultiuserMode, @@ -128,8 +137,6 @@ pub mod ffi { unsafe extern "C++" { #[cxx_name = "Utf8CStr"] type Utf8CStrRef<'a> = base::Utf8CStrRef<'a>; - #[cxx_name = "ucred"] - type UCred = crate::UCred; include!("include/core.hpp"); @@ -190,7 +197,6 @@ pub mod ffi { fn get_prop(name: Utf8CStrRef) -> String; unsafe fn resetprop_main(argc: i32, argv: *mut *mut c_char) -> i32; - #[namespace = "rust"] fn daemon_entry(); } @@ -206,9 +212,6 @@ pub mod ffi { type MagiskD; fn sdk_int(&self) -> i32; fn zygisk_enabled(&self) -> bool; - fn boot_stage_handler(&self, client: i32, code: i32); - fn handle_request_sync(&self, client: i32, code: i32); - fn handle_request_async(&self, client: i32, code: i32, cred: &UCred); fn get_db_setting(&self, key: DbEntryKey) -> i32; #[cxx_name = "set_db_setting"] fn set_db_setting_for_cxx(&self, key: DbEntryKey, value: i32) -> bool; @@ -219,14 +222,6 @@ pub mod ffi { } } -#[repr(transparent)] -pub struct UCred(pub libc::ucred); - -unsafe impl ExternType for UCred { - type Id = type_id!("ucred"); - type Kind = cxx::kind::Trivial; -} - impl SuRequest { fn write_to_fd(&self, fd: i32) { unsafe { diff --git a/native/src/core/logging.rs b/native/src/core/logging.rs index 73ec1e3a9..08e550ae6 100644 --- a/native/src/core/logging.rs +++ b/native/src/core/logging.rs @@ -3,10 +3,10 @@ use crate::ffi::get_magisk_tmp; use crate::logging::LogFile::{Actual, Buffer}; use base::{ FsPathBuilder, LogLevel, LoggedResult, ReadExt, Utf8CStr, Utf8CStrBuf, WriteExt, - const_format::concatcp, cstr, libc, raw_cstr, update_logger, + const_format::concatcp, cstr, libc, new_daemon_thread, raw_cstr, update_logger, }; use bytemuck::{Pod, Zeroable, bytes_of, write_zeroes}; -use libc::{PIPE_BUF, c_char, c_void, localtime_r, sigtimedwait, time_t, timespec, tm}; +use libc::{PIPE_BUF, c_char, localtime_r, sigtimedwait, time_t, timespec, tm}; use nix::{ fcntl::OFlag, sys::signal::{SigSet, SigmaskHow, Signal}, @@ -41,12 +41,9 @@ enum ALogPriority { ANDROID_LOG_SILENT, } -type ThreadEntry = extern "C" fn(*mut c_void) -> *mut c_void; - unsafe extern "C" { fn __android_log_write(prio: i32, tag: *const c_char, msg: *const c_char); fn strftime(buf: *mut c_char, len: usize, fmt: *const c_char, tm: *const tm) -> usize; - fn new_daemon_thread(entry: ThreadEntry, arg: *mut c_void); } fn level_to_prio(level: LogLevel) -> i32 { @@ -226,92 +223,83 @@ impl LogFile { } } -extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void { - fn writer_loop(pipefd: RawFd) -> io::Result<()> { - let mut pipe = unsafe { File::from_raw_fd(pipefd) }; - let mut logfile: LogFile = Buffer(Vec::new()); +fn logfile_write_loop(mut pipe: File) -> io::Result<()> { + let mut logfile: LogFile = Buffer(Vec::new()); - let mut meta = LogMeta::zeroed(); - let mut msg_buf = [0u8; MAX_MSG_LEN]; - let mut aux = cstr::buf::new::<64>(); + let mut meta = LogMeta::zeroed(); + let mut msg_buf = [0u8; MAX_MSG_LEN]; + let mut aux = cstr::buf::new::<64>(); - loop { - // Read request - write_zeroes(&mut meta); - pipe.read_pod(&mut meta)?; + loop { + // Read request + write_zeroes(&mut meta); + pipe.read_pod(&mut meta)?; - if meta.prio < 0 { - if let Buffer(ref mut buf) = logfile { - fs::rename(LOGFILE, concatcp!(LOGFILE, ".bak")).ok(); - let mut out = File::create(LOGFILE)?; - out.write_all(buf.as_slice())?; - logfile = Actual(out); - } - continue; + if meta.prio < 0 { + if let Buffer(ref mut buf) = logfile { + fs::rename(LOGFILE, concatcp!(LOGFILE, ".bak")).ok(); + let mut out = File::create(LOGFILE)?; + out.write_all(buf.as_slice())?; + logfile = Actual(out); } - - if meta.len < 0 || meta.len > MAX_MSG_LEN as i32 { - continue; - } - - // Read the rest of the message - let msg = &mut msg_buf[..(meta.len as usize)]; - pipe.read_exact(msg)?; - - // Start building the log string - aux.clear(); - let prio = - ALogPriority::from_i32(meta.prio).unwrap_or(ALogPriority::ANDROID_LOG_UNKNOWN); - let prio = match prio { - ALogPriority::ANDROID_LOG_VERBOSE => 'V', - ALogPriority::ANDROID_LOG_DEBUG => 'D', - ALogPriority::ANDROID_LOG_INFO => 'I', - ALogPriority::ANDROID_LOG_WARN => 'W', - ALogPriority::ANDROID_LOG_ERROR => 'E', - // Unsupported values, skip - _ => continue, - }; - - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - - // Note: the obvious better implementation is to use the rust chrono crate, however - // the crate cannot fetch the proper local timezone without pulling in a bunch of - // timezone handling code. To reduce binary size, fallback to use localtime_r in libc. - unsafe { - let secs = now.as_secs() as time_t; - let mut tm: tm = std::mem::zeroed(); - if localtime_r(&secs, &mut tm).is_null() { - continue; - } - strftime(aux.as_mut_ptr(), aux.capacity(), raw_cstr!("%m-%d %T"), &tm); - } - - if aux.rebuild().is_ok() { - write!( - aux, - ".{:03} {:5} {:5} {} : ", - now.subsec_millis(), - meta.pid, - meta.tid, - prio - ) - .ok(); - } else { - continue; - } - - let io1 = IoSlice::new(aux.as_bytes()); - let io2 = IoSlice::new(msg); - // We don't need to care the written len because we are writing less than PIPE_BUF - // It's guaranteed to always write the whole thing atomically - let _ = logfile.as_write().write_vectored(&[io1, io2])?; + continue; } - } - writer_loop(arg as RawFd).ok(); - // If any error occurs, shut down the logd pipe - *MAGISK_LOGD_FD.lock().unwrap() = None; - null_mut() + if meta.len < 0 || meta.len > MAX_MSG_LEN as i32 { + continue; + } + + // Read the rest of the message + let msg = &mut msg_buf[..(meta.len as usize)]; + pipe.read_exact(msg)?; + + // Start building the log string + aux.clear(); + let prio = ALogPriority::from_i32(meta.prio).unwrap_or(ALogPriority::ANDROID_LOG_UNKNOWN); + let prio = match prio { + ALogPriority::ANDROID_LOG_VERBOSE => 'V', + ALogPriority::ANDROID_LOG_DEBUG => 'D', + ALogPriority::ANDROID_LOG_INFO => 'I', + ALogPriority::ANDROID_LOG_WARN => 'W', + ALogPriority::ANDROID_LOG_ERROR => 'E', + // Unsupported values, skip + _ => continue, + }; + + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + + // Note: the obvious better implementation is to use the rust chrono crate, however + // the crate cannot fetch the proper local timezone without pulling in a bunch of + // timezone handling code. To reduce binary size, fallback to use localtime_r in libc. + unsafe { + let secs = now.as_secs() as time_t; + let mut tm: tm = std::mem::zeroed(); + if localtime_r(&secs, &mut tm).is_null() { + continue; + } + strftime(aux.as_mut_ptr(), aux.capacity(), raw_cstr!("%m-%d %T"), &tm); + } + + if aux.rebuild().is_ok() { + write!( + aux, + ".{:03} {:5} {:5} {} : ", + now.subsec_millis(), + meta.pid, + meta.tid, + prio + ) + .ok(); + } else { + continue; + } + + let io1 = IoSlice::new(aux.as_bytes()); + let io2 = IoSlice::new(msg); + // We don't need to care the written len because we are writing less than PIPE_BUF + // It's guaranteed to always write the whole thing atomically + let _ = logfile.as_write().write_vectored(&[io1, io2])?; + } } pub fn setup_logfile() { @@ -331,6 +319,14 @@ pub fn start_log_daemon() { .join_path(get_magisk_tmp()) .join_path(LOG_PIPE); + extern "C" fn logfile_writer_thread(arg: usize) -> usize { + let file = unsafe { File::from_raw_fd(arg as RawFd) }; + logfile_write_loop(file).ok(); + // If any error occurs, shut down the logd pipe + *MAGISK_LOGD_FD.lock().unwrap() = None; + 0 + } + let _: LoggedResult<()> = try { path.mkfifo(0o666)?; chown(path.as_utf8_cstr(), Some(Uid::from(0)), Some(Gid::from(0)))?; @@ -338,7 +334,7 @@ pub fn start_log_daemon() { let write = path.open(OFlag::O_WRONLY | OFlag::O_CLOEXEC)?; *MAGISK_LOGD_FD.lock().unwrap() = Some(Arc::new(write)); unsafe { - new_daemon_thread(logfile_writer, read.into_raw_fd() as *mut c_void); + new_daemon_thread(logfile_writer_thread, read.into_raw_fd() as usize); } }; } diff --git a/native/src/core/su/connect.rs b/native/src/core/su/connect.rs index 8b848d060..e64861b5d 100644 --- a/native/src/core/su/connect.rs +++ b/native/src/core/su/connect.rs @@ -7,7 +7,6 @@ use crate::socket::IpcRead; use ExtraVal::{Bool, Int, IntList, Str}; use base::{ BytesExt, FileAttr, LibcReturn, LoggedResult, ResultExt, Utf8CStrBuf, cstr, fork_dont_care, - libc, }; use nix::{ fcntl::OFlag, @@ -15,6 +14,7 @@ use nix::{ }; use num_traits::AsPrimitive; use std::os::fd::AsFd; +use std::os::unix::net::UCred; use std::{fmt::Write, fs::File, process::Command, process::exit}; struct Extra<'a> { @@ -87,7 +87,7 @@ impl Extra<'_> { } pub(super) struct SuAppContext<'a> { - pub(super) cred: libc::ucred, + pub(super) cred: UCred, pub(super) request: &'a SuRequest, pub(super) info: &'a SuInfo, pub(super) settings: &'a mut RootSettings, @@ -167,7 +167,7 @@ impl SuAppContext<'_> { "{}/{}/su_request_{}", get_magisk_tmp(), INTERNAL_DIR, - self.cred.pid + self.cred.pid.unwrap_or(-1) )) .ok(); @@ -192,7 +192,7 @@ impl SuAppContext<'_> { }, Extra { key: "pid", - value: Int(self.cred.pid), + value: Int(self.cred.pid.unwrap_or(-1)), }, ]; self.exec_cmd("request", &extras, false); @@ -230,7 +230,7 @@ impl SuAppContext<'_> { }, Extra { key: "pid", - value: Int(self.cred.pid.as_()), + value: Int(self.cred.pid.unwrap_or(-1).as_()), }, Extra { key: "policy", @@ -257,7 +257,7 @@ impl SuAppContext<'_> { }, Extra { key: "pid", - value: Int(self.cred.pid.as_()), + value: Int(self.cred.pid.unwrap_or(-1).as_()), }, Extra { key: "policy", diff --git a/native/src/core/su/daemon.rs b/native/src/core/su/daemon.rs index 87675da6a..77cf08cc7 100644 --- a/native/src/core/su/daemon.rs +++ b/native/src/core/su/daemon.rs @@ -1,13 +1,12 @@ use super::connect::SuAppContext; use super::db::RootSettings; -use crate::UCred; use crate::daemon::{AID_ROOT, AID_SHELL, MagiskD, to_app_id, to_user_id}; use crate::db::{DbSettings, MultiuserMode, RootAccess}; use crate::ffi::{SuPolicy, SuRequest, exec_root_shell}; use crate::socket::IpcRead; use base::{LoggedResult, ResultExt, WriteExt, debug, error, exit_on_error, libc, warn}; -use std::os::fd::{IntoRawFd, OwnedFd}; -use std::os::unix::net::UnixStream; +use std::os::fd::IntoRawFd; +use std::os::unix::net::{UCred, UnixStream}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; @@ -114,17 +113,14 @@ impl AccessInfo { } impl MagiskD { - pub fn su_daemon_handler(&self, client: OwnedFd, cred: &UCred) { - let cred = cred.0; + pub fn su_daemon_handler(&self, mut client: UnixStream, cred: UCred) { debug!( "su: request from uid=[{}], pid=[{}], client=[{}]", cred.uid, - cred.pid, + cred.pid.unwrap_or(-1), client.as_raw_fd() ); - let mut client = UnixStream::from(client); - let mut req = match client.read_decodable::().log() { Ok(req) => req, Err(_) => { @@ -174,7 +170,12 @@ impl MagiskD { // ack client.write_pod(&0).ok(); - exec_root_shell(client.into_raw_fd(), cred.pid, &mut req, info.cfg.mnt_ns); + exec_root_shell( + client.into_raw_fd(), + cred.pid.unwrap_or(-1), + &mut req, + info.cfg.mnt_ns, + ); return; } if child < 0 { diff --git a/native/src/core/thread.cpp b/native/src/core/thread.cpp deleted file mode 100644 index 006f7aecd..000000000 --- a/native/src/core/thread.cpp +++ /dev/null @@ -1,98 +0,0 @@ -// Cached thread pool implementation - -#include - -#include - -using namespace std; - -#define THREAD_IDLE_MAX_SEC 60 -#define CORE_POOL_SIZE 3 - -static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t send_task = PTHREAD_COND_INITIALIZER_MONOTONIC_NP; -static pthread_cond_t recv_task = PTHREAD_COND_INITIALIZER_MONOTONIC_NP; - -// The following variables should be guarded by lock -static int idle_threads = 0; -static int total_threads = 0; -static function pending_task; - -static void operator+=(timespec &a, const timespec &b) { - a.tv_sec += b.tv_sec; - a.tv_nsec += b.tv_nsec; - if (a.tv_nsec >= 1000000000L) { - a.tv_sec++; - a.tv_nsec -= 1000000000L; - } -} - -static void reset_pool() { - pthread_mutex_unlock(&lock); - pthread_mutex_destroy(&lock); - pthread_mutex_init(&lock, nullptr); - pthread_cond_destroy(&send_task); - send_task = PTHREAD_COND_INITIALIZER_MONOTONIC_NP; - pthread_cond_destroy(&recv_task); - recv_task = PTHREAD_COND_INITIALIZER_MONOTONIC_NP; - idle_threads = 0; - total_threads = 0; - pending_task = nullptr; -} - -static void *thread_pool_loop(void * const is_core_pool) { - // Block all signals - sigset_t mask; - sigfillset(&mask); - - for (;;) { - // Restore sigmask - pthread_sigmask(SIG_SETMASK, &mask, nullptr); - function local_task; - { - mutex_guard g(lock); - ++idle_threads; - if (!pending_task) { - if (is_core_pool) { - pthread_cond_wait(&send_task, &lock); - } else { - timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - ts += { THREAD_IDLE_MAX_SEC, 0 }; - if (pthread_cond_timedwait(&send_task, &lock, &ts) == ETIMEDOUT) { - // Terminate thread after max idle time - --idle_threads; - --total_threads; - return nullptr; - } - } - } - if (pending_task) { - local_task.swap(pending_task); - pthread_cond_signal(&recv_task); - } - --idle_threads; - } - if (local_task) - local_task(); - if (getpid() == gettid()) - exit(0); - } -} - -void init_thread_pool() { - pthread_atfork(nullptr, nullptr, &reset_pool); -} - -void exec_task(function &&task) { - mutex_guard g(lock); - pending_task.swap(task); - if (idle_threads == 0) { - ++total_threads; - long is_core_pool = total_threads <= CORE_POOL_SIZE; - new_daemon_thread(thread_pool_loop, (void *) is_core_pool); - } else { - pthread_cond_signal(&send_task); - } - pthread_cond_wait(&recv_task, &lock); -} diff --git a/native/src/core/thread.rs b/native/src/core/thread.rs new file mode 100644 index 000000000..b1f955a94 --- /dev/null +++ b/native/src/core/thread.rs @@ -0,0 +1,98 @@ +use base::{ResultExt, new_daemon_thread}; +use nix::{sys::signal::SigSet, unistd::getpid, unistd::gettid}; +use std::sync::{Condvar, LazyLock, Mutex, WaitTimeoutResult}; +use std::time::Duration; + +static THREAD_POOL: LazyLock = LazyLock::new(ThreadPool::default); + +const THREAD_IDLE_MAX_SEC: u64 = 60; +const CORE_POOL_SIZE: i32 = 3; + +#[derive(Default)] +pub struct ThreadPool { + task_is_some: Condvar, + task_is_none: Condvar, + info: Mutex, +} + +#[derive(Default)] +struct PoolInfo { + idle_threads: i32, + total_threads: i32, + task: Option>, +} + +impl ThreadPool { + fn pool_loop(&self, is_core_pool: bool) { + let mask = SigSet::all(); + + loop { + // Always restore the sigmask to block all signals + mask.thread_set_mask().log_ok(); + + let task: Option>; + { + let mut info = self.info.lock().unwrap(); + info.idle_threads += 1; + if info.task.is_none() { + if is_core_pool { + // Core pool never closes, wait forever. + info = self.task_is_some.wait(info).unwrap(); + } else { + let dur = Duration::from_secs(THREAD_IDLE_MAX_SEC); + let result: WaitTimeoutResult; + (info, result) = self.task_is_some.wait_timeout(info, dur).unwrap(); + if result.timed_out() { + // Terminate thread after timeout + info.idle_threads -= 1; + info.total_threads -= 1; + return; + } + } + } + task = info.task.take(); + self.task_is_none.notify_one(); + info.idle_threads -= 1; + } + if let Some(task) = task { + task(); + } + if getpid() == gettid() { + // This meant the current thread forked and became the main thread, exit + std::process::exit(0); + } + } + } + + fn exec_task_impl(&self, f: impl FnOnce() + Send + 'static) { + extern "C" fn pool_loop_raw(arg: usize) -> usize { + let is_core_pool = arg != 0; + THREAD_POOL.pool_loop(is_core_pool); + 0 + } + + let mut info = self.info.lock().unwrap(); + while info.task.is_some() { + // Wait until task is none + info = self.task_is_none.wait(info).unwrap(); + } + info.task = Some(Box::new(f)); + if info.idle_threads == 0 { + info.total_threads += 1; + let is_core_thread = if info.total_threads <= CORE_POOL_SIZE { + 1_usize + } else { + 0_usize + }; + unsafe { + new_daemon_thread(pool_loop_raw, is_core_thread); + } + } else { + self.task_is_some.notify_one(); + } + } + + pub fn exec_task(f: impl FnOnce() + Send + 'static) { + THREAD_POOL.exec_task_impl(f); + } +} diff --git a/native/src/core/zygisk/daemon.rs b/native/src/core/zygisk/daemon.rs index a79bc5bbb..4bd60ebf6 100644 --- a/native/src/core/zygisk/daemon.rs +++ b/native/src/core/zygisk/daemon.rs @@ -10,7 +10,7 @@ use base::{ }; use nix::fcntl::OFlag; use std::fmt::Write; -use std::os::fd::{AsRawFd, OwnedFd, RawFd}; +use std::os::fd::{AsRawFd, RawFd}; use std::os::unix::net::UnixStream; use std::ptr; use std::sync::atomic::Ordering; @@ -158,8 +158,7 @@ impl ZygiskState { } impl MagiskD { - pub fn zygisk_handler(&self, client: OwnedFd) { - let mut client = UnixStream::from(client); + pub fn zygisk_handler(&self, mut client: UnixStream) { let _: LoggedResult<()> = try { let code = ZygiskRequest { repr: client.read_decodable()?, diff --git a/native/src/core/zygisk/entry.cpp b/native/src/core/zygisk/entry.cpp index ccb4e6400..54b5b5364 100644 --- a/native/src/core/zygisk/entry.cpp +++ b/native/src/core/zygisk/entry.cpp @@ -9,12 +9,13 @@ using namespace std; +using comp_entry = void(*)(int); +extern "C" void exec_companion_entry(int, comp_entry); + static void zygiskd(int socket) { if (getuid() != 0 || fcntl(socket, F_GETFD) < 0) exit(-1); - init_thread_pool(); - #if defined(__LP64__) set_nice_name("zygiskd64"); LOGI("* Launching zygiskd64\n"); @@ -24,7 +25,6 @@ static void zygiskd(int socket) { #endif // Load modules - using comp_entry = void(*)(int); vector modules; { auto module_fds = recv_fds(socket); @@ -65,20 +65,7 @@ static void zygiskd(int socket) { } int module_id = read_int(client); if (module_id >= 0 && module_id < modules.size() && modules[module_id]) { - exec_task([=, entry = modules[module_id]] { - struct stat s1; - fstat(client, &s1); - entry(client); - // 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 (struct stat s2; fstat(client, &s2) == 0) { - if (s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino) { - close(client); - } - } - }); + exec_companion_entry(client, modules[module_id]); } else { close(client); } diff --git a/native/src/core/zygisk/mod.rs b/native/src/core/zygisk/mod.rs index bfd2e148b..bb4e02a99 100644 --- a/native/src/core/zygisk/mod.rs +++ b/native/src/core/zygisk/mod.rs @@ -1,3 +1,28 @@ mod daemon; +use crate::thread::ThreadPool; +use base::{fd_get_attr, libc}; pub use daemon::{ZygiskState, zygisk_should_load_module}; +use std::os::fd::RawFd; + +#[unsafe(no_mangle)] +extern "C" fn exec_companion_entry(client: RawFd, companion_handler: extern "C" fn(RawFd)) { + ThreadPool::exec_task(move || { + let Ok(s1) = fd_get_attr(client) else { + return; + }; + + companion_handler(client); + + // 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(s2) = fd_get_attr(client) + && s1.st.st_dev == s2.st.st_dev + && s1.st.st_ino == s2.st.st_ino + { + unsafe { libc::close(client) }; + } + }); +} diff --git a/native/src/include/consts.hpp b/native/src/include/consts.hpp index d340f3aae..ce5e3c76b 100644 --- a/native/src/include/consts.hpp +++ b/native/src/include/consts.hpp @@ -3,7 +3,6 @@ #define JAVA_PACKAGE_NAME "com.topjohnwu.magisk" #define SECURE_DIR "/data/adb" #define MODULEROOT SECURE_DIR "/modules" -#define MODULEUPGRADE SECURE_DIR "/modules_update" #define DATABIN SECURE_DIR "/magisk" #define MAGISKDB SECURE_DIR "/magisk.db" @@ -14,7 +13,6 @@ #define DEVICEDIR INTLROOT "/device" #define PREINITDEV DEVICEDIR "/preinit" #define WORKERDIR INTLROOT "/worker" -#define MODULEMNT INTLROOT "/modules" #define BBPATH INTLROOT "/busybox" #define ROOTOVL INTLROOT "/rootdir" #define SHELLPTS INTLROOT "/pts" @@ -32,8 +30,3 @@ constexpr const char *applet_names[] = { "su", "resetprop", nullptr }; // Unconstrained file type that anyone can access #define SEPOL_FILE_TYPE "magisk_file" #define MAGISK_FILE_CON "u:object_r:" SEPOL_FILE_TYPE ":s0" -// Log pipe that only root and zygote can open -#define SEPOL_LOG_TYPE "magisk_log_file" - -extern int SDK_INT; -#define APP_DATA_DIR (SDK_INT >= 24 ? "/data/user_de" : "/data/user") diff --git a/native/src/include/consts.rs b/native/src/include/consts.rs index 0824eacd9..c7dc6531a 100644 --- a/native/src/include/consts.rs +++ b/native/src/include/consts.rs @@ -27,6 +27,7 @@ pub const MODULEMNT: &str = concatcp!(INTERNAL_DIR, "/modules"); pub const WORKERDIR: &str = concatcp!(INTERNAL_DIR, "/worker"); pub const BBPATH: &str = concatcp!(INTERNAL_DIR, "/busybox"); pub const DEVICEDIR: &str = concatcp!(INTERNAL_DIR, "/device"); +pub const MAIN_SOCKET: &str = concatcp!(DEVICEDIR, "/socket"); pub const PREINITDEV: &str = concatcp!(DEVICEDIR, "/preinit"); pub const LOG_PIPE: &str = concatcp!(DEVICEDIR, "/log"); pub const ROOTOVL: &str = concatcp!(INTERNAL_DIR, "/rootdir");