Migrate daemon_entry to Rust

This commit is contained in:
topjohnwu
2025-09-15 12:37:29 -07:00
committed by John Wu
parent 7057d4c7f1
commit 8b49eda85a
16 changed files with 409 additions and 412 deletions

View File

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

View File

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

View File

@@ -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<sockaddr *>(&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<sockaddr *>(&addr), sizeof(addr)))
usleep(10000);
}
write_int(fd, req);

View File

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

View File

@@ -1,7 +1,6 @@
#pragma once
#include <sys/socket.h>
#include <poll.h>
#include <string>
#include <atomic>
#include <functional>
@@ -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<T> &vec) {
return xread(fd, vec.data(), size * sizeof(T)) == size * sizeof(T);
}
// Thread pool
void init_thread_pool();
void exec_task(std::function<void()> &&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<bool> 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()); }

View File

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

View File

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

View File

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

View File

@@ -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::<SuRequest>().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 {

View File

@@ -1,98 +0,0 @@
// Cached thread pool implementation
#include <base.hpp>
#include <core.hpp>
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<void()> 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<void()> 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<void()> &&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);
}

98
native/src/core/thread.rs Normal file
View File

@@ -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<ThreadPool> = 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<PoolInfo>,
}
#[derive(Default)]
struct PoolInfo {
idle_threads: i32,
total_threads: i32,
task: Option<Box<dyn FnOnce() + Send>>,
}
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<Box<dyn FnOnce() + Send>>;
{
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);
}
}

View File

@@ -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()?,

View File

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

View File

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

View File

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

View File

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