From a1b65682261682a9c83bd66567cf2bdb2b69b882 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Thu, 28 Mar 2024 14:11:03 -0700 Subject: [PATCH] Implement preinit related features in Rust --- native/src/Cargo.lock | 2 +- native/src/base/Cargo.toml | 1 + native/src/base/cstr.rs | 4 +- native/src/base/lib.rs | 1 + native/src/core/Cargo.toml | 1 - native/src/core/bootstages.cpp | 151 ---------------- native/src/core/include/core.hpp | 1 - native/src/core/include/daemon.hpp | 5 + native/src/core/lib.rs | 7 + native/src/core/logging.rs | 5 +- native/src/core/magisk.cpp | 3 +- native/src/core/mount.rs | 276 +++++++++++++++++++++++++++++ native/src/include/consts.rs | 19 +- 13 files changed, 312 insertions(+), 164 deletions(-) create mode 100644 native/src/core/mount.rs diff --git a/native/src/Cargo.lock b/native/src/Cargo.lock index 9abcf7a18..4472ba2d3 100644 --- a/native/src/Cargo.lock +++ b/native/src/Cargo.lock @@ -72,6 +72,7 @@ dependencies = [ "argh", "bytemuck", "cfg-if", + "const_format", "cxx", "cxx-gen", "libc", @@ -467,7 +468,6 @@ version = "0.0.0" dependencies = [ "base", "bytemuck", - "const_format", "cxx", "cxx-gen", "num-derive", diff --git a/native/src/base/Cargo.toml b/native/src/base/Cargo.toml index d0a1891ec..395349aad 100644 --- a/native/src/base/Cargo.toml +++ b/native/src/base/Cargo.toml @@ -22,3 +22,4 @@ argh = { workspace = true } bytemuck = { workspace = true } num-traits = { workspace = true } num-derive = { workspace = true } +const_format = { workspace = true } diff --git a/native/src/base/cstr.rs b/native/src/base/cstr.rs index cbdb116cc..2e21278ae 100644 --- a/native/src/base/cstr.rs +++ b/native/src/base/cstr.rs @@ -451,6 +451,7 @@ pub struct FsPathBuf<'a>(&'a mut dyn Utf8CStrWrite); impl<'a> FsPathBuf<'a> { pub fn new(value: &'a mut dyn Utf8CStrWrite) -> Self { + value.clear(); FsPathBuf(value) } @@ -656,7 +657,8 @@ macro_rules! cstr { ); #[allow(unused_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()) } }}; } diff --git a/native/src/base/lib.rs b/native/src/base/lib.rs index 39c9d8d11..7eb17b5b3 100644 --- a/native/src/base/lib.rs +++ b/native/src/base/lib.rs @@ -3,6 +3,7 @@ #![feature(io_error_more)] #![feature(utf8_chunks)] +pub use const_format; pub use libc; use num_traits::FromPrimitive; diff --git a/native/src/core/Cargo.toml b/native/src/core/Cargo.toml index 93715b90f..f92d5bb3c 100644 --- a/native/src/core/Cargo.toml +++ b/native/src/core/Cargo.toml @@ -17,5 +17,4 @@ cxx = { workspace = true } num-traits = { workspace = true } num-derive = { workspace = true } quick-protobuf = { workspace = true } -const_format = { workspace = true } bytemuck = { workspace = true, features = ["derive"] } diff --git a/native/src/core/bootstages.cpp b/native/src/core/bootstages.cpp index 36d8b136c..84023f7d9 100644 --- a/native/src/core/bootstages.cpp +++ b/native/src/core/bootstages.cpp @@ -19,157 +19,6 @@ bool zygisk_enabled = false; /********* * 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() { char buf[4096]; diff --git a/native/src/core/include/core.hpp b/native/src/core/include/core.hpp index 879d93825..c68fd52c1 100644 --- a/native/src/core/include/core.hpp +++ b/native/src/core/include/core.hpp @@ -42,7 +42,6 @@ extern std::string native_bridge; void reset_zygisk(bool restore); int connect_daemon(int req, bool create = false); -std::string find_preinit_device(); void unlock_blocks(); // Poll control diff --git a/native/src/core/include/daemon.hpp b/native/src/core/include/daemon.hpp index 805d7107b..49a94ac43 100644 --- a/native/src/core/include/daemon.hpp +++ b/native/src/core/include/daemon.hpp @@ -26,4 +26,9 @@ private: }; const char *get_magisk_tmp(); + +// Rust bindings 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()); +} diff --git a/native/src/core/lib.rs b/native/src/core/lib.rs index 43d0ceb06..c6d1232c5 100644 --- a/native/src/core/lib.rs +++ b/native/src/core/lib.rs @@ -1,4 +1,5 @@ #![feature(format_args_nl)] +#![feature(try_blocks)] #![allow(clippy::missing_safety_doc)] use base::Utf8CStr; @@ -7,6 +8,7 @@ use daemon::{daemon_entry, find_apk_path, get_magiskd, MagiskD}; use 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}; mod cert; @@ -14,6 +16,7 @@ mod cert; mod consts; mod daemon; mod logging; +mod mount; mod resetprop; #[cxx::bridge] @@ -66,6 +69,8 @@ pub mod ffi { #[cxx_name = "get_magisk_tmp_rs"] fn get_magisk_tmp() -> Utf8CStrRef<'static>; + #[cxx_name = "resolve_preinit_dir_rs"] + fn resolve_preinit_dir(base_dir: Utf8CStrRef) -> String; #[cxx_name = "MagiskD"] type CxxMagiskD; @@ -83,6 +88,8 @@ pub mod ffi { fn zygisk_get_logd() -> i32; fn find_apk_path(pkg: Utf8CStrRef, data: &mut [u8]) -> usize; fn read_certificate(fd: i32, version: i32) -> Vec; + fn setup_mounts(); + fn find_preinit_device() -> String; 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_delete_prop(name: Utf8CStrRef) -> bool; diff --git a/native/src/core/logging.rs b/native/src/core/logging.rs index fd55864aa..9e6fbc4d5 100644 --- a/native/src/core/logging.rs +++ b/native/src/core/logging.rs @@ -9,7 +9,6 @@ use std::sync::atomic::{AtomicI32, Ordering}; use std::{fs, io}; use bytemuck::{bytes_of, bytes_of_mut, write_zeroes, Pod, Zeroable}; -use const_format::concatcp; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::FromPrimitive; @@ -19,8 +18,8 @@ use base::libc::{ SIG_SETMASK, }; use base::{ - exit_on_error, libc, raw_cstr, FsPathBuf, LogLevel, Logger, Utf8CStr, Utf8CStrBuf, - Utf8CStrBufArr, Utf8CStrWrite, LOGGER, + const_format::concatcp, exit_on_error, libc, raw_cstr, FsPathBuf, LogLevel, Logger, Utf8CStr, + Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrWrite, LOGGER, }; use crate::consts::{LOGFILE, LOG_PIPE}; diff --git a/native/src/core/magisk.cpp b/native/src/core/magisk.cpp index 9381bca86..3dd810d9e 100644 --- a/native/src/core/magisk.cpp +++ b/native/src/core/magisk.cpp @@ -137,9 +137,8 @@ int magisk_main(int argc, char *argv[]) { install_module(argv[2]); } else if (argv[1] == "--preinit-device"sv) { auto name = find_preinit_device(); - LOGD("preinit device: %s\n", name.data()); if (!name.empty()) { - printf("%s\n", name.data()); + printf("%s\n", name.c_str()); return 0; } return 1; diff --git a/native/src/core/mount.rs b/native/src/core/mount.rs new file mode 100644 index 000000000..9d15ac7a6 --- /dev/null +++ b/native/src/core/mount.rs @@ -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() +} diff --git a/native/src/include/consts.rs b/native/src/include/consts.rs index 528a66ca0..bd2337aeb 100644 --- a/native/src/include/consts.rs +++ b/native/src/include/consts.rs @@ -1,6 +1,17 @@ -use const_format::concatcp; +use base::const_format::concatcp; pub const LOGFILE: &str = "/cache/magisk.log"; -pub const INTLROOT: &str = ".magisk"; -pub const LOG_PIPE: &str = concatcp!(INTLROOT, "/device/log"); -pub const MAIN_CONFIG: &str = concatcp!(INTLROOT, "/config"); + +// data paths +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");