322 lines
9.4 KiB
Rust
322 lines
9.4 KiB
Rust
use crate::{
|
|
defs,
|
|
utils::{self, umask},
|
|
};
|
|
use anyhow::{Context, Ok, Result, bail};
|
|
use getopts::Options;
|
|
use libc::c_int;
|
|
use log::error;
|
|
#[cfg(unix)]
|
|
use std::os::unix::process::CommandExt;
|
|
use std::{
|
|
env,
|
|
ffi::{CStr, CString},
|
|
path::PathBuf,
|
|
process::Command,
|
|
};
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
use crate::ksucalls::get_wrapped_fd;
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
use rustix::{
|
|
process::getuid,
|
|
thread::{Gid, Uid, set_thread_res_gid, set_thread_res_uid},
|
|
};
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
pub fn grant_root(global_mnt: bool) -> Result<()> {
|
|
crate::ksucalls::grant_root()?;
|
|
|
|
let mut command = Command::new("sh");
|
|
let command = unsafe {
|
|
command.pre_exec(move || {
|
|
if global_mnt {
|
|
let _ = utils::switch_mnt_ns(1);
|
|
}
|
|
Result::Ok(())
|
|
})
|
|
};
|
|
// add /data/adb/ksu/bin to PATH
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
add_path_to_env(defs::BINARY_DIR)?;
|
|
Err(command.exec().into())
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
|
pub fn grant_root(_global_mnt: bool) -> 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, gid: u32, groups: &[u32]) {
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
{
|
|
rustix::thread::set_thread_groups(
|
|
groups
|
|
.iter()
|
|
.map(|g| unsafe { Gid::from_raw(*g) })
|
|
.collect::<Vec<_>>()
|
|
.as_ref(),
|
|
)
|
|
.ok();
|
|
let gid = unsafe { Gid::from_raw(gid) };
|
|
let uid = unsafe { Uid::from_raw(uid) };
|
|
set_thread_res_gid(gid, gid, gid).ok();
|
|
set_thread_res_uid(uid, uid, uid).ok();
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "android")]
|
|
fn wrap_tty(fd: c_int) {
|
|
let inner_fn = move || -> Result<()> {
|
|
if unsafe { libc::isatty(fd) != 1 } {
|
|
return Ok(());
|
|
}
|
|
let new_fd = get_wrapped_fd(fd).context("get_wrapped_fd")?;
|
|
if unsafe { libc::dup2(new_fd, fd) } == -1 {
|
|
bail!("dup {new_fd} -> {fd} errno: {}", unsafe {
|
|
*libc::__errno()
|
|
});
|
|
}
|
|
unsafe { libc::close(new_fd) };
|
|
Ok(())
|
|
};
|
|
|
|
if let Err(e) = inner_fn() {
|
|
error!("wrap tty {fd}: {e:?}");
|
|
}
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
|
pub fn root_shell() -> Result<()> {
|
|
unimplemented!()
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
#[allow(clippy::similar_names)]
|
|
pub fn root_shell() -> Result<()> {
|
|
// we are root now, this was set in kernel!
|
|
|
|
use anyhow::anyhow;
|
|
let env_args: Vec<String> = env::args().collect();
|
|
let program = env_args[0].clone();
|
|
let args = env_args.iter().position(|arg| arg == "-c").map_or_else(
|
|
|| env_args.clone(),
|
|
|i| {
|
|
let rest = env_args[i + 1..].to_vec();
|
|
let mut new_args = env_args[..i].to_vec();
|
|
new_args.push("-c".to_string());
|
|
if !rest.is_empty() {
|
|
new_args.push(rest.join(" "));
|
|
}
|
|
new_args
|
|
},
|
|
);
|
|
|
|
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", "pretend the shell to be a login shell");
|
|
opts.optflag(
|
|
"p",
|
|
"preserve-environment",
|
|
"preserve the entire environment",
|
|
);
|
|
opts.optopt(
|
|
"s",
|
|
"shell",
|
|
"use SHELL instead of the default /system/bin/sh",
|
|
"SHELL",
|
|
);
|
|
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",
|
|
);
|
|
opts.optopt("g", "group", "Specify the primary group", "GROUP");
|
|
opts.optmulti(
|
|
"G",
|
|
"supp-group",
|
|
"Specify a supplementary group. The first specified supplementary group is also used as a primary group if the option -g is not specified.",
|
|
"GROUP",
|
|
);
|
|
opts.optflag("W", "no-wrapper", "don't use ksu fd wrapper");
|
|
|
|
// 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..]) {
|
|
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_else(|| "/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 use_fd_wrapper = !matches.opt_present("W");
|
|
|
|
let groups = matches
|
|
.opt_strs("G")
|
|
.into_iter()
|
|
.map(|g| g.parse::<u32>().map_err(|_| anyhow!("Invalid GID: {}", g)))
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
// if -g provided, use it.
|
|
let mut gid = matches
|
|
.opt_str("g")
|
|
.map(|g| g.parse::<u32>().map_err(|_| anyhow!("Invalid GID: {}", g)))
|
|
.transpose()?;
|
|
|
|
// otherwise, use the first gid of groups.
|
|
if gid.is_none() && !groups.is_empty() {
|
|
gid = Some(groups[0]);
|
|
}
|
|
|
|
// we've make sure that -c is the last option and it already contains the whole command, no need to construct it again
|
|
let args = matches
|
|
.opt_str("c")
|
|
.map(|cmd| vec!["-c".to_string(), cmd])
|
|
.unwrap_or_default();
|
|
|
|
let mut free_idx = 0;
|
|
if !matches.free.is_empty() && matches.free[free_idx] == "-" {
|
|
is_login = true;
|
|
free_idx += 1;
|
|
}
|
|
|
|
// use current uid if no user specified, these has been done in kernel!
|
|
let mut uid = getuid().as_raw();
|
|
if free_idx < matches.free.len() {
|
|
let name = &matches.free[free_idx];
|
|
uid = unsafe {
|
|
let pw = CString::new(name.as_str())
|
|
.ok()
|
|
.and_then(|c_name| libc::getpwnam(c_name.as_ptr()).as_ref());
|
|
|
|
pw.map_or_else(|| name.parse::<u32>().unwrap_or(0), |pw| pw.pw_uid)
|
|
}
|
|
}
|
|
|
|
// if there is no gid provided, use uid.
|
|
let gid = gid.unwrap_or(uid);
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
// add /data/adb/ksu/bin to PATH
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
add_path_to_env(defs::BINARY_DIR)?;
|
|
|
|
// when KSURC_PATH exists and ENV is not set, set ENV to KSURC_PATH
|
|
if PathBuf::from(defs::KSURC_PATH).exists() && env::var("ENV").is_err() {
|
|
command = command.env("ENV", defs::KSURC_PATH);
|
|
}
|
|
|
|
// escape from the current cgroup and become session leader
|
|
// WARNING!!! This cause some root shell hang forever!
|
|
// 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);
|
|
}
|
|
|
|
#[cfg(target_os = "android")]
|
|
if use_fd_wrapper {
|
|
wrap_tty(0);
|
|
wrap_tty(1);
|
|
wrap_tty(2);
|
|
}
|
|
|
|
set_identity(uid, gid, &groups);
|
|
|
|
Result::Ok(())
|
|
})
|
|
};
|
|
|
|
command = command.args(args).arg0(arg0);
|
|
Err(command.exec().into())
|
|
}
|
|
|
|
fn add_path_to_env(path: &str) -> Result<()> {
|
|
let mut paths =
|
|
env::var_os("PATH").map_or(Vec::new(), |val| env::split_paths(&val).collect::<Vec<_>>());
|
|
let new_path = PathBuf::from(path.trim_end_matches('/'));
|
|
paths.push(new_path);
|
|
let new_path_env = env::join_paths(paths)?;
|
|
unsafe { env::set_var("PATH", new_path_env) };
|
|
Ok(())
|
|
}
|