Skip to content

Commit aa5394f

Browse files
MggMugginssquell
authored andcommitted
pam: poll in read_password* for timeouts
1 parent aa518f4 commit aa5394f

File tree

2 files changed

+90
-9
lines changed

2 files changed

+90
-9
lines changed

src/pam/rpassword.rs

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
/// (although much more robust than in the original code)
1515
///
1616
use std::io::{self, Error, ErrorKind, Read};
17-
use std::os::fd::{AsFd, AsRawFd};
17+
use std::os::fd::{AsFd, AsRawFd, BorrowedFd};
18+
use std::time::Instant;
1819
use std::{fs, mem};
1920

2021
use libc::{tcsetattr, termios, ECHO, ECHONL, ICANON, TCSANOW, VEOF, VERASE, VKILL};
2122

2223
use crate::cutils::cerr;
24+
use crate::system::time::Duration;
2325

2426
use super::securemem::PamBuffer;
2527

@@ -177,6 +179,66 @@ fn write_unbuffered(sink: &mut dyn io::Write, text: &[u8]) -> io::Result<()> {
177179
sink.flush()
178180
}
179181

182+
struct TimeoutRead<'a> {
183+
timeout_at: Option<Instant>,
184+
fd: BorrowedFd<'a>,
185+
}
186+
187+
impl<'a> TimeoutRead<'a> {
188+
fn new(fd: BorrowedFd<'a>, timeout: Option<Duration>) -> TimeoutRead<'a> {
189+
TimeoutRead {
190+
timeout_at: timeout.map(|timeout| Instant::now() + timeout.into()),
191+
fd,
192+
}
193+
}
194+
}
195+
196+
impl io::Read for TimeoutRead<'_> {
197+
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
198+
let pollmask = libc::POLLIN | libc::POLLRDHUP;
199+
200+
let mut pollfd = [libc::pollfd {
201+
fd: self.fd.as_raw_fd(),
202+
events: pollmask,
203+
revents: 0,
204+
}; 1];
205+
206+
let timeout = match self.timeout_at {
207+
Some(timeout_at) => {
208+
let now = Instant::now();
209+
if now > timeout_at {
210+
return Err(io::Error::from(ErrorKind::TimedOut));
211+
}
212+
213+
(timeout_at - now)
214+
.as_millis()
215+
.try_into()
216+
.unwrap_or(i32::MAX)
217+
}
218+
None => -1,
219+
};
220+
221+
// SAFETY: pollfd is initialized and its length matches
222+
cerr(unsafe { libc::poll(pollfd.as_mut_ptr(), pollfd.len() as u64, timeout) })?;
223+
224+
// There may yet be data waiting to be read even if POLLHUP is set.
225+
if pollfd[0].revents & (pollmask | libc::POLLHUP) > 0 {
226+
// SAFETY: buf is initialized and its length matches
227+
let ret = cerr(unsafe {
228+
libc::read(
229+
self.fd.as_raw_fd(),
230+
buf.as_mut_ptr() as *mut libc::c_void,
231+
buf.len(),
232+
)
233+
})?;
234+
235+
Ok(ret as usize)
236+
} else {
237+
Err(io::Error::from(io::ErrorKind::TimedOut))
238+
}
239+
}
240+
}
241+
180242
/// A data structure representing either /dev/tty or /dev/stdin+stderr
181243
pub enum Terminal<'a> {
182244
Tty(fs::File),
@@ -201,20 +263,23 @@ impl Terminal<'_> {
201263

202264
/// Reads input with TTY echo disabled
203265
pub fn read_password(&mut self) -> io::Result<PamBuffer> {
204-
let input = self.source();
266+
let mut input = self.source_timeout(None);
205267
let _hide_input = HiddenInput::new(false)?;
206-
read_unbuffered(input)
268+
read_unbuffered(&mut input)
207269
}
208270

209271
/// Reads input with TTY echo disabled, but do provide visual feedback while typing.
210272
pub fn read_password_with_feedback(&mut self) -> io::Result<PamBuffer> {
211-
if let Some(hide_input) = HiddenInput::new(true)? {
212-
match self {
213-
Terminal::StdIE(x, y) => read_unbuffered_with_feedback(x, y, &hide_input),
214-
Terminal::Tty(x) => read_unbuffered_with_feedback(&mut &*x, &mut &*x, &hide_input),
273+
match (HiddenInput::new(true)?, self) {
274+
(Some(hide_input), Terminal::StdIE(stdin, stdout)) => {
275+
let mut reader = TimeoutRead::new(stdin.as_fd(), None);
276+
read_unbuffered_with_feedback(&mut reader, stdout, &hide_input)
215277
}
216-
} else {
217-
read_unbuffered(self.source())
278+
(Some(hide_input), Terminal::Tty(file)) => {
279+
let mut reader = TimeoutRead::new(file.as_fd(), None);
280+
read_unbuffered_with_feedback(&mut reader, &mut &*file, &hide_input)
281+
}
282+
(None, term) => read_unbuffered(&mut term.source_timeout(None)),
218283
}
219284
}
220285

@@ -242,6 +307,13 @@ impl Terminal<'_> {
242307
}
243308
}
244309

310+
fn source_timeout(&self, timeout: Option<Duration>) -> TimeoutRead {
311+
match self {
312+
Terminal::StdIE(stdin, _) => TimeoutRead::new(stdin.as_fd(), timeout),
313+
Terminal::Tty(file) => TimeoutRead::new(file.as_fd(), timeout),
314+
}
315+
}
316+
245317
fn sink(&mut self) -> &mut dyn io::Write {
246318
match self {
247319
Terminal::StdIE(_, x) => x,

src/system/time.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,15 @@ impl Sub<Duration> for Duration {
124124
}
125125
}
126126

127+
impl From<Duration> for std::time::Duration {
128+
fn from(dur: Duration) -> std::time::Duration {
129+
std::time::Duration::new(
130+
dur.secs.try_into().unwrap_or(0),
131+
dur.nsecs.try_into().unwrap_or(0),
132+
)
133+
}
134+
}
135+
127136
impl From<libc::timespec> for SystemTime {
128137
fn from(value: libc::timespec) -> Self {
129138
SystemTime::new(value.tv_sec as _, value.tv_nsec as _)

0 commit comments

Comments
 (0)