From 48e76f9096f27875913d0c922e172d62cc6b03ae Mon Sep 17 00:00:00 2001 From: tiann Date: Tue, 4 Apr 2023 13:50:36 +0800 Subject: [PATCH] ksud: refine su and make it compitable with MagiskSU Supported features: 1. --mount-master, -M, -mm which would make the command run in global mount namespace. 2. - user to switch a specific user's shell. 3. -v, -V to print version code and name. fix #330 #306 #305 #32 --- userspace/ksud/Cargo.lock | 10 ++ userspace/ksud/Cargo.toml | 1 + userspace/ksud/src/cli.rs | 2 +- userspace/ksud/src/ksu.rs | 186 ++++++++++++++++++++++++++++++++++- userspace/ksud/src/module.rs | 30 +----- userspace/ksud/src/utils.rs | 30 +++++- 6 files changed, 227 insertions(+), 32 deletions(-) diff --git a/userspace/ksud/Cargo.lock b/userspace/ksud/Cargo.lock index 8ce422ed..11c6b942 100644 --- a/userspace/ksud/Cargo.lock +++ b/userspace/ksud/Cargo.lock @@ -584,6 +584,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.2.8" @@ -793,6 +802,7 @@ dependencies = [ "encoding", "env_logger", "extattr", + "getopts", "humansize", "is_executable", "java-properties", diff --git a/userspace/ksud/Cargo.toml b/userspace/ksud/Cargo.toml index 9e37abdb..b655efe7 100644 --- a/userspace/ksud/Cargo.toml +++ b/userspace/ksud/Cargo.toml @@ -32,6 +32,7 @@ rust-embed = { version = "6.4.2", features = [ "compression", # must clean build after updating binaries ] } which = "4.2.2" +getopts = "0.2.21" [target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] sys-mount = { git = "https://github.com/tiann/sys-mount" } diff --git a/userspace/ksud/src/cli.rs b/userspace/ksud/src/cli.rs index 977056b1..fab63de5 100644 --- a/userspace/ksud/src/cli.rs +++ b/userspace/ksud/src/cli.rs @@ -141,7 +141,7 @@ pub fn run() -> Result<()> { // the kernel executes su with argv[0] = "su" and replace it with us let arg0 = std::env::args().next().unwrap_or_default(); if arg0 == "su" || arg0 == "/system/bin/su" { - return crate::ksu::grant_root(); + return crate::ksu::root_shell(); } let cli = Args::parse(); diff --git a/userspace/ksud/src/ksu.rs b/userspace/ksud/src/ksu.rs index 31631e88..7f3456f5 100644 --- a/userspace/ksud/src/ksu.rs +++ b/userspace/ksud/src/ksu.rs @@ -1,9 +1,19 @@ -use anyhow::Result; +use anyhow::{Ok, Result}; #[cfg(unix)] use anyhow::ensure; +use getopts::Options; #[cfg(unix)] use std::os::unix::process::CommandExt; +use std::{ + ffi::CStr, + process::Command, +}; + +use crate::{ + defs, + utils::{self, umask}, +}; pub const KERNEL_SU_OPTION: u32 = 0xDEAD_BEEF; @@ -44,6 +54,180 @@ pub fn grant_root() -> Result<()> { unimplemented!("grant_root is only available on android"); } +fn print_usage(program: &str, opts: Options) { + let brief = format!("KernelSU\n\nUsage: {program} [options] [-] [user [argument...]]"); + print!("{}", opts.usage(&brief)); +} + +fn set_identity(uid: u32) { + #[cfg(any(target_os = "linux", target_os = "android"))] + unsafe { + libc::seteuid(uid); + libc::setresgid(uid, uid, uid); + libc::setresuid(uid, uid, uid); + } +} + +pub fn root_shell() -> Result<()> { + // we are root now, this was set in kernel! + let args: Vec = std::env::args().collect(); + let program = args[0].clone(); + + let mut opts = Options::new(); + opts.optopt( + "c", + "command", + "pass COMMAND to the invoked shell", + "COMMAND", + ); + opts.optflag("h", "help", "display this help message and exit"); + opts.optflag("l", "login", "force run in the global mount namespace"); + opts.optflag( + "p", + "preserve-environment", + "preserve the entire environment", + ); + opts.optflag( + "s", + "shell", + "use SHELL instead of the default /system/bin/sh", + ); + opts.optflag("v", "version", "display version number and exit"); + opts.optflag("V", "", "display version code and exit"); + opts.optflag( + "M", + "mount-master", + "force run in the global mount namespace", + ); + + // Replace -cn with -z, -mm with -M for supporting getopt_long + let args = args + .into_iter() + .map(|e| { + if e == "-mm" { + "-M".to_string() + } else if e == "-cn" { + "-z".to_string() + } else { + e + } + }) + .collect::>(); + + let matches = match opts.parse(&args[1..]) { + std::result::Result::Ok(m) => m, + Err(f) => { + println!("{f}"); + print_usage(&program, opts); + std::process::exit(-1); + } + }; + + if matches.opt_present("h") { + print_usage(&program, opts); + return Ok(()); + } + + if matches.opt_present("v") { + println!("{}:KernelSU", defs::VERSION_NAME); + return Ok(()); + } + + if matches.opt_present("V") { + println!("{}", defs::VERSION_CODE); + return Ok(()); + } + + let shell = matches.opt_str("s").unwrap_or("/system/bin/sh".to_string()); + let mut is_login = matches.opt_present("l"); + let preserve_env = matches.opt_present("p"); + let mount_master = matches.opt_present("M"); + + let mut free_idx = 0; + let command = matches.opt_str("c").map(|cmd| { + free_idx = matches.free.len(); + let mut cmds = vec![]; + cmds.push(cmd); + cmds.extend(matches.free.clone()); + cmds + }); + + let mut args = vec![]; + if let Some(cmd) = command { + args.push("-c".to_string()); + args.push(cmd.join(" ")); + }; + + if free_idx < matches.free.len() && matches.free[free_idx] == "-" { + is_login = true; + free_idx += 1; + } + + let mut uid = 0; // default uid = 0(root) + #[cfg(any(target_os = "linux", target_os = "android"))] + if free_idx < matches.free.len() { + let name = &matches.free[free_idx]; + uid = unsafe { + match libc::getpwnam(name.as_ptr() as *const u8).as_ref() { + Some(pw) => pw.pw_uid, + None => name.parse::().unwrap_or(0), + } + } + } + + // https://github.com/topjohnwu/Magisk/blob/master/native/src/su/su_daemon.cpp#L408 + let arg0 = if is_login { "-" } else { &shell }; + + let mut command = &mut Command::new(&shell); + + if !preserve_env { + // This is actually incorrect, i don't know why. + // command = command.env_clear(); + + let pw = unsafe { libc::getpwuid(uid).as_ref() }; + + if let Some(pw) = pw { + let home = unsafe { CStr::from_ptr(pw.pw_dir) }; + let pw_name = unsafe { CStr::from_ptr(pw.pw_name)}; + + let home = home.to_string_lossy(); + let pw_name = pw_name.to_string_lossy(); + + command = command + .env("HOME", home.as_ref()) + .env("USER", pw_name.as_ref()) + .env("LOGNAME", pw_name.as_ref()) + .env("SHELL", &shell); + } + } + + #[cfg(unix)] + { + // escape from the current cgroup and become session leader + command = command.process_group(0); + command = unsafe { + command.pre_exec(move || { + umask(0o22); + utils::switch_cgroups(); + + // switch to global mount namespace + #[cfg(any(target_os = "linux", target_os = "android"))] + if mount_master { + let _ = utils::switch_mnt_ns(1); + let _ = utils::unshare_mnt_ns(); + } + + set_identity(uid); + + std::result::Result::Ok(()) + }) + }; + } + + command = command.args(args).arg0(arg0); + Err(command.exec().into()) +} + pub fn get_version() -> i32 { let mut result: i32 = 0; #[cfg(any(target_os = "linux", target_os = "android"))] diff --git a/userspace/ksud/src/module.rs b/userspace/ksud/src/module.rs index 5f626d3a..74fd137f 100644 --- a/userspace/ksud/src/module.rs +++ b/userspace/ksud/src/module.rs @@ -14,8 +14,8 @@ use log::{info, warn}; use std::{ collections::HashMap, env::var as env_var, - fs::{remove_dir_all, set_permissions, File, OpenOptions, Permissions}, - io::{Cursor, Write}, + fs::{remove_dir_all, set_permissions, File, Permissions}, + io::Cursor, path::{Path, PathBuf}, process::{Command, Stdio}, str::FromStr, @@ -154,32 +154,6 @@ fn grow_image_size(img: &str, extra_size: u64) -> Result<()> { Ok(()) } -fn switch_cgroup(grp: &str, pid: u32) { - let path = Path::new(grp).join("cgroup.procs"); - if !path.exists() { - return; - } - - let fp = OpenOptions::new().append(true).open(path); - if let Ok(mut fp) = fp { - let _ = writeln!(fp, "{pid}"); - } -} - -fn switch_cgroups() { - let pid = std::process::id(); - switch_cgroup("/acct", pid); - switch_cgroup("/dev/cg2_bpf", pid); - switch_cgroup("/sys/fs/cgroup", pid); - - if getprop("ro.config.per_app_memcg") - .filter(|prop| prop == "false") - .is_none() - { - switch_cgroup("/dev/memcg/apps", pid); - } -} - pub fn load_sepolicy_rule() -> Result<()> { let modules_dir = Path::new(defs::MODULE_DIR); let dir = std::fs::read_dir(modules_dir)?; diff --git a/userspace/ksud/src/utils.rs b/userspace/ksud/src/utils.rs index a6231254..e7814d31 100644 --- a/userspace/ksud/src/utils.rs +++ b/userspace/ksud/src/utils.rs @@ -1,7 +1,7 @@ use anyhow::{bail, Context, Error, Ok, Result}; use std::{ - fs::{create_dir_all, write, File}, - io::ErrorKind::AlreadyExists, + fs::{create_dir_all, write, File, OpenOptions}, + io::{ErrorKind::AlreadyExists, Write}, path::Path, }; @@ -116,6 +116,32 @@ pub fn unshare_mnt_ns() -> Result<()> { Ok(()) } +fn switch_cgroup(grp: &str, pid: u32) { + let path = Path::new(grp).join("cgroup.procs"); + if !path.exists() { + return; + } + + let fp = OpenOptions::new().append(true).open(path); + if let std::result::Result::Ok(mut fp) = fp { + let _ = writeln!(fp, "{pid}"); + } +} + +pub fn switch_cgroups() { + let pid = std::process::id(); + switch_cgroup("/acct", pid); + switch_cgroup("/dev/cg2_bpf", pid); + switch_cgroup("/sys/fs/cgroup", pid); + + if getprop("ro.config.per_app_memcg") + .filter(|prop| prop == "false") + .is_none() + { + switch_cgroup("/dev/memcg/apps", pid); + } +} + #[cfg(any(target_os = "linux", target_os = "android"))] pub fn umask(mask: u32) { unsafe { libc::umask(mask) };