Implement preinit related features in Rust

This commit is contained in:
topjohnwu 2024-03-28 14:11:03 -07:00
parent 1eddbfd72c
commit a1b6568226
13 changed files with 312 additions and 164 deletions

2
native/src/Cargo.lock generated
View File

@ -72,6 +72,7 @@ dependencies = [
"argh", "argh",
"bytemuck", "bytemuck",
"cfg-if", "cfg-if",
"const_format",
"cxx", "cxx",
"cxx-gen", "cxx-gen",
"libc", "libc",
@ -467,7 +468,6 @@ version = "0.0.0"
dependencies = [ dependencies = [
"base", "base",
"bytemuck", "bytemuck",
"const_format",
"cxx", "cxx",
"cxx-gen", "cxx-gen",
"num-derive", "num-derive",

View File

@ -22,3 +22,4 @@ argh = { workspace = true }
bytemuck = { workspace = true } bytemuck = { workspace = true }
num-traits = { workspace = true } num-traits = { workspace = true }
num-derive = { workspace = true } num-derive = { workspace = true }
const_format = { workspace = true }

View File

@ -451,6 +451,7 @@ pub struct FsPathBuf<'a>(&'a mut dyn Utf8CStrWrite);
impl<'a> FsPathBuf<'a> { impl<'a> FsPathBuf<'a> {
pub fn new(value: &'a mut dyn Utf8CStrWrite) -> Self { pub fn new(value: &'a mut dyn Utf8CStrWrite) -> Self {
value.clear();
FsPathBuf(value) FsPathBuf(value)
} }
@ -656,7 +657,8 @@ macro_rules! cstr {
); );
#[allow(unused_unsafe)] #[allow(unused_unsafe)]
unsafe { unsafe {
$crate::Utf8CStr::from_bytes_unchecked(concat!($($str)*, "\0").as_bytes()) $crate::Utf8CStr::from_bytes_unchecked($crate::const_format::concatcp!($($str)*, "\0")
.as_bytes())
} }
}}; }};
} }

View File

@ -3,6 +3,7 @@
#![feature(io_error_more)] #![feature(io_error_more)]
#![feature(utf8_chunks)] #![feature(utf8_chunks)]
pub use const_format;
pub use libc; pub use libc;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;

View File

@ -17,5 +17,4 @@ cxx = { workspace = true }
num-traits = { workspace = true } num-traits = { workspace = true }
num-derive = { workspace = true } num-derive = { workspace = true }
quick-protobuf = { workspace = true } quick-protobuf = { workspace = true }
const_format = { workspace = true }
bytemuck = { workspace = true, features = ["derive"] } bytemuck = { workspace = true, features = ["derive"] }

View File

