Skip to content

Commit

Permalink
su: allocate new pty (#1693)
Browse files Browse the repository at this point in the history
  • Loading branch information
5ec1cff authored May 7, 2024
1 parent f281665 commit 935dc18
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 8 deletions.
6 changes: 0 additions & 6 deletions kernel/selinux/rules.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
4 changes: 2 additions & 2 deletions userspace/ksud/src/cli.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
}

Expand Down
2 changes: 2 additions & 0 deletions userspace/ksud/src/defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
6 changes: 6 additions & 0 deletions userspace/ksud/src/init_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions userspace/ksud/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
19 changes: 19 additions & 0 deletions userspace/ksud/src/mount.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -181,6 +182,19 @@ pub fn mount_tmpfs(dest: impl AsRef<Path>) -> Result<()> {
Ok(())
}

#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn mount_devpts(dest: impl AsRef<Path>) -> 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<Path>, to: impl AsRef<Path>) -> Result<()> {
info!(
Expand Down Expand Up @@ -325,3 +339,8 @@ pub fn mount_overlay(
pub fn mount_tmpfs(_dest: impl AsRef<Path>) -> Result<()> {
unimplemented!()
}

#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn mount_devpts(_dest: impl AsRef<Path>) -> Result<()> {
unimplemented!()
}
192 changes: 192 additions & 0 deletions userspace/ksud/src/pty.rs
Original file line number Diff line number Diff line change
@@ -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<F: AsFd>(fd: F) -> Result<u32> {
Ok(unsafe {
let tiocgptn = Getter::<ReadOpcode<b'T', 0x30, u32>, u32>::new();
ioctl(fd, tiocgptn)?
})
}

static mut OLD_STDIN: Option<Termios> = None;

fn watch_sigwinch_async(slave: RawFd) {
let mut winch = MaybeUninit::<sigset_t>::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::<sigset_t>::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::<winsize>::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<R: Read, W: Write>(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(())
}
10 changes: 10 additions & 0 deletions userspace/ksud/src/su.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 935dc18

Please sign in to comment.