kernel: expose umount list to ioctl interface (#2950)

This idea is borrowed from simonpunk's susfs4ksu.
What we see here is that, yeah well, lets just have userspace send us
what it
wants unmounted, this is better than hardcoding everything.

This also solves that issue where MNT_DETACH fails, as long as we send
unmountables in proper order.

A small anti-duplicate mechanism is also added.

While in-kernel umount is a bit worse than zygisk-provider-based ones,
this can still
serve as a healthy alternative.

---------

- Remove duplicate checks

Signed-off-by: backslashxx <118538522+backslashxx@users.noreply.github.com>
Co-authored-by: weishu <twsxtd@gmail.com>
Co-authored-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
This commit is contained in:
backslashxx
2025-11-18 11:10:44 +08:00
committed by ShirkNeko
parent 58c8289890
commit 029ae8d389
48 changed files with 252 additions and 260 deletions

View File

@@ -390,6 +390,32 @@ enum Kernel {
/// mount point
mnt: String,
},
/// Manage umount list
Umount {
#[command(subcommand)]
command: UmountOp,
},
/// Notify that module is mounted
NotifyModuleMounted,
}
#[derive(clap::Subcommand, Debug)]
enum UmountOp {
/// Add mount point to umount list
Add {
/// mount point path
mnt: String,
/// umount flags (default: 0, MNT_DETACH: 2)
#[arg(short, long, default_value = "0")]
flags: u32,
},
/// Delete mount point from umount list
Del {
/// mount point path
mnt: String,
},
/// Wipe all entries from umount list
Wipe,
}
#[cfg(target_arch = "aarch64")]
@@ -425,7 +451,6 @@ enum Umount {
/// Check mount type (overlay)
#[arg(long, default_value = "false")]
check_mnt: bool,
/// Umount flags (0 or 8 for MNT_DETACH)
#[arg(long, default_value = "-1")]
@@ -606,6 +631,15 @@ pub fn run() -> Result<()> {
} => crate::boot_patch::restore(boot, magiskboot, flash),
Commands::Kernel { command } => match command {
Kernel::NukeExt4Sysfs { mnt } => ksucalls::nuke_ext4_sysfs(&mnt),
Kernel::Umount { command } => match command {
UmountOp::Add { mnt, flags } => ksucalls::umount_list_add(&mnt, flags),
UmountOp::Del { mnt } => ksucalls::umount_list_del(&mnt),
UmountOp::Wipe => ksucalls::umount_list_wipe().map_err(Into::into),
},
Kernel::NotifyModuleMounted => {
ksucalls::report_module_mounted();
Ok(())
}
},
#[cfg(target_arch = "aarch64")]
Commands::Kpm { command } => {
@@ -629,9 +663,8 @@ pub fn run() -> Result<()> {
Commands::Umount { command } => match command {
Umount::Add {
path,
check_mnt,
flags,
} => crate::umount_manager::add_umount_path(&path, check_mnt, flags),
} => crate::umount_manager::add_umount_path(&path, flags),
Umount::Remove { path } => crate::umount_manager::remove_umount_path(&path),
Umount::List => crate::umount_manager::list_umount_paths(),
Umount::ClearCustom => crate::umount_manager::clear_custom_paths(),

View File

@@ -18,6 +18,7 @@ const KSU_IOCTL_SET_FEATURE: u32 = 0x40004b0e; // _IOC(_IOC_WRITE, 'K', 14, 0)
const KSU_IOCTL_GET_WRAPPER_FD: u32 = 0x40004b0f; // _IOC(_IOC_WRITE, 'K', 15, 0)
const KSU_IOCTL_MANAGE_MARK: u32 = 0xc0004b10; // _IOC(_IOC_READ|_IOC_WRITE, 'K', 16, 0)
const KSU_IOCTL_NUKE_EXT4_SYSFS: u32 = 0x40004b11; // _IOC(_IOC_WRITE, 'K', 17, 0)
const KSU_IOCTL_ADD_TRY_UMOUNT: u32 = 0x40004b12; // _IOC(_IOC_WRITE, 'K', 18, 0)
#[repr(C)]
#[derive(Clone, Copy, Default)]
@@ -80,12 +81,25 @@ pub struct NukeExt4SysfsCmd {
pub arg: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct AddTryUmountCmd {
arg: u64, // char ptr, this is the mountpoint
flags: u32, // this is the flag we use for it
mode: u8, // denotes what to do with it 0:wipe_list 1:add_to_list 2:delete_entry
}
// Mark operation constants
const KSU_MARK_GET: u32 = 1;
const KSU_MARK_MARK: u32 = 2;
const KSU_MARK_UNMARK: u32 = 3;
const KSU_MARK_REFRESH: u32 = 4;
// Umount operation constants
const KSU_UMOUNT_WIPE: u8 = 0;
const KSU_UMOUNT_ADD: u8 = 1;
const KSU_UMOUNT_DEL: u8 = 2;
// Global driver fd cache
#[cfg(any(target_os = "linux", target_os = "android"))]
static DRIVER_FD: OnceLock<RawFd> = OnceLock::new();
@@ -310,3 +324,38 @@ pub fn nuke_ext4_sysfs(mnt: &str) -> anyhow::Result<()> {
ksuctl(KSU_IOCTL_NUKE_EXT4_SYSFS, &mut ioctl_cmd as *mut _)?;
Ok(())
}
/// Wipe all entries from umount list
pub fn umount_list_wipe() -> std::io::Result<()> {
let mut cmd = AddTryUmountCmd {
arg: 0,
flags: 0,
mode: KSU_UMOUNT_WIPE,
};
ksuctl(KSU_IOCTL_ADD_TRY_UMOUNT, &mut cmd as *mut _)?;
Ok(())
}
/// Add mount point to umount list
pub fn umount_list_add(path: &str, flags: u32) -> anyhow::Result<()> {
let c_path = std::ffi::CString::new(path)?;
let mut cmd = AddTryUmountCmd {
arg: c_path.as_ptr() as u64,
flags,
mode: KSU_UMOUNT_ADD,
};
ksuctl(KSU_IOCTL_ADD_TRY_UMOUNT, &mut cmd as *mut _)?;
Ok(())
}
/// Delete mount point from umount list
pub fn umount_list_del(path: &str) -> anyhow::Result<()> {
let c_path = std::ffi::CString::new(path)?;
let mut cmd = AddTryUmountCmd {
arg: c_path.as_ptr() as u64,
flags: 0,
mode: KSU_UMOUNT_DEL,
};
ksuctl(KSU_IOCTL_ADD_TRY_UMOUNT, &mut cmd as *mut _)?;
Ok(())
}

View File

@@ -13,7 +13,6 @@ const KSU_IOCTL_UMOUNT_MANAGER: u32 = 0xc0004b6b; // _IOC(_IOC_READ|_IOC_WRITE,
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct UmountEntry {
pub path: String,
pub check_mnt: bool,
pub flags: i32,
pub is_default: bool,
}
@@ -34,7 +33,6 @@ pub struct UmountManager {
struct UmountManagerCmd {
pub operation: u32,
pub path: [u8; 256],
pub check_mnt: u8,
pub flags: i32,
pub count: u32,
pub entries_ptr: u64,
@@ -45,7 +43,6 @@ impl Default for UmountManagerCmd {
UmountManagerCmd {
operation: 0,
path: [0; 256],
check_mnt: 0,
flags: 0,
count: 0,
entries_ptr: 0,
@@ -112,7 +109,7 @@ impl UmountManager {
Ok(())
}
pub fn add_entry(&mut self, path: &str, check_mnt: bool, flags: i32) -> Result<()> {
pub fn add_entry(&mut self, path: &str, flags: i32) -> Result<()> {
let exists = self
.defaults
.iter()
@@ -126,7 +123,6 @@ impl UmountManager {
let entry = UmountEntry {
path: path.to_string(),
check_mnt,
flags,
is_default,
};
@@ -165,43 +161,36 @@ impl UmountManager {
vec![
UmountEntry {
path: "/odm".to_string(),
check_mnt: true,
flags: 0,
is_default: true,
},
UmountEntry {
path: "/system".to_string(),
check_mnt: true,
flags: 0,
is_default: true,
},
UmountEntry {
path: "/vendor".to_string(),
check_mnt: true,
flags: 0,
is_default: true,
},
UmountEntry {
path: "/product".to_string(),
check_mnt: true,
flags: 0,
is_default: true,
},
UmountEntry {
path: "/system_ext".to_string(),
check_mnt: true,
flags: 0,
is_default: true,
},
UmountEntry {
path: "/data/adb/modules".to_string(),
check_mnt: false,
flags: -1, // MNT_DETACH
is_default: true,
},
UmountEntry {
path: "/debug_ramdisk".to_string(),
check_mnt: false,
flags: -1, // MNT_DETACH
is_default: true,
},
@@ -226,7 +215,6 @@ impl UmountManager {
fn kernel_add_entry(entry: &UmountEntry) -> Result<()> {
let mut cmd = UmountManagerCmd {
operation: 0,
check_mnt: entry.check_mnt as u8,
flags: entry.flags,
..Default::default()
};
@@ -255,9 +243,9 @@ pub fn init_umount_manager() -> Result<UmountManager> {
Ok(manager)
}
pub fn add_umount_path(path: &str, check_mnt: bool, flags: i32) -> Result<()> {
pub fn add_umount_path(path: &str, flags: i32) -> Result<()> {
let mut manager = init_umount_manager()?;
manager.add_entry(path, check_mnt, flags)?;
manager.add_entry(path, flags)?;
manager.save_config()?;
println!("✓ Added umount path: {}", path);
Ok(())
@@ -290,7 +278,6 @@ pub fn list_umount_paths() -> Result<()> {
println!(
"{:<30} {:<12} {:<8} {:<10}",
entry.path,
entry.check_mnt,
entry.flags,
if entry.is_default { "Yes" } else { "No" }
);