diff --git a/native/src/core/Cargo.toml b/native/src/core/Cargo.toml index 6f3cc70a9..5ef746420 100644 --- a/native/src/core/Cargo.toml +++ b/native/src/core/Cargo.toml @@ -26,4 +26,4 @@ bytemuck = { workspace = true, features = ["derive"] } thiserror = { workspace = true } bit-set = { workspace = true } argh = { workspace = true } -nix = { workspace = true, features = ["fs", "poll", "signal"] } +nix = { workspace = true, features = ["fs", "poll", "signal", "term"] } diff --git a/native/src/core/lib.rs b/native/src/core/lib.rs index d920a090f..aa2e53bdf 100644 --- a/native/src/core/lib.rs +++ b/native/src/core/lib.rs @@ -21,7 +21,7 @@ use std::fs::File; use std::mem::ManuallyDrop; use std::ops::DerefMut; use std::os::fd::FromRawFd; -use su::{get_pty_num, pump_tty, restore_stdin}; +use su::{get_pty_num, pump_tty}; use zygisk::zygisk_should_load_module; #[path = "../include/consts.rs"] @@ -181,9 +181,8 @@ pub mod ffi { fn recv_fd(socket: i32) -> i32; fn recv_fds(socket: i32) -> Vec; fn write_to_fd(self: &SuRequest, fd: i32); - fn pump_tty(infd: i32, outfd: i32); + fn pump_tty(ptmx: i32, pump_stdin: bool); fn get_pty_num(fd: i32) -> i32; - fn restore_stdin() -> bool; fn restorecon(); fn lgetfilecon(path: Utf8CStrRef, con: &mut [u8]) -> bool; fn setfilecon(path: Utf8CStrRef, con: Utf8CStrRef) -> bool; diff --git a/native/src/core/su/mod.rs b/native/src/core/su/mod.rs index 60dcc4471..43f44e516 100644 --- a/native/src/core/su/mod.rs +++ b/native/src/core/su/mod.rs @@ -4,4 +4,4 @@ mod db; mod pts; pub use daemon::SuInfo; -pub use pts::{get_pty_num, pump_tty, restore_stdin}; +pub use pts::{get_pty_num, pump_tty}; diff --git a/native/src/core/su/pts.rs b/native/src/core/su/pts.rs index 31acb0247..ecae3dabe 100644 --- a/native/src/core/su/pts.rs +++ b/native/src/core/su/pts.rs @@ -1,13 +1,12 @@ -use base::{FileOrStd, LibcReturn, LoggedResult, OsResult, libc, warn}; -use libc::{ - STDIN_FILENO, STDOUT_FILENO, TCSADRAIN, TCSAFLUSH, TIOCGWINSZ, TIOCSWINSZ, c_int, cfmakeraw, - ssize_t, tcgetattr, tcsetattr, termios, winsize, -}; +use base::{FileOrStd, LibcReturn, LoggedResult, OsResult, ResultExt, libc, warn}; +use libc::{STDIN_FILENO, STDOUT_FILENO, TIOCGWINSZ, TIOCSWINSZ, c_int, ssize_t, winsize}; use nix::{ fcntl::OFlag, - poll::{PollFd, PollFlags, PollTimeout}, - sys::signal::{SigSet, Signal}, + poll::{PollFd, PollFlags, PollTimeout, poll}, + sys::signal::{SigSet, Signal, raise}, sys::signalfd::{SfdFlags, SignalFd}, + sys::termios::{SetArg, Termios, cfmakeraw, tcgetattr, tcsetattr}, + unistd::pipe2, }; use std::fs::File; use std::io::{Read, Write}; @@ -16,7 +15,6 @@ use std::os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd, RawFd}; use std::ptr::null_mut; use std::sync::atomic::{AtomicBool, Ordering}; -static mut OLD_STDIN: Option = None; static SHOULD_USE_SPLICE: AtomicBool = AtomicBool::new(true); const TIOCGPTN: u32 = 0x80045430; @@ -33,51 +31,10 @@ pub fn get_pty_num(fd: i32) -> i32 { pty_num } -fn set_stdin_raw() -> bool { - unsafe { - let mut termios: termios = std::mem::zeroed(); - - if tcgetattr(STDIN_FILENO, &mut termios) < 0 { - return false; - } - - let old_c_oflag = termios.c_oflag; - OLD_STDIN = Some(termios); - - cfmakeraw(&mut termios); - - // don't modify output flags, since we are not setting stdout raw - termios.c_oflag = old_c_oflag; - - if tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios) < 0 - && tcsetattr(STDIN_FILENO, TCSADRAIN, &termios) < 0 - { - warn!("Failed to set terminal attributes"); - return false; - } - } - - true -} - -pub fn restore_stdin() -> bool { - unsafe { - if let Some(ref termios) = OLD_STDIN - && tcsetattr(STDIN_FILENO, TCSAFLUSH, termios) < 0 - && tcsetattr(STDIN_FILENO, TCSADRAIN, termios) < 0 - { - warn!("Failed to restore terminal attributes"); - return false; - } - OLD_STDIN = None; - true - } -} - -fn resize_pty(outfd: i32) { +fn sync_winsize(ptmx: i32) { let mut ws: winsize = unsafe { std::mem::zeroed() }; if unsafe { ioctl(STDIN_FILENO, TIOCGWINSZ as u32, &mut ws) } >= 0 { - unsafe { ioctl(outfd, TIOCSWINSZ as u32, &ws) }; + unsafe { ioctl(ptmx, TIOCSWINSZ as u32, &ws) }; } } @@ -114,21 +71,55 @@ fn pump_via_splice(infd: RawFd, outfd: RawFd, pipe: &(OwnedFd, OwnedFd)) -> Logg Ok(()) } -fn pump_tty_impl(infd: OwnedFd, raw_out: RawFd) -> LoggedResult<()> { - let mut set = SigSet::empty(); - set.add(Signal::SIGWINCH); - set.thread_block() - .check_os_err("pthread_sigmask", None, None)?; - let signal_fd = - SignalFd::with_flags(&set, SfdFlags::SFD_CLOEXEC).into_os_result("signalfd", None, None)?; +fn set_stdin_raw() -> LoggedResult { + let mut term = tcgetattr(FileOrStd::StdIn.as_file())?; + let old_term = term.clone(); - let raw_in = infd.as_raw_fd(); - let raw_sig = signal_fd.as_raw_fd(); + let old_output_flags = old_term.output_flags; + cfmakeraw(&mut term); + + // Preserve output_flags, since we are not setting stdout raw + term.output_flags = old_output_flags; + + tcsetattr(FileOrStd::StdIn.as_file(), SetArg::TCSAFLUSH, &term) + .or_else(|_| tcsetattr(FileOrStd::StdIn.as_file(), SetArg::TCSADRAIN, &term)) + .check_os_err("tcsetattr", None, None) + .log_with_msg(|w| w.write_str("Failed to set terminal attributes"))?; + + Ok(old_term) +} + +fn restore_stdin(term: Termios) -> LoggedResult<()> { + tcsetattr(FileOrStd::StdIn.as_file(), SetArg::TCSAFLUSH, &term) + .or_else(|_| tcsetattr(FileOrStd::StdIn.as_file(), SetArg::TCSADRAIN, &term)) + .check_os_err("tcsetattr", None, None) + .log_with_msg(|w| w.write_str("Failed to restore terminal attributes")) +} + +fn pump_tty_impl(ptmx: OwnedFd, pump_stdin: bool) -> LoggedResult<()> { + let mut signal_fd: Option = None; + + let raw_ptmx = ptmx.as_raw_fd(); + let mut raw_sig = -1; let mut poll_fds = Vec::with_capacity(3); - poll_fds.push(PollFd::new(infd.as_fd(), PollFlags::POLLIN)); - poll_fds.push(PollFd::new(signal_fd.as_fd(), PollFlags::POLLIN)); - if raw_out >= 0 { + poll_fds.push(PollFd::new(ptmx.as_fd(), PollFlags::POLLIN)); + if pump_stdin { + // If stdin is tty, we need to monitor SIGWINCH + let mut set = SigSet::empty(); + set.add(Signal::SIGWINCH); + set.thread_block() + .check_os_err("pthread_sigmask", None, None)?; + let sig = SignalFd::with_flags(&set, SfdFlags::SFD_CLOEXEC) + .into_os_result("signalfd", None, None)?; + raw_sig = sig.as_raw_fd(); + signal_fd = Some(sig); + poll_fds.push(PollFd::new( + signal_fd.as_ref().unwrap().as_fd(), + PollFlags::POLLIN, + )); + + // We also need to pump stdin to ptmx poll_fds.push(PollFd::new( FileOrStd::StdIn.as_file().as_fd(), PollFlags::POLLIN, @@ -139,21 +130,21 @@ fn pump_tty_impl(infd: OwnedFd, raw_out: RawFd) -> LoggedResult<()> { let stop_flags = PollFlags::POLLERR | PollFlags::POLLHUP | PollFlags::POLLNVAL; // Open a pipe to bypass userspace copy with splice - let pipe_fd = nix::unistd::pipe2(OFlag::O_CLOEXEC).into_os_result("pipe2", None, None)?; + let pipe_fd = pipe2(OFlag::O_CLOEXEC).into_os_result("pipe2", None, None)?; 'poll: loop { // Wait for event - nix::poll::poll(&mut poll_fds, PollTimeout::NONE).check_os_err("poll", None, None)?; + poll(&mut poll_fds, PollTimeout::NONE).check_os_err("poll", None, None)?; for pfd in &poll_fds { if pfd.all().unwrap_or(false) { let raw_fd = pfd.as_fd().as_raw_fd(); if raw_fd == STDIN_FILENO { - pump_via_splice(STDIN_FILENO, raw_out, &pipe_fd)?; - } else if raw_fd == raw_in { - pump_via_splice(raw_in, STDOUT_FILENO, &pipe_fd)?; + pump_via_splice(STDIN_FILENO, raw_ptmx, &pipe_fd)?; + } else if raw_fd == raw_ptmx { + pump_via_splice(raw_ptmx, STDOUT_FILENO, &pipe_fd)?; } else if raw_fd == raw_sig { - resize_pty(raw_out); - signal_fd.read_signal()?; + sync_winsize(raw_ptmx); + signal_fd.as_ref().unwrap().read_signal()?; } } else if pfd .revents() @@ -168,12 +159,19 @@ fn pump_tty_impl(infd: OwnedFd, raw_out: RawFd) -> LoggedResult<()> { Ok(()) } -pub fn pump_tty(infd: i32, outfd: i32) { - set_stdin_raw(); +pub fn pump_tty(ptmx: RawFd, pump_stdin: bool) { + let old_term = if pump_stdin { + sync_winsize(ptmx); + set_stdin_raw().ok() + } else { + None + }; - let infd = unsafe { OwnedFd::from_raw_fd(infd) }; - pump_tty_impl(infd, outfd).ok(); + let ptmx = unsafe { OwnedFd::from_raw_fd(ptmx) }; + pump_tty_impl(ptmx, pump_stdin).ok(); - restore_stdin(); - nix::sys::signal::raise(Signal::SIGWINCH).ok(); + if let Some(term) = old_term { + restore_stdin(term).ok(); + } + raise(Signal::SIGWINCH).ok(); } diff --git a/native/src/core/su/su.cpp b/native/src/core/su/su.cpp index 15410e45a..07fa4423a 100644 --- a/native/src/core/su/su.cpp +++ b/native/src/core/su/su.cpp @@ -63,14 +63,8 @@ int quit_signals[] = { SIGALRM, SIGABRT, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGI } static void sighandler(int sig) { - restore_stdin(); - - // Assume we'll only be called before death - // See note before sigaction() in set_stdin_raw() - // - // Now, close all standard I/O to cause the pumps - // to exit so we can continue and retrieve the exit - // code + // Close all standard I/O to cause the pumps to exit + // so we can continue and retrieve the exit code. close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); @@ -243,9 +237,9 @@ int su_client_main(int argc, char *argv[]) { write_int(fd, 1); int ptmx = recv_fd(fd); setup_sighandlers(sighandler); - // if stdin is not a tty, if we pump to ptmx, our process may intercept the input to ptmx and + // If stdin is not a tty, and if we pump to ptmx, our process may intercept the input to ptmx and // output to stdout, which cause the target process lost input. - pump_tty(ptmx, (atty & ATTY_IN) ? ptmx : -1); + pump_tty(ptmx, atty & ATTY_IN); } else { write_int(fd, 0); }