C++/Rust 2 way binding for MagiskD

This commit is contained in:
topjohnwu 2023-11-17 13:35:50 -08:00
parent 6c0966b795
commit 8d5b9e5329
11 changed files with 234 additions and 176 deletions

View File

@ -325,3 +325,9 @@ void exec_command_async(Args &&...args) {
};
exec_command(exec);
}
template <typename T>
constexpr auto operator+(T e) noexcept ->
std::enable_if_t<std::is_enum<T>::value, std::underlying_type_t<T>> {
return static_cast<std::underlying_type_t<T>>(e);
}

View File

@ -14,17 +14,6 @@
using namespace std;
// Boot stage state
enum : int {
FLAG_NONE = 0,
FLAG_POST_FS_DATA_DONE = (1 << 0),
FLAG_LATE_START_DONE = (1 << 1),
FLAG_BOOT_COMPLETE = (1 << 2),
FLAG_SAFE_MODE = (1 << 3),
};
static int boot_state = FLAG_NONE;
bool zygisk_enabled = false;
/*********
@ -243,31 +232,6 @@ static bool magisk_env() {
return true;
}
static bool check_data() {
bool mnt = false;
file_readline("/proc/mounts", [&](string_view s) {
if (str_contains(s, " /data ") && !str_contains(s, "tmpfs")) {
mnt = true;
return false;
}
return true;
});
if (!mnt)
return false;
auto crypto = get_prop("ro.crypto.state");
if (!crypto.empty()) {
if (crypto != "encrypted") {
// Unencrypted, we can directly access data
return true;
} else {
// Encrypted, check whether vold is started
return !get_prop("init.svc.vold").empty();
}
}
// ro.crypto.state is not set, assume it's unencrypted
return true;
}
void unlock_blocks() {
int fd, dev, OFF = 0;
@ -341,10 +305,7 @@ static bool check_key_combo() {
extern int disable_deny();
void MagiskD::post_fs_data() const {
if (!check_data())
return;
setup_logfile();
as_rust().setup_logfile();
LOGI("** post-fs-data mode running\n");
@ -364,7 +325,7 @@ void MagiskD::post_fs_data() const {
if (get_prop("persist.sys.safemode", true) == "1" ||
get_prop("ro.sys.safemode") == "1" || check_key_combo()) {
boot_state |= FLAG_SAFE_MODE;
as_rust().enable_safe_mode();
// Disable all modules and denylist so next boot will be clean
disable_modules();
disable_deny();
@ -380,23 +341,19 @@ void MagiskD::post_fs_data() const {
early_abort:
// We still do magic mount because root itself might need it
load_modules();
boot_state |= FLAG_POST_FS_DATA_DONE;
}
void MagiskD::late_start() const {
setup_logfile();
as_rust().setup_logfile();
LOGI("** late_start service mode running\n");
exec_common_scripts("service");
exec_module_scripts("service");
boot_state |= FLAG_LATE_START_DONE;
}
void MagiskD::boot_complete() const {
boot_state |= FLAG_BOOT_COMPLETE;
setup_logfile();
as_rust().setup_logfile();
LOGI("** boot-complete triggered\n");
@ -410,30 +367,3 @@ void MagiskD::boot_complete() const {
reset_zygisk(true);
}
void boot_stage_handler(int client, int code) {
// Make sure boot stage execution is always serialized
static pthread_mutex_t stage_lock = PTHREAD_MUTEX_INITIALIZER;
mutex_guard lock(stage_lock);
MagiskD daemon;
switch (code) {
case MainRequest::POST_FS_DATA:
if ((boot_state & FLAG_POST_FS_DATA_DONE) == 0)
daemon.post_fs_data();
close(client);
break;
case MainRequest::LATE_START:
close(client);
if ((boot_state & FLAG_POST_FS_DATA_DONE) && (boot_state & FLAG_SAFE_MODE) == 0)
daemon.late_start();
break;
case MainRequest::BOOT_COMPLETE:
close(client);
if ((boot_state & FLAG_SAFE_MODE) == 0)
daemon.boot_complete();
break;
default:
__builtin_unreachable();
}
}

View File

@ -128,8 +128,20 @@ static void poll_ctrl_handler(pollfd *pfd) {
}
}
const MagiskD &MagiskD::get() {
return *reinterpret_cast<const MagiskD*>(&rust::get_magiskd());
}
const rust::MagiskD &MagiskD::as_rust() const {
return *reinterpret_cast<const rust::MagiskD*>(this);
}
void MagiskD::boot_stage_handler(int client, int code) const {
as_rust().boot_stage_handler(client, code);
}
void MagiskD::reboot() const {
if (is_recovery())
if (as_rust().is_recovery())
exec_command_sync("/system/bin/reboot", "recovery");
else
exec_command_sync("/system/bin/reboot");
@ -137,34 +149,33 @@ void MagiskD::reboot() const {
static void handle_request_async(int client, int code, const sock_cred &cred) {
switch (code) {
case MainRequest::DENYLIST:
case +RequestCode::DENYLIST:
denylist_handler(client, &cred);
break;
case MainRequest::SUPERUSER:
case +RequestCode::SUPERUSER:
su_daemon_handler(client, &cred);
break;
case MainRequest::ZYGOTE_RESTART:
case +RequestCode::ZYGOTE_RESTART:
LOGI("** zygote restarted\n");
pkg_xml_ino = 0;
prune_su_access();
reset_zygisk(false);
close(client);
break;
case MainRequest::SQLITE_CMD:
case +RequestCode::SQLITE_CMD:
exec_sql(client);
break;
case MainRequest::REMOVE_MODULES: {
case +RequestCode::REMOVE_MODULES: {
int do_reboot = read_int(client);
remove_modules();
write_int(client, 0);
close(client);
if (do_reboot) {
MagiskD daemon;
daemon.reboot();
MagiskD::get().reboot();
}
break;
}
case MainRequest::ZYGISK:
case +RequestCode::ZYGISK:
zygisk_handler(client, &cred);
break;
default:
@ -174,20 +185,20 @@ static void handle_request_async(int client, int code, const sock_cred &cred) {
static void handle_request_sync(int client, int code) {
switch (code) {
case MainRequest::CHECK_VERSION:
case +RequestCode::CHECK_VERSION:
#if MAGISK_DEBUG
write_string(client, MAGISK_VERSION ":MAGISK:D");
#else
write_string(client, MAGISK_VERSION ":MAGISK:R");
#endif
break;
case MainRequest::CHECK_VERSION_CODE:
case +RequestCode::CHECK_VERSION_CODE:
write_int(client, MAGISK_VER_CODE);
break;
case MainRequest::START_DAEMON:
case +RequestCode::START_DAEMON:
rust::get_magiskd().setup_logfile();
break;
case MainRequest::STOP_DAEMON: {
case +RequestCode::STOP_DAEMON: {
// Unmount all overlays
denylist_handler(-1, nullptr);
@ -236,42 +247,42 @@ static void handle_request(pollfd *pfd) {
if (!is_root && !is_zygote && !is_client(cred.pid)) {
// Unsupported client state
write_int(client, MainResponse::ACCESS_DENIED);
write_int(client, +RespondCode::ACCESS_DENIED);
return;
}
code = read_int(client);
if (code < 0 || code >= MainRequest::END ||
code == MainRequest::_SYNC_BARRIER_ ||
code == MainRequest::_STAGE_BARRIER_) {
if (code < 0 || code >= +RequestCode::END ||
code == +RequestCode::_SYNC_BARRIER_ ||
code == +RequestCode::_STAGE_BARRIER_) {
// Unknown request code
return;
}
// Check client permissions
switch (code) {
case MainRequest::POST_FS_DATA:
case MainRequest::LATE_START:
case MainRequest::BOOT_COMPLETE:
case MainRequest::ZYGOTE_RESTART:
case MainRequest::SQLITE_CMD:
case MainRequest::DENYLIST:
case MainRequest::STOP_DAEMON:
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, MainResponse::ROOT_REQUIRED);
write_int(client, +RespondCode::ROOT_REQUIRED);
return;
}
break;
case MainRequest::REMOVE_MODULES:
case +RequestCode::REMOVE_MODULES:
if (!is_root && cred.uid != AID_SHELL) {
write_int(client, MainResponse::ACCESS_DENIED);
write_int(client, +RespondCode::ACCESS_DENIED);
return;
}
break;
case MainRequest::ZYGISK:
case +RequestCode::ZYGISK:
if (!is_zygote) {
// Invalid client context
write_int(client, MainResponse::ACCESS_DENIED);
write_int(client, +RespondCode::ACCESS_DENIED);
return;
}
break;
@ -279,14 +290,16 @@ static void handle_request(pollfd *pfd) {
break;
}
write_int(client, MainResponse::OK);
write_int(client, +RespondCode::OK);
if (code < MainRequest::_SYNC_BARRIER_) {
if (code < +RequestCode::_SYNC_BARRIER_) {
handle_request_sync(client, code);
} else if (code < MainRequest::_STAGE_BARRIER_) {
} else if (code < +RequestCode::_STAGE_BARRIER_) {
exec_task([=, fd = client.release()] { handle_request_async(fd, code, cred); });
} else {
exec_task([=, fd = client.release()] { boot_stage_handler(fd, code); });
exec_task([=, fd = client.release()] {
MagiskD::get().boot_stage_handler(fd, code);
});
}
}
@ -460,20 +473,20 @@ int connect_daemon(int req, bool create) {
}
write_int(fd, req);
int res = read_int(fd);
if (res < MainResponse::ERROR || res >= MainResponse::END)
res = MainResponse::ERROR;
if (res < +RespondCode::ERROR || res >= +RespondCode::END)
res = +RespondCode::ERROR;
switch (res) {
case MainResponse::OK:
case +RespondCode::OK:
break;
case MainResponse::ERROR:
case +RespondCode::ERROR:
LOGE("Daemon error\n");
close(fd);
return -1;
case MainResponse::ROOT_REQUIRED:
case +RespondCode::ROOT_REQUIRED:
LOGE("Root is required for this operation\n");
close(fd);
return -1;
case MainResponse::ACCESS_DENIED:
case +RespondCode::ACCESS_DENIED:
LOGE("Access denied\n");
close(fd);
return -1;

View File

@ -1,23 +1,51 @@
use std::fs::File;
use std::io;
use std::io::BufReader;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Mutex, OnceLock};
use std::{io, mem};
use base::libc::{O_CLOEXEC, O_RDONLY};
use base::{
cstr, BufReadExt, Directory, FsPathBuf, ResultExt, Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr,
Utf8CStrBufRef, WalkResult,
cstr, libc, open_fd, BufReadExt, Directory, FsPathBuf, ResultExt, Utf8CStr, Utf8CStrBuf,
Utf8CStrBufArr, Utf8CStrBufRef, WalkResult,
};
use crate::ffi::{CxxMagiskD, RequestCode};
use crate::logging::magisk_logging;
use crate::{get_prop, MAIN_CONFIG};
// Global magiskd singleton
pub static MAGISKD: OnceLock<MagiskD> = OnceLock::new();
#[repr(u32)]
enum BootState {
PostFsDataDone = (1 << 0),
LateStartDone = (1 << 1),
BootComplete = (1 << 2),
SafeMode = (1 << 3),
}
#[derive(Default)]
#[repr(transparent)]
struct BootStateFlags(AtomicU32);
impl BootStateFlags {
fn contains(&self, stage: BootState) -> bool {
let v = self.0.load(Ordering::Relaxed);
(v & stage as u32) != 0
}
fn set(&self, stage: BootState) {
let v = self.0.load(Ordering::Relaxed);
self.0.store(v | stage as u32, Ordering::Relaxed);
}
}
#[derive(Default)]
pub struct MagiskD {
pub logd: Mutex<Option<File>>,
boot_stage_lock: Mutex<()>,
boot_state_flags: BootStateFlags,
is_emulator: bool,
is_recovery: bool,
}
@ -30,6 +58,50 @@ impl MagiskD {
pub fn is_recovery(&self) -> bool {
self.is_recovery
}
pub fn enable_safe_mode(&self) {
self.boot_state_flags.set(BootState::SafeMode)
}
pub fn boot_stage_handler(&self, client: i32, code: i32) {
// Make sure boot stage execution is always serialized
let _guard = self.boot_stage_lock.lock().unwrap();
let code = RequestCode { repr: code };
match code {
RequestCode::POST_FS_DATA => {
if check_data() && !self.boot_state_flags.contains(BootState::PostFsDataDone) {
self.as_cxx().post_fs_data();
self.boot_state_flags.set(BootState::PostFsDataDone);
}
unsafe { libc::close(client) };
}
RequestCode::LATE_START => {
unsafe { libc::close(client) };
if self.boot_state_flags.contains(BootState::PostFsDataDone)
&& !self.boot_state_flags.contains(BootState::SafeMode)
{
self.as_cxx().late_start();
self.boot_state_flags.set(BootState::LateStartDone);
}
}
RequestCode::BOOT_COMPLETE => {
unsafe { libc::close(client) };
if !self.boot_state_flags.contains(BootState::SafeMode) {
self.boot_state_flags.set(BootState::BootComplete);
self.as_cxx().boot_complete()
}
}
_ => {
unsafe { libc::close(client) };
}
}
}
#[inline(always)]
fn as_cxx(&self) -> &CxxMagiskD {
unsafe { mem::transmute(self) }
}
}
mod cxx_extern {
@ -69,15 +141,46 @@ pub fn daemon_entry() {
}
let magiskd = MagiskD {
logd: Default::default(),
is_emulator,
is_recovery,
..Default::default()
};
magiskd.start_log_daemon();
MAGISKD.set(magiskd).ok();
magisk_logging();
}
fn check_data() -> bool {
if let Ok(fd) = open_fd!(cstr!("/proc/mounts"), O_RDONLY | O_CLOEXEC) {
let file = File::from(fd);
let mut mnt = false;
BufReader::new(file).foreach_lines(|line| {
if line.contains(" /data ") && !line.contains("tmpfs") {
mnt = true;
return false;
}
true
});
if !mnt {
return false;
}
let crypto = get_prop(cstr!("ro.crypto.state"), false);
return if !crypto.is_empty() {
if crypto != "encrypted" {
// Unencrypted, we can directly access data
true
} else {
// Encrypted, check whether vold is started
!get_prop(cstr!("init.svc.vold"), false).is_empty()
}
} else {
// ro.crypto.state is not set, assume it's unencrypted
true
};
}
false
}
pub fn get_magiskd() -> &'static MagiskD {
unsafe { MAGISKD.get().unwrap_unchecked() }
}

View File

@ -92,7 +92,7 @@ int denylist_cli(int argc, char **argv) {
}
// Send request
int fd = connect_daemon(MainRequest::DENYLIST);
int fd = connect_daemon(+RequestCode::DENYLIST);
write_int(fd, req);
if (req == DenyRequest::ADD || req == DenyRequest::REMOVE) {
write_string(fd, argv[2]);

View File

@ -8,7 +8,6 @@
#include <functional>
#include "socket.hpp"
#include "../core-rs.hpp"
#define AID_ROOT 0
#define AID_SHELL 2000
@ -19,11 +18,17 @@
#define to_app_id(uid) (uid % AID_USER_OFFSET)
#define to_user_id(uid) (uid / AID_USER_OFFSET)
namespace rust {
struct MagiskD;
}
struct MagiskD {
// Make sure only references can exist
~MagiskD() = delete;
// Binding to Rust
void setup_logfile() const noexcept { impl.setup_logfile(); }
bool is_emulator() const noexcept { return impl.is_emulator(); }
bool is_recovery() const noexcept { return impl.is_recovery(); }
static const MagiskD &get();
void boot_stage_handler(int client, int code) const;
// C++ implementation
void reboot() const;
@ -32,46 +37,17 @@ struct MagiskD {
void boot_complete() const;
private:
const rust::MagiskD &impl = rust::get_magiskd();
const rust::MagiskD &as_rust() const;
};
// Daemon command codes
namespace MainRequest {
enum : int {
START_DAEMON,
CHECK_VERSION,
CHECK_VERSION_CODE,
STOP_DAEMON,
_SYNC_BARRIER_,
SUPERUSER,
ZYGOTE_RESTART,
DENYLIST,
SQLITE_CMD,
REMOVE_MODULES,
ZYGISK,
_STAGE_BARRIER_,
POST_FS_DATA,
LATE_START,
BOOT_COMPLETE,
END,
};
}
// Return codes for daemon
namespace MainResponse {
enum : int {
enum class RespondCode : int {
ERROR = -1,
OK = 0,
ROOT_REQUIRED,
ACCESS_DENIED,
END
};
}
struct module_info {
std::string name;
@ -139,3 +115,6 @@ int denylist_cli(int argc, char **argv);
void initialize_denylist();
bool is_deny_target(int uid, std::string_view process);
void revert_unmount();
// Include last to prevent recursive include issues
#include "../core-rs.hpp"

View File

@ -18,6 +18,31 @@ mod resetprop;
#[cxx::bridge]
pub mod ffi {
#[repr(i32)]
enum RequestCode {
START_DAEMON,
CHECK_VERSION,
CHECK_VERSION_CODE,
STOP_DAEMON,
_SYNC_BARRIER_,
SUPERUSER,
ZYGOTE_RESTART,
DENYLIST,
SQLITE_CMD,
REMOVE_MODULES,
ZYGISK,
_STAGE_BARRIER_,
POST_FS_DATA,
LATE_START,
BOOT_COMPLETE,
END,
}
extern "C++" {
include!("include/resetprop.hpp");
@ -27,6 +52,16 @@ pub mod ffi {
unsafe fn prop_cb_exec(cb: Pin<&mut PropCb>, name: *const c_char, value: *const c_char);
}
unsafe extern "C++" {
include!("include/core.hpp");
#[cxx_name = "MagiskD"]
type CxxMagiskD;
fn post_fs_data(self: &CxxMagiskD);
fn late_start(self: &CxxMagiskD);
fn boot_complete(self: &CxxMagiskD);
}
extern "Rust" {
fn rust_test_entry();
fn android_logging();
@ -48,11 +83,11 @@ pub mod ffi {
type MagiskD;
fn get_magiskd() -> &'static MagiskD;
fn get_log_pipe(self: &MagiskD) -> i32;
fn close_log_pipe(self: &MagiskD);
fn setup_logfile(self: &MagiskD);
fn is_emulator(self: &MagiskD) -> bool;
fn is_recovery(self: &MagiskD) -> bool;
fn boot_stage_handler(self: &MagiskD, client: i32, code: i32);
fn enable_safe_mode(self: &MagiskD);
}
}

View File

@ -3,7 +3,7 @@ use std::ffi::{c_char, c_void};
use std::fmt::Write as FmtWrite;
use std::fs::File;
use std::io::{IoSlice, Read, Write};
use std::os::fd::{AsRawFd, FromRawFd, RawFd};
use std::os::fd::{FromRawFd, RawFd};
use std::ptr::null_mut;
use std::sync::atomic::{AtomicI32, Ordering};
use std::{fs, io};
@ -382,14 +382,6 @@ impl MagiskD {
}
}
pub fn get_log_pipe(&self) -> RawFd {
self.logd
.lock()
.unwrap()
.as_ref()
.map_or(-1, |s| s.as_raw_fd())
}
pub fn close_log_pipe(&self) {
*self.logd.lock().unwrap() = None;
}

View File

@ -58,12 +58,12 @@ int magisk_main(int argc, char *argv[]) {
#endif
return 0;
} else if (argv[1] == "-v"sv) {
int fd = connect_daemon(MainRequest::CHECK_VERSION);
int fd = connect_daemon(+RequestCode::CHECK_VERSION);
string v = read_string(fd);
printf("%s\n", v.data());
return 0;
} else if (argv[1] == "-V"sv) {
int fd = connect_daemon(MainRequest::CHECK_VERSION_CODE);
int fd = connect_daemon(+RequestCode::CHECK_VERSION_CODE);
printf("%d\n", read_int(fd));
return 0;
} else if (argv[1] == "--list"sv) {
@ -83,29 +83,29 @@ int magisk_main(int argc, char *argv[]) {
cp_afc(argv[2], argv[3]);
return 0;
} else if (argv[1] == "--daemon"sv) {
close(connect_daemon(MainRequest::START_DAEMON, true));
close(connect_daemon(+RequestCode::START_DAEMON, true));
return 0;
} else if (argv[1] == "--stop"sv) {
int fd = connect_daemon(MainRequest::STOP_DAEMON);
int fd = connect_daemon(+RequestCode::STOP_DAEMON);
return read_int(fd);
} else if (argv[1] == "--post-fs-data"sv) {
int fd = connect_daemon(MainRequest::POST_FS_DATA, true);
int fd = connect_daemon(+RequestCode::POST_FS_DATA, true);
struct pollfd pfd = { fd, POLLIN, 0 };
poll(&pfd, 1, 1000 * POST_FS_DATA_WAIT_TIME);
return 0;
} else if (argv[1] == "--service"sv) {
close(connect_daemon(MainRequest::LATE_START, true));
close(connect_daemon(+RequestCode::LATE_START, true));
return 0;
} else if (argv[1] == "--boot-complete"sv) {
close(connect_daemon(MainRequest::BOOT_COMPLETE));
close(connect_daemon(+RequestCode::BOOT_COMPLETE));
return 0;
} else if (argv[1] == "--zygote-restart"sv) {
close(connect_daemon(MainRequest::ZYGOTE_RESTART));
close(connect_daemon(+RequestCode::ZYGOTE_RESTART));
return 0;
} else if (argv[1] == "--denylist"sv) {
return denylist_cli(argc - 1, argv + 1);
} else if (argc >= 3 && argv[1] == "--sqlite"sv) {
int fd = connect_daemon(MainRequest::SQLITE_CMD);
int fd = connect_daemon(+RequestCode::SQLITE_CMD);
write_string(fd, argv[2]);
string res;
for (;;) {
@ -123,7 +123,7 @@ int magisk_main(int argc, char *argv[]) {
} else {
usage();
}
int fd = connect_daemon(MainRequest::REMOVE_MODULES);
int fd = connect_daemon(+RequestCode::REMOVE_MODULES);
write_int(fd, do_reboot);
return read_int(fd);
} else if (argv[1] == "--path"sv) {

View File

@ -189,7 +189,7 @@ int su_client_main(int argc, char *argv[]) {
int ptmx, fd;
// Connect to client
fd = connect_daemon(MainRequest::SUPERUSER);
fd = connect_daemon(+RequestCode::SUPERUSER);
// Send su_request
xwrite(fd, &su_req, sizeof(su_req_base));

View File

@ -39,7 +39,7 @@ void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods
int remote_get_info(int uid, const char *process, uint32_t *flags, std::vector<int> &fds);
inline int zygisk_request(int req) {
int fd = connect_daemon(MainRequest::ZYGISK);
int fd = connect_daemon(+RequestCode::ZYGISK);
if (fd < 0) return fd;
write_int(fd, req);
return fd;