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
This commit is contained in:
tiann
2023-04-04 13:50:36 +08:00
parent 6fc8cc7e8e
commit 48e76f9096
6 changed files with 227 additions and 32 deletions

View File

@@ -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<String> = 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::<Vec<String>>();
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::<u32>().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"))]