diff --git a/kernel/selinux/rules.c b/kernel/selinux/rules.c index 7c3f7b33b212..1ba6d853f2a9 100644 --- a/kernel/selinux/rules.c +++ b/kernel/selinux/rules.c @@ -130,12 +130,6 @@ void apply_kernelsu_rules() // Allow all binder transactions ksu_allow(db, ALL, KERNEL_SU_DOMAIN, "binder", ALL); - // Allow system server devpts - ksu_allow(db, "system_server", "untrusted_app_all_devpts", "chr_file", - "read"); - ksu_allow(db, "system_server", "untrusted_app_all_devpts", "chr_file", - "write"); - // Allow system server kill su process ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "getpgid"); ksu_allow(db, "system_server", KERNEL_SU_DOMAIN, "process", "sigkill"); diff --git a/userspace/ksud/src/cli.rs b/userspace/ksud/src/cli.rs index cd96a360f346..b3954bd168a8 100644 --- a/userspace/ksud/src/cli.rs +++ b/userspace/ksud/src/cli.rs @@ -1,6 +1,6 @@ use anyhow::{Ok, Result}; use clap::Parser; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; #[cfg(target_os = "android")] use android_logger::Config; @@ -283,7 +283,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" { + if Path::new(&arg0).file_name().and_then(|f| f.to_str()) == Some("su") { return crate::su::root_shell(); } diff --git a/userspace/ksud/src/defs.rs b/userspace/ksud/src/defs.rs index c4b9fc3f989b..bcde77120ee4 100644 --- a/userspace/ksud/src/defs.rs +++ b/userspace/ksud/src/defs.rs @@ -43,3 +43,5 @@ pub const VERSION_NAME: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_N pub const KSU_BACKUP_DIR: &str = WORKING_DIR; pub const KSU_BACKUP_FILE_PREFIX: &str = "ksu_backup_"; pub const BACKUP_FILENAME: &str = "stock_image.sha1"; + +pub const PTS_NAME: &str = "pts"; diff --git a/userspace/ksud/src/init_event.rs b/userspace/ksud/src/init_event.rs index 7107ff74225d..f2334d1bf897 100644 --- a/userspace/ksud/src/init_event.rs +++ b/userspace/ksud/src/init_event.rs @@ -2,6 +2,7 @@ use anyhow::{bail, Context, Result}; use log::{info, warn}; use std::{collections::HashMap, path::Path}; +use crate::defs::PTS_NAME; use crate::module::prune_modules; use crate::{ assets, defs, ksucalls, mount, restorecon, @@ -193,6 +194,11 @@ pub fn on_post_data_fs() -> Result<()> { // mount temp dir if let Err(e) = mount::mount_tmpfs(utils::get_tmp_path()) { warn!("do temp dir mount failed: {}", e); + } else { + let pts_dir = format!("{}/{PTS_NAME}", utils::get_tmp_path()); + if let Err(e) = mount::mount_devpts(pts_dir) { + warn!("do devpts mount failed: {}", e); + } } // exec modules post-fs-data scripts diff --git a/userspace/ksud/src/main.rs b/userspace/ksud/src/main.rs index 3b51720557c1..b22de94805bb 100644 --- a/userspace/ksud/src/main.rs +++ b/userspace/ksud/src/main.rs @@ -9,6 +9,8 @@ mod ksucalls; mod module; mod mount; mod profile; +#[cfg(any(target_os = "linux", target_os = "android"))] +mod pty; mod restorecon; mod sepolicy; mod su; diff --git a/userspace/ksud/src/mount.rs b/userspace/ksud/src/mount.rs index 11be898bbeb7..8eb0eb20908d 100644 --- a/userspace/ksud/src/mount.rs +++ b/userspace/ksud/src/mount.rs @@ -1,4 +1,5 @@ use anyhow::{anyhow, bail, Ok, Result}; +use std::fs::create_dir; #[cfg(any(target_os = "linux", target_os = "android"))] use anyhow::Context; @@ -181,6 +182,19 @@ pub fn mount_tmpfs(dest: impl AsRef) -> Result<()> { Ok(()) } +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn mount_devpts(dest: impl AsRef) -> Result<()> { + create_dir(dest.as_ref())?; + mount( + KSU_OVERLAY_SOURCE, + dest.as_ref(), + "devpts", + MountFlags::empty(), + "newinstance", + )?; + Ok(()) +} + #[cfg(any(target_os = "linux", target_os = "android"))] pub fn bind_mount(from: impl AsRef, to: impl AsRef) -> Result<()> { info!( @@ -325,3 +339,8 @@ pub fn mount_overlay( pub fn mount_tmpfs(_dest: impl AsRef) -> Result<()> { unimplemented!() } + +#[cfg(not(any(target_os = "linux", target_os = "android")))] +pub fn mount_devpts(_dest: impl AsRef) -> Result<()> { + unimplemented!() +} diff --git a/userspace/ksud/src/pty.rs b/userspace/ksud/src/pty.rs new file mode 100644 index 000000000000..80a0d18d81e1 --- /dev/null +++ b/userspace/ksud/src/pty.rs @@ -0,0 +1,192 @@ +use std::ffi::c_int; +use std::fs::File; +use std::io::{stderr, stdin, stdout, Read, Write}; +use std::mem::MaybeUninit; +use std::os::fd::{AsFd, AsRawFd, OwnedFd, RawFd}; +use std::process::exit; +use std::ptr::null_mut; +use std::thread; + +use anyhow::{bail, Ok, Result}; +use libc::{ + __errno, fork, pthread_sigmask, sigaddset, sigemptyset, sigset_t, sigwait, waitpid, winsize, + EINTR, SIGWINCH, SIG_BLOCK, SIG_UNBLOCK, TIOCGWINSZ, TIOCSWINSZ, +}; +use rustix::fs::{open, Mode, OFlags}; +use rustix::io::dup; +use rustix::ioctl::{ioctl, Getter, ReadOpcode}; +use rustix::process::setsid; +use rustix::pty::{grantpt, unlockpt}; +use rustix::stdio::{dup2_stderr, dup2_stdin, dup2_stdout}; +use rustix::termios::{isatty, tcgetattr, tcsetattr, OptionalActions, Termios}; + +use crate::defs::PTS_NAME; +use crate::utils::get_tmp_path; + +// https://github.com/topjohnwu/Magisk/blob/5627053b7481618adfdf8fa3569b48275589915b/native/src/core/su/pts.cpp + +fn get_pty_num(fd: F) -> Result { + Ok(unsafe { + let tiocgptn = Getter::, u32>::new(); + ioctl(fd, tiocgptn)? + }) +} + +static mut OLD_STDIN: Option = None; + +fn watch_sigwinch_async(slave: RawFd) { + let mut winch = MaybeUninit::::uninit(); + unsafe { + sigemptyset(winch.as_mut_ptr()); + sigaddset(winch.as_mut_ptr(), SIGWINCH); + pthread_sigmask(SIG_BLOCK, winch.as_mut_ptr(), null_mut()); + } + + thread::spawn(move || unsafe { + let mut winch = MaybeUninit::::uninit(); + sigemptyset(winch.as_mut_ptr()); + sigaddset(winch.as_mut_ptr(), SIGWINCH); + pthread_sigmask(SIG_UNBLOCK, winch.as_mut_ptr(), null_mut()); + let mut sig: c_int = 0; + loop { + let mut w = MaybeUninit::::uninit(); + if libc::ioctl(1, TIOCGWINSZ, w.as_mut_ptr()) < 0 { + continue; + } + libc::ioctl(slave, TIOCSWINSZ, w.as_mut_ptr()); + if sigwait(winch.as_mut_ptr(), &mut sig) != 0 { + break; + } + } + }); +} + +fn set_stdin_raw() { + let mut termios = match tcgetattr(stdin()) { + Result::Ok(termios) => { + unsafe { + OLD_STDIN = Some(termios.clone()); + } + termios + } + Err(_) => return, + }; + + termios.make_raw(); + + if tcsetattr(stdin(), OptionalActions::Flush, &termios).is_err() { + let _ = tcsetattr(stdin(), OptionalActions::Drain, &termios); + } +} + +fn restore_stdin() { + let Some(termios) = (unsafe { OLD_STDIN.take() }) else { + return; + }; + + if tcsetattr(stdin(), OptionalActions::Flush, &termios).is_err() { + let _ = tcsetattr(stdin(), OptionalActions::Drain, &termios); + } +} + +fn pump(mut from: R, mut to: W) { + let mut buf = [0u8; 4096]; + loop { + match from.read(&mut buf) { + Result::Ok(len) => { + if len == 0 { + return; + } + if to.write_all(&buf[0..len]).is_err() { + return; + } + if to.flush().is_err() { + return; + } + } + Err(_) => { + return; + } + } + } +} + +fn pump_stdin_async(mut ptmx: File) { + set_stdin_raw(); + + thread::spawn(move || { + let mut stdin = stdin(); + pump(&mut stdin, &mut ptmx); + }); +} + +fn pump_stdout_blocking(mut ptmx: File) { + let mut stdout = stdout(); + pump(&mut ptmx, &mut stdout); + + restore_stdin(); +} + +fn create_transfer(ptmx: OwnedFd) -> Result<()> { + let pid = unsafe { fork() }; + match pid { + d if d < 0 => bail!("fork"), + 0 => return Ok(()), + _ => {} + } + + let ptmx_r = ptmx; + let ptmx_w = dup(&ptmx_r).unwrap(); + + let ptmx_r = File::from(ptmx_r); + let ptmx_w = File::from(ptmx_w); + + watch_sigwinch_async(ptmx_w.as_raw_fd()); + pump_stdin_async(ptmx_r); + pump_stdout_blocking(ptmx_w); + + let mut status: c_int = -1; + + unsafe { + loop { + if waitpid(pid, &mut status, 0) == -1 && *__errno() != EINTR { + continue; + } + break; + } + } + + exit(status) +} + +pub fn prepare_pty() -> Result<()> { + let tty_in = isatty(stdin()); + let tty_out = isatty(stdout()); + let tty_err = isatty(stderr()); + if !tty_in && !tty_out && !tty_err { + return Ok(()); + } + + let mut pts_path = format!("{}/{}", get_tmp_path(), PTS_NAME); + if !std::path::Path::new(&pts_path).exists() { + pts_path = "/dev/pts".to_string(); + } + let ptmx_path = format!("{}/ptmx", pts_path); + let ptmx_fd = open(ptmx_path, OFlags::RDWR, Mode::empty())?; + grantpt(&ptmx_fd)?; + unlockpt(&ptmx_fd)?; + let pty_num = get_pty_num(&ptmx_fd)?; + create_transfer(ptmx_fd)?; + setsid()?; + let pty_fd = open(format!("{pts_path}/{pty_num}"), OFlags::RDWR, Mode::empty())?; + if tty_in { + dup2_stdin(&pty_fd)?; + } + if tty_out { + dup2_stdout(&pty_fd)?; + } + if tty_err { + dup2_stderr(&pty_fd)?; + } + Ok(()) +} diff --git a/userspace/ksud/src/su.rs b/userspace/ksud/src/su.rs index b19bf7a21ca1..d97eff49a1f9 100644 --- a/userspace/ksud/src/su.rs +++ b/userspace/ksud/src/su.rs @@ -11,6 +11,8 @@ use crate::{ utils::{self, umask}, }; +#[cfg(any(target_os = "linux", target_os = "android"))] +use crate::pty::prepare_pty; #[cfg(any(target_os = "linux", target_os = "android"))] use rustix::{ process::getuid, @@ -138,6 +140,7 @@ pub fn root_shell() -> Result<()> { "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("", "no-pty", "Do not allocate a new pseudo terminal."); // Replace -cn with -z, -mm with -M for supporting getopt_long let args = args @@ -265,6 +268,13 @@ pub fn root_shell() -> Result<()> { command = command.env("ENV", defs::KSURC_PATH); } + #[cfg(target_os = "android")] + if !matches.opt_present("no-pty") { + if let Err(e) = prepare_pty() { + log::error!("failed to prepare pty: {:?}", e); + } + } + // escape from the current cgroup and become session leader // WARNING!!! This cause some root shell hang forever! // command = command.process_group(0);