@@ -20,30 +20,23 @@ use std::{fs, mem};
2020
2121use libc:: { tcsetattr, termios, ECHO , ECHONL , ICANON , TCSANOW , VEOF , VERASE , VKILL } ;
2222
23- use crate :: cutils:: cerr;
23+ use crate :: cutils:: { cerr, safe_isatty } ;
2424use crate :: system:: time:: Duration ;
2525
2626use super :: securemem:: PamBuffer ;
2727
28- struct HiddenInput {
29- tty : fs :: File ,
28+ struct HiddenInput < ' a > {
29+ tty : BorrowedFd < ' a > ,
3030 term_orig : termios ,
3131}
3232
33- impl HiddenInput {
34- fn new ( ) -> io:: Result < Option < HiddenInput > > {
35- // control ourselves that we are really talking to a TTY
36- // mitigates: https://marc.info/?l=oss-security&m=168164424404224
37- let Ok ( tty) = fs:: File :: open ( "/dev/tty" ) else {
38- // if we have nothing to show, we have nothing to hide
39- return Ok ( None ) ;
40- } ;
41-
33+ impl HiddenInput < ' _ > {
34+ fn new ( tty : BorrowedFd ) -> io:: Result < HiddenInput > {
4235 // Make two copies of the terminal settings. The first one will be modified
4336 // and the second one will act as a backup for when we want to set the
4437 // terminal back to its original state.
45- let mut term = safe_tcgetattr ( & tty) ?;
46- let term_orig = safe_tcgetattr ( & tty) ?;
38+ let mut term = safe_tcgetattr ( tty) ?;
39+ let term_orig = safe_tcgetattr ( tty) ?;
4740
4841 // Hide the password. This is what makes this function useful.
4942 term. c_lflag &= !ECHO ;
@@ -58,11 +51,11 @@ impl HiddenInput {
5851 // SAFETY: we are passing tcsetattr a valid file descriptor and pointer-to-struct
5952 cerr ( unsafe { tcsetattr ( tty. as_raw_fd ( ) , TCSANOW , & term) } ) ?;
6053
61- Ok ( Some ( HiddenInput { tty, term_orig } ) )
54+ Ok ( HiddenInput { tty, term_orig } )
6255 }
6356}
6457
65- impl Drop for HiddenInput {
58+ impl Drop for HiddenInput < ' _ > {
6659 fn drop ( & mut self ) {
6760 // Set the the mode back to normal
6861 // SAFETY: we are passing tcsetattr a valid file descriptor and pointer-to-struct
@@ -89,17 +82,17 @@ fn erase_feedback(sink: &mut dyn io::Write, i: usize) {
8982 }
9083}
9184
92- enum Hidden < ' a > {
85+ pub ( super ) enum Hidden < T > {
9386 No ,
94- Yes ( & ' a HiddenInput ) ,
95- WithFeedback ( & ' a HiddenInput ) ,
87+ Yes ( T ) ,
88+ WithFeedback ( T ) ,
9689}
9790
9891/// Reads a password from the given file descriptor while optionally showing feedback to the user.
9992fn read_unbuffered (
10093 source : & mut dyn io:: Read ,
10194 sink : & mut dyn io:: Write ,
102- hide_input : Hidden < ' _ > ,
95+ hide_input : & Hidden < HiddenInput > ,
10396) -> io:: Result < PamBuffer > {
10497 struct ExitGuard < ' a > {
10598 pw_len : usize ,
@@ -259,6 +252,8 @@ pub enum Terminal<'a> {
259252impl Terminal < ' _ > {
260253 /// Open the current TTY for user communication
261254 pub fn open_tty ( ) -> io:: Result < Self > {
255+ // control ourselves that we are really talking to a TTY
256+ // mitigates: https://marc.info/?l=oss-security&m=168164424404224
262257 Ok ( Terminal :: Tty (
263258 fs:: OpenOptions :: new ( )
264259 . read ( true )
@@ -272,48 +267,41 @@ impl Terminal<'_> {
272267 Ok ( Terminal :: StdIE ( io:: stdin ( ) . lock ( ) , io:: stderr ( ) . lock ( ) ) )
273268 }
274269
275- /// Reads input with TTY echo disabled
276- pub fn read_password ( & mut self , timeout : Option < Duration > ) -> io:: Result < PamBuffer > {
277- let hide_input = HiddenInput :: new ( ) ?;
278- self . read_inner (
279- timeout,
280- hide_input. as_ref ( ) . map ( Hidden :: Yes ) . unwrap_or ( Hidden :: No ) ,
281- )
282- }
283-
284- /// Reads input with TTY echo disabled, but do provide visual feedback while typing.
285- pub fn read_password_with_feedback (
270+ /// Reads input with TTY echo and visual feedback set according to the `hidden` parameter.
271+ pub ( super ) fn read_input (
286272 & mut self ,
273+ prompt : & str ,
287274 timeout : Option < Duration > ,
275+ hidden : Hidden < ( ) > ,
288276 ) -> io:: Result < PamBuffer > {
289- let hide_input = HiddenInput :: new ( ) ?;
290- self . read_inner (
291- timeout,
292- hide_input
293- . as_ref ( )
294- . map ( Hidden :: WithFeedback )
295- . unwrap_or ( Hidden :: No ) ,
296- )
297- }
298-
299- /// Reads input with TTY echo enabled
300- pub fn read_cleartext ( & mut self ) -> io:: Result < PamBuffer > {
301- self . read_inner ( None , Hidden :: No )
302- }
277+ fn do_hide_input (
278+ hidden : Hidden < ( ) > ,
279+ input : BorrowedFd ,
280+ ) -> Result < Hidden < HiddenInput > , io:: Error > {
281+ Ok ( match hidden {
282+ // If input is not a tty, we can't hide feedback.
283+ _ if !safe_isatty ( input) => Hidden :: No ,
284+
285+ Hidden :: No => Hidden :: No ,
286+ Hidden :: Yes ( ( ) ) => Hidden :: Yes ( HiddenInput :: new ( input) ?) ,
287+ Hidden :: WithFeedback ( ( ) ) => Hidden :: WithFeedback ( HiddenInput :: new ( input) ?) ,
288+ } )
289+ }
303290
304- fn read_inner (
305- & mut self ,
306- timeout : Option < Duration > ,
307- hide_input : Hidden < ' _ > ,
308- ) -> io:: Result < PamBuffer > {
309291 match self {
310292 Terminal :: StdIE ( stdin, stdout) => {
293+ write_unbuffered ( stdout, prompt. as_bytes ( ) ) ?;
294+
295+ let hide_input = do_hide_input ( hidden, stdin. as_fd ( ) ) ?;
311296 let mut reader = TimeoutRead :: new ( stdin. as_fd ( ) , timeout) ;
312- read_unbuffered ( & mut reader, stdout, hide_input)
297+ read_unbuffered ( & mut reader, stdout, & hide_input)
313298 }
314299 Terminal :: Tty ( file) => {
300+ write_unbuffered ( file, prompt. as_bytes ( ) ) ?;
301+
302+ let hide_input = do_hide_input ( hidden, file. as_fd ( ) ) ?;
315303 let mut reader = TimeoutRead :: new ( file. as_fd ( ) , timeout) ;
316- read_unbuffered ( & mut reader, & mut & * file, hide_input)
304+ read_unbuffered ( & mut reader, & mut & * file, & hide_input)
317305 }
318306 }
319307 }
@@ -346,7 +334,7 @@ mod test {
346334 fn miri_test_read ( ) {
347335 let mut data = "password123\n hello world" . as_bytes ( ) ;
348336 let mut stdout = Vec :: new ( ) ;
349- let buf = read_unbuffered ( & mut data, & mut stdout, Hidden :: No ) . unwrap ( ) ;
337+ let buf = read_unbuffered ( & mut data, & mut stdout, & Hidden :: No ) . unwrap ( ) ;
350338 // check that the \n is not part of input
351339 assert_eq ! (
352340 buf. iter( )
@@ -362,8 +350,10 @@ mod test {
362350 #[ test]
363351 fn miri_test_longpwd ( ) {
364352 let mut stdout = Vec :: new ( ) ;
365- assert ! ( read_unbuffered( & mut "a" . repeat( 511 ) . as_bytes( ) , & mut stdout, Hidden :: No ) . is_ok( ) ) ;
366- assert ! ( read_unbuffered( & mut "a" . repeat( 512 ) . as_bytes( ) , & mut stdout, Hidden :: No ) . is_err( ) ) ;
353+ assert ! ( read_unbuffered( & mut "a" . repeat( 511 ) . as_bytes( ) , & mut stdout, & Hidden :: No ) . is_ok( ) ) ;
354+ assert ! (
355+ read_unbuffered( & mut "a" . repeat( 512 ) . as_bytes( ) , & mut stdout, & Hidden :: No ) . is_err( )
356+ ) ;
367357 }
368358
369359 #[ test]
0 commit comments