@ -19,157 +19,6 @@ bool zygisk_enabled = false;
/********* /*********
* Setup * * Setup *
*********/ *********/
static void setup_mounts() {
LOGI("* Magic mount setup\n");
auto self_mount_info = parse_mount_info("self");
char path[PATH_MAX];
// Bind remount module root to clear nosuid
ssprintf(path, sizeof(path), "%s/" MODULEMNT, get_magisk_tmp());
xmkdir(path, 0755);
xmount(MODULEROOT, path, nullptr, MS_BIND, nullptr);
xmount(nullptr, path, nullptr, MS_REMOUNT | MS_BIND | MS_RDONLY, nullptr);
xmount(nullptr, path, nullptr, MS_PRIVATE, nullptr);
// Check and mount preinit mirror
char dev_path[64];
ssprintf(dev_path, sizeof(dev_path), "%s/" PREINITDEV, get_magisk_tmp());
if (struct stat st{}; stat(dev_path, &st) == 0 && S_ISBLK(st.st_mode)) {
// DO NOT mount the block device directly, as we do not know the flags and configs
// to properly mount the partition; mounting block devices directly as rw could cause
// crashes if the filesystem driver is crap (e.g. some broken F2FS drivers).
// What we do instead is to scan through the current mountinfo and find a pre-existing
// mount point mounting our desired partition, and then bind mount the target folder.
dev_t preinit_dev = st.st_rdev;
bool mounted = false;
ssprintf(path, sizeof(path), "%s/" PREINITMIRR, get_magisk_tmp());
for (const auto &info: self_mount_info) {
if (info.root == "/" && info.device == preinit_dev) {
auto flags = split_view(info.fs_option, ",");
auto rw = std::any_of(flags.begin(), flags.end(), [](const auto &flag) {
return flag == "rw"sv;
});
if (!rw) continue;
string preinit_dir = resolve_preinit_dir(info.target.data());
xmkdir(preinit_dir.data(), 0700);
xmkdirs(path, 0755);
mounted = xmount(preinit_dir.data(), path, nullptr, MS_BIND, nullptr) == 0;
if (mounted) {
break;
}
}
}
if (!mounted) {
LOGW("preinit mirror not mounted %u:%u\n", major(preinit_dev), minor(preinit_dev));
unlink(dev_path);
}
}
// Prepare worker
ssprintf(path, sizeof(path), "%s/" WORKERDIR, get_magisk_tmp());
xmkdir(path, 0);
xmount(path, path, nullptr, MS_BIND, nullptr);
xmount(nullptr, path, nullptr, MS_PRIVATE, nullptr);
}
string find_preinit_device() {
enum part_t {
UNKNOWN,
PERSIST,
METADATA,
CACHE,
DATA,
};
part_t ext4_type = UNKNOWN;
part_t f2fs_type = UNKNOWN;
bool encrypted = get_prop("ro.crypto.state") == "encrypted";
bool mount = getuid() == 0 && getenv("MAGISKTMP");
bool make_dev = mount && getenv("MAKEDEV");
string preinit_source;
string preinit_dir;
dev_t preinit_dev;
for (const auto &info: parse_mount_info("self")) {
if (info.target.ends_with(PREINITMIRR))
return basename(info.source.data());
if (info.root != "/" || info.source[0] != '/' || info.source.find("/dm-") != string::npos)
continue;
// Skip all non ext4 partitions once we found a matching ext4 partition
if (ext4_type != UNKNOWN && info.type != "ext4")
continue;
if (info.type != "ext4" && info.type != "f2fs")
continue;
auto flags = split_view(info.fs_option, ",");
auto rw = std::any_of(flags.begin(), flags.end(), [](const auto &flag) {
return flag == "rw"sv;
});
if (!rw) continue;
if (auto base = std::string_view(info.source).substr(0, info.source.find_last_of('/'));
!base.ends_with("/by-name") && !base.ends_with("/block")) {
continue;
}
part_t &matched = (info.type == "f2fs") ? f2fs_type : ext4_type;
switch (matched) {
case UNKNOWN:
if (info.target == "/persist" || info.target == "/mnt/vendor/persist") {
matched = PERSIST;
break;
}
[[fallthrough]];
case PERSIST:
if (info.target == "/metadata") {
matched = METADATA;
break;
}
[[fallthrough]];
case METADATA:
if (info.target == "/cache") {
matched = CACHE;
break;
}
[[fallthrough]];
case CACHE:
if (info.target == "/data") {
if (!encrypted || access("/data/unencrypted", F_OK) == 0) {
matched = DATA;
break;
}
}
[[fallthrough]];
default:
continue;
}
if (mount) {
preinit_dir = resolve_preinit_dir(info.target.data());
preinit_dev = info.device;
}
preinit_source = info.source;
// Cannot find any better partition, stop finding
if (ext4_type == DATA)
break;
}
if (preinit_source.empty())
return "";
if (!preinit_dir.empty()) {
auto mirror_dir = string(getenv("MAGISKTMP")) + "/" PREINITMIRR;
mkdirs(preinit_dir.data(), 0700);
mkdirs(mirror_dir.data(), 0700);
xmount(preinit_dir.data(), mirror_dir.data(), nullptr, MS_BIND, nullptr);
if (make_dev) {
auto dev_path = string(getenv("MAGISKTMP")) + "/" PREINITDEV;
xmknod(dev_path.data(), S_IFBLK | 0600, preinit_dev);
}
}
return basename(preinit_source.data());
}
static bool magisk_env() { static bool magisk_env() {
char buf[4096]; char buf[4096];

View File

@ -42,7 +42,6 @@ extern std::string native_bridge;
void reset_zygisk(bool restore); void reset_zygisk(bool restore);
int connect_daemon(int req, bool create = false); int connect_daemon(int req, bool create = false);
std::string find_preinit_device();
void unlock_blocks(); void unlock_blocks();
// Poll control // Poll control

View File

@ -26,4 +26,9 @@ private:
}; };
const char *get_magisk_tmp(); const char *get_magisk_tmp();
// Rust bindings
static inline rust::Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); } static inline rust::Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); }
static inline rust::String resolve_preinit_dir_rs(rust::Utf8CStr base_dir) {
return resolve_preinit_dir(base_dir.c_str());
}

View File

