Magisk/native/src/core/mount.rs
2024-08-15 02:39:51 -07:00

310 lines
10 KiB
Rust

use std::{
cmp::Ordering::{Greater, Less},
path::{Path, PathBuf},
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, MountInfo, ResultExt, Utf8CStr, Utf8CStrBufArr,
};
use crate::consts::{MODULEMNT, MODULEROOT, PREINITDEV, PREINITMIRR, WORKERDIR};
use crate::ffi::{get_magisk_tmp, resolve_preinit_dir, switch_mnt_ns};
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);
let mut linked = false;
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);
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)?;
let mut buf = Utf8CStrBufArr::default();
if mnt_path.parent(&mut buf) {
FsPath::from(&buf).mkdirs(0o755)?;
}
mnt_path.remove().ok();
unsafe {
libc::symlink(preinit_dir.as_ptr(), mnt_path.as_ptr()).as_os_err()?
}
};
if r.is_ok() {
linked = true;
break;
}
}
}
}
}
if !linked {
warn!("mount: preinit dir not found");
dev_path.remove().ok();
} else {
debug!("mount: preinit dir found");
}
// 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()?;
}
};
}
pub fn clean_mounts() {
let magisk_tmp = get_magisk_tmp();
let mut buf = Utf8CStrBufArr::default();
let module_mnt = FsPathBuf::new(&mut buf).join(magisk_tmp).join(MODULEMNT);
let _: LoggedResult<()> = try {
unsafe {
libc::umount2(
module_mnt.as_ptr(),
libc::MNT_DETACH,
)
.as_os_err()?;
}
};
let worker_dir = FsPathBuf::new(&mut buf).join(magisk_tmp).join(WORKERDIR);
let _: LoggedResult<()> = try {
unsafe {
libc::mount(
ptr::null(),
worker_dir.as_ptr(),
ptr::null(),
libc::MS_PRIVATE | libc::MS_REC,
ptr::null(),
)
.as_os_err()?;
libc::umount2(
worker_dir.as_ptr(),
libc::MNT_DETACH,
)
.as_os_err()?;
}
};
}
// when partitions have the same fs type, the order is:
// - data: it has sufficient space and can be safely written
// - cache: size is limited, but still can be safely written
// - metadata: size is limited, and it might cause unexpected behavior if written
// - persist: it's the last resort, as it's dangerous to write to it
#[derive(PartialEq, Eq, PartialOrd, Ord)]
enum PartId {
Data,
Cache,
Metadata,
Persist,
}
enum EncryptType {
None,
Block,
File,
Metadata,
}
pub fn find_preinit_device() -> String {
let encrypt_type = if get_prop(cstr!("ro.crypto.state"), false) != "encrypted" {
EncryptType::None
} else if get_prop(cstr!("ro.crypto.type"), false) == "block" {
EncryptType::Block
} else if get_prop(cstr!("ro.crypto.metadata.enabled"), false) == "true" {
EncryptType::Metadata
} else {
EncryptType::File
};
let mut matched_info = parse_mount_info("self")
.into_iter()
.filter_map(|info| {
if info.root != "/" || !info.source.starts_with('/') || info.source.contains("/dm-") {
return None;
}
match info.fs_type.as_str() {
"ext4" | "f2fs" => (),
_ => return None,
}
if !info.fs_option.split(',').any(|s| s == "rw") {
return None;
}
if let Some(path) = Path::new(&info.source).parent() {
if !path.ends_with("by-name") && !path.ends_with("block") {
return None;
}
} else {
return None;
}
// take data iff it's not encrypted or file-based encrypted without metadata
// other partitions are always taken
match info.target.as_str() {
"/persist" | "/mnt/vendor/persist" => Some((PartId::Persist, info)),
"/metadata" => Some((PartId::Metadata, info)),
"/cache" => Some((PartId::Cache, info)),
"/data" => Some((PartId::Data, info))
.take_if(|_| matches!(encrypt_type, EncryptType::None | EncryptType::File)),
_ => None,
}
})
.collect::<Vec<_>>();
if matched_info.is_empty() {
return String::new();
}
let (_, preinit_info, _) = matched_info.select_nth_unstable_by(
0,
|(ap, MountInfo { fs_type: at, .. }), (bp, MountInfo { fs_type: bt, .. })| match (
ap,
bp,
at.as_str() == "ext4",
bt.as_str() == "ext4",
) {
// metadata is not affected by f2fs kernel bug
(PartId::Metadata, _, _, true) | (_, PartId::Metadata, true, _) => ap.cmp(bp),
// otherwise, take ext4 f2fs because f2fs has a kernel bug that causes kernel panic
(_, _, true, false) => Less,
(_, _, false, true) => Greater,
// if both has the same fs type, compare the mount point
_ => ap.cmp(bp),
},
);
let info = &preinit_info.1;
let mut target = info.target.clone();
let mut preinit_dir = resolve_preinit_dir(Utf8CStr::from_string(&mut target));
if unsafe { libc::getuid() } == 0
&& let Ok(tmp) = std::env::var("MAGISKTMP")
&& !tmp.is_empty()
{
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)?;
let mut buf = Utf8CStrBufArr::default();
if mirror_dir.parent(&mut buf) {
FsPath::from(&buf).mkdirs(0o755)?;
}
unsafe {
libc::umount2(mirror_dir.as_ptr(), libc::MNT_DETACH)
.as_os_err()
.ok(); // ignore error
mirror_dir.remove().ok();
libc::symlink(preinit_dir.as_ptr(), mirror_dir.as_ptr()).as_os_err()?;
}
};
if std::env::var_os("MAKEDEV").is_some() {
let dev_path = FsPathBuf::new(&mut buf).join(&tmp).join(PREINITDEV);
unsafe {
libc::mknod(
dev_path.as_ptr(),
libc::S_IFBLK | 0o600,
info.device as dev_t,
)
.as_os_err()
.log()
.ok();
}
}
}
Path::new(&info.source)
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string()
}
pub fn revert_unmount(pid: i32) {
if pid > 0 {
if switch_mnt_ns(pid) != 0 {
return;
}
debug!("denylist: handling PID=[{}]", pid);
}
let mut targets = Vec::new();
// Unmount Magisk tmpfs and mounts from module files
for info in parse_mount_info("self") {
if info.source == "magisk" || info.root.starts_with("/adb/modules") {
targets.push(info.target);
}
}
if targets.is_empty() {
return;
}
let mut prev: Option<PathBuf> = None;
targets.sort();
targets.retain(|target| {
if let Some(prev) = &prev {
if Path::new(target).starts_with(prev) {
return false;
}
}
prev = Some(PathBuf::from(target.clone()));
true
});
for mut target in targets {
let target = Utf8CStr::from_string(&mut target);
unsafe {
if libc::umount2(target.as_ptr(), libc::MNT_DETACH) == 0 {
debug!("denylist: Unmounted ({})", target);
}
}
}
}