@ -1,4 +1,5 @@
#![feature(format_args_nl)] #![feature(format_args_nl)]
#![feature(try_blocks)]
#![allow(clippy::missing_safety_doc)] #![allow(clippy::missing_safety_doc)]
use base::Utf8CStr; use base::Utf8CStr;
@ -7,6 +8,7 @@ use daemon::{daemon_entry, find_apk_path, get_magiskd, MagiskD};
use logging::{ use logging::{
android_logging, magisk_logging, zygisk_close_logd, zygisk_get_logd, zygisk_logging, android_logging, magisk_logging, zygisk_close_logd, zygisk_get_logd, zygisk_logging,
}; };
use mount::{find_preinit_device, setup_mounts};
use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop}; use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop};
mod cert; mod cert;
@ -14,6 +16,7 @@ mod cert;
mod consts; mod consts;
mod daemon; mod daemon;
mod logging; mod logging;
mod mount;
mod resetprop; mod resetprop;
#[cxx::bridge] #[cxx::bridge]
@ -66,6 +69,8 @@ pub mod ffi {
#[cxx_name = "get_magisk_tmp_rs"] #[cxx_name = "get_magisk_tmp_rs"]
fn get_magisk_tmp() -> Utf8CStrRef<'static>; fn get_magisk_tmp() -> Utf8CStrRef<'static>;
#[cxx_name = "resolve_preinit_dir_rs"]
fn resolve_preinit_dir(base_dir: Utf8CStrRef) -> String;
#[cxx_name = "MagiskD"] #[cxx_name = "MagiskD"]
type CxxMagiskD; type CxxMagiskD;
@ -83,6 +88,8 @@ pub mod ffi {
fn zygisk_get_logd() -> i32; fn zygisk_get_logd() -> i32;
fn find_apk_path(pkg: Utf8CStrRef, data: &mut [u8]) -> usize; fn find_apk_path(pkg: Utf8CStrRef, data: &mut [u8]) -> usize;
fn read_certificate(fd: i32, version: i32) -> Vec<u8>; fn read_certificate(fd: i32, version: i32) -> Vec<u8>;
fn setup_mounts();
fn find_preinit_device() -> String;
unsafe fn persist_get_prop(name: Utf8CStrRef, prop_cb: Pin<&mut PropCb>); unsafe fn persist_get_prop(name: Utf8CStrRef, prop_cb: Pin<&mut PropCb>);
unsafe fn persist_get_props(prop_cb: Pin<&mut PropCb>); unsafe fn persist_get_props(prop_cb: Pin<&mut PropCb>);
unsafe fn persist_delete_prop(name: Utf8CStrRef) -> bool; unsafe fn persist_delete_prop(name: Utf8CStrRef) -> bool;

View File

@ -9,7 +9,6 @@ use std::sync::atomic::{AtomicI32, Ordering};
use std::{fs, io}; use std::{fs, io};
use bytemuck::{bytes_of, bytes_of_mut, write_zeroes, Pod, Zeroable}; use bytemuck::{bytes_of, bytes_of_mut, write_zeroes, Pod, Zeroable};
use const_format::concatcp;
use num_derive::{FromPrimitive, ToPrimitive}; use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
@ -19,8 +18,8 @@ use base::libc::{
SIG_SETMASK, SIG_SETMASK,
}; };
use base::{ use base::{
exit_on_error, libc, raw_cstr, FsPathBuf, LogLevel, Logger, Utf8CStr, Utf8CStrBuf, const_format::concatcp, exit_on_error, libc, raw_cstr, FsPathBuf, LogLevel, Logger, Utf8CStr,
Utf8CStrBufArr, Utf8CStrWrite, LOGGER, Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrWrite, LOGGER,
}; };
use crate::consts::{LOGFILE, LOG_PIPE}; use crate::consts::{LOGFILE, LOG_PIPE};

View File

@ -137,9 +137,8 @@ int magisk_main(int argc, char *argv[]) {
install_module(argv[2]); install_module(argv[2]);
} else if (argv[1] == "--preinit-device"sv) { } else if (argv[1] == "--preinit-device"sv) {
auto name = find_preinit_device(); auto name = find_preinit_device();
LOGD("preinit device: %s\n", name.data());
if (!name.empty()) { if (!name.empty()) {
printf("%s\n", name.data()); printf("%s\n", name.c_str());
return 0; return 0;
} }
return 1; return 1;

276
native/src/core/mount.rs Normal file
View File

@ -0,0 +1,276 @@
use std::path::Path;
use std::ptr;
use num_traits::AsPrimitive;
use base::libc::{c_uint, dev_t};
use base::{
cstr, debug, info, libc, parse_mount_info, raw_cstr, warn, FsPath, FsPathBuf, LibcReturn,
LoggedResult, ResultExt, Utf8CStr, Utf8CStrBufArr,
};
use crate::consts::{MODULEMNT, MODULEROOT, PREINITDEV, PREINITMIRR, WORKERDIR};
use crate::ffi::{get_magisk_tmp, resolve_preinit_dir};
use crate::get_prop;
pub fn setup_mounts() {
info!("* Setup internal mounts");
let magisk_tmp = get_magisk_tmp();
let mut buf = Utf8CStrBufArr::default();
// Mount preinit directory
let mut dev_buf = Utf8CStrBufArr::<64>::new();
let dev_path = FsPathBuf::new(&mut dev_buf)
.join(magisk_tmp)
.join(PREINITDEV);
if let Ok(attr) = dev_path.get_attr() {
if attr.st.st_mode & libc::S_IFMT as c_uint == libc::S_IFBLK.as_() {
// DO NOT mount the block device directly, as we do not know the flags and configs
// to properly mount the partition; mounting block devices directly as rw could cause
// crashes if the filesystem driver is crap (e.g. some broken F2FS drivers).
// What we do instead is to scan through the current mountinfo and find a pre-existing
// mount point mounting our desired partition, and then bind mount the target folder.
let preinit_dev = attr.st.st_rdev;
let mnt_path = FsPathBuf::new(&mut buf).join(magisk_tmp).join(PREINITMIRR);
let mut mounted = false;
for info in parse_mount_info("self") {
if info.root == "/" && info.device == preinit_dev {
if !info.fs_option.split(',').any(|s| s == "rw") {
// Only care about rw mounts
continue;
}
let mut target = info.target;
let target = Utf8CStr::from_string(&mut target);
let mut preinit_dir = resolve_preinit_dir(target);
let preinit_dir = Utf8CStr::from_string(&mut preinit_dir);
let r: LoggedResult<()> = try {
FsPath::from(preinit_dir).mkdir(0o700)?;
mnt_path.mkdirs(0o755)?;
unsafe {
libc::mount(
preinit_dir.as_ptr(),
mnt_path.as_ptr(),
ptr::null(),
libc::MS_BIND,
ptr::null(),
)
.as_os_err()?
}
};
if r.is_ok() {
mounted = true;
break;
}
}
}
if !mounted {
warn!("mount: preinit mirror not mounted");
dev_path.remove().ok();
} else {
debug!("mount: preinit mirror mounted");
}
}
}
// Bind remount module root to clear nosuid
let module_mnt = FsPathBuf::new(&mut buf).join(magisk_tmp).join(MODULEMNT);
let _: LoggedResult<()> = try {
module_mnt.mkdir(0o755)?;
unsafe {
libc::mount(
raw_cstr!(MODULEROOT),
module_mnt.as_ptr(),
ptr::null(),
libc::MS_BIND,
ptr::null(),
)
.as_os_err()?;
libc::mount(
ptr::null(),
module_mnt.as_ptr(),
ptr::null(),
libc::MS_REMOUNT | libc::MS_BIND | libc::MS_RDONLY,
ptr::null(),
)
.as_os_err()?;
libc::mount(
ptr::null(),
module_mnt.as_ptr(),
ptr::null(),
libc::MS_PRIVATE,
ptr::null(),
)
.as_os_err()?;
}
};
// Prepare worker
let worker_dir = FsPathBuf::new(&mut buf).join(magisk_tmp).join(WORKERDIR);
let _: LoggedResult<()> = try {
worker_dir.mkdir(0)?;
unsafe {
libc::mount(
worker_dir.as_ptr(),
worker_dir.as_ptr(),
ptr::null(),
libc::MS_BIND,
ptr::null(),
)
.as_os_err()?;
libc::mount(
ptr::null(),
worker_dir.as_ptr(),
ptr::null(),
libc::MS_PRIVATE,
ptr::null(),
)
.as_os_err()?;
}
};
}
#[derive(Ord, PartialOrd, Eq, PartialEq)]
enum PartId {
Unknown,
Persist,
Metadata,
Cache,
Data,
}
pub fn find_preinit_device() -> String {
let encrypted = get_prop(cstr!("ro.crypto.state"), false) == "encrypted";
let mount = unsafe { libc::getuid() } == 0 && std::env::var("MAGISKTMP").is_ok();
let make_dev = mount && std::env::var_os("MAKEDEV").is_some();
let mut ext4_type = PartId::Unknown;
let mut f2fs_type = PartId::Unknown;
let mut preinit_source: String = String::new();
let mut preinit_dir: String = String::new();
let mut preinit_dev: u64 = 0;
'info_loop: for info in parse_mount_info("self") {
if info.target.ends_with(PREINITMIRR) {
return Path::new(&info.source)
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string();
}
if info.root != "/" || !info.source.starts_with('/') || info.source.contains("/dm-") {
continue;
}
if ext4_type != PartId::Unknown && info.fs_type != "ext4" {
// Skip all non ext4 partitions once we found a matching ext4 partition
continue;
}
if info.fs_type != "ext4" && info.fs_type != "f2fs" {
// Only care about ext4 and f2fs filesystems
continue;
}
if !info.fs_option.split(',').any(|s| s == "rw") {
// Only care about rw mounts
continue;
}
if let Some(path) = Path::new(&info.source).parent() {
if !path.ends_with("by-name") && !path.ends_with("block") {
continue;
}
} else {
continue;
}
let matched_type = if info.fs_type == "f2fs" {
&mut f2fs_type
} else {
&mut ext4_type
};
'block: {
if *matched_type <= PartId::Unknown
&& (info.target == "/persist" || info.target == "/mnt/vendor/persist")
{
*matched_type = PartId::Persist;
break 'block;
}
if *matched_type <= PartId::Persist && info.target == "/metadata" {
*matched_type = PartId::Metadata;
break 'block;
}
if *matched_type <= PartId::Metadata && info.target == "/cache" {
*matched_type = PartId::Cache;
break 'block;
}
if *matched_type <= PartId::Cache
&& info.target == "/data"
&& (!encrypted || FsPath::from(cstr!("/data/unencrypted")).exists())
{
*matched_type = PartId::Data;
}
// No matches, continue through the loop
continue 'info_loop;
}
if mount {
let mut target = info.target;
preinit_dir = resolve_preinit_dir(Utf8CStr::from_string(&mut target));
preinit_dev = info.device;
}
preinit_source = info.source;
// Cannot find any better partition, stop finding
if ext4_type == PartId::Data {
break;
}
}
if preinit_source.is_empty() {
return String::new();
}
if !preinit_dir.is_empty() {
if let Ok(tmp) = std::env::var("MAGISKTMP") {
let mut buf = Utf8CStrBufArr::default();
let mirror_dir = FsPathBuf::new(&mut buf).join(&tmp).join(PREINITMIRR);
let preinit_dir = FsPath::from(Utf8CStr::from_string(&mut preinit_dir));
let _: LoggedResult<()> = try {
preinit_dir.mkdirs(0o700)?;
mirror_dir.mkdirs(0o700)?;
unsafe {
libc::mount(
preinit_dir.as_ptr(),
mirror_dir.as_ptr(),
ptr::null(),
libc::MS_BIND,
ptr::null(),
)
.as_os_err()?;
}
};
if make_dev {
let dev_path = FsPathBuf::new(&mut buf).join(&tmp).join(PREINITDEV);
unsafe {
libc::mknod(
dev_path.as_ptr(),
libc::S_IFBLK | 0o600,
preinit_dev as dev_t,
)
.as_os_err()
.log()
.ok();
}
}
}
}
Path::new(&preinit_source)
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string()
}

View File

@ -1,6 +1,17 @@
use const_format::concatcp; use base::const_format::concatcp;
pub const LOGFILE: &str = "/cache/magisk.log"; pub const LOGFILE: &str = "/cache/magisk.log";
pub const INTLROOT: &str = ".magisk";
pub const LOG_PIPE: &str = concatcp!(INTLROOT, "/device/log"); // data paths
pub const MAIN_CONFIG: &str = concatcp!(INTLROOT, "/config"); const SECURE_DIR: &str = "/data/adb";
pub const MODULEROOT: &str = concatcp!(SECURE_DIR, "modules");
// tmpfs paths
const INTERNAL_DIR: &str = ".magisk";
pub const LOG_PIPE: &str = concatcp!(INTERNAL_DIR, "/device/log");
pub const MAIN_CONFIG: &str = concatcp!(INTERNAL_DIR, "/config");
pub const PREINITMIRR: &str = concatcp!(INTERNAL_DIR, "/preinit");
pub const MODULEMNT: &str = concatcp!(INTERNAL_DIR, "/modules");
pub const WORKERDIR: &str = concatcp!(INTERNAL_DIR, "/worker");
pub const DEVICEDIR: &str = concatcp!(INTERNAL_DIR, "/device");
pub const PREINITDEV: &str = concatcp!(DEVICEDIR, "/preinit");