Skip to content

Commit 9080a37

Browse files
committed
tgetpass: Use cbreak mode if possible even if pwfeedback is disabled
This fixes an issue where if sudo is killed by SIGKILL while there is pending input, it could be echoed after sudo exits. By using cbreak mode by default the password is read one character at a time rather than by line. In sudo-rs this is CVE-2025-64170.
1 parent b946acf commit 9080a37

1 file changed

Lines changed: 33 additions & 23 deletions

File tree

src/tgetpass.c

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
* SPDX-License-Identifier: ISC
33
*
4-
* Copyright (c) 1996, 1998-2005, 2007-2021
4+
* Copyright (c) 1996, 1998-2005, 2007-2025
55
* Todd C. Miller <[email protected]>
66
*
77
* Permission to use, copy, modify, and distribute this software for any
@@ -49,7 +49,7 @@ enum tgetpass_errval {
4949
static volatile sig_atomic_t signo[NSIG];
5050

5151
static void tgetpass_handler(int);
52-
static char *getln(int, char *, size_t, bool, enum tgetpass_errval *);
52+
static char *getln(int, char *, size_t, bool, bool, enum tgetpass_errval *);
5353
static char *sudo_askpass(const char *, const char *);
5454

5555
static int
@@ -112,7 +112,7 @@ tgetpass(const char *prompt, int timeout, unsigned int flags,
112112
static const char *askpass;
113113
static char buf[SUDO_CONV_REPL_MAX + 1];
114114
int i, input, output, save_errno, ttyfd;
115-
bool feedback, need_restart, neednl;
115+
bool cbreak, feedback, need_restart;
116116
enum tgetpass_errval errval;
117117
debug_decl(tgetpass, SUDO_DEBUG_CONV);
118118

@@ -158,7 +158,7 @@ tgetpass(const char *prompt, int timeout, unsigned int flags,
158158
signo[i] = 0;
159159
pass = NULL;
160160
save_errno = 0;
161-
neednl = false;
161+
cbreak = false;
162162
need_restart = false;
163163
feedback = false;
164164

@@ -173,17 +173,21 @@ tgetpass(const char *prompt, int timeout, unsigned int flags,
173173
output = ttyfd;
174174
}
175175

176-
/*
177-
* If we are using a tty but are not the foreground pgrp this will
178-
* return EINTR. We send ourself SIGTTOU bracketed by callbacks.
179-
*/
180176
if (!ISSET(flags, TGP_ECHO)) {
177+
/*
178+
* Instead of just disabling echo, we enable "cbreak" mode.
179+
* This lets us read one character at a time, which is also
180+
* necessary when masking input (for the pwfeedback option).
181+
*/
181182
for (;;) {
183+
/*
184+
* If we are using a tty but are not the foreground pgrp this will
185+
* return EINTR. We send ourself SIGTTOU bracketed by callbacks.
186+
*/
187+
cbreak = sudo_term_cbreak(input, true);
182188
if (ISSET(flags, TGP_MASK))
183-
neednl = feedback = sudo_term_cbreak(input, true);
184-
else
185-
neednl = sudo_term_noecho(input);
186-
if (neednl || errno != EINTR)
189+
feedback = cbreak;
190+
if (cbreak || errno != EINTR)
187191
break;
188192
/* Received SIGTTOU, suspend the process. */
189193
if (suspend(SIGTTOU, callback) == -1) {
@@ -216,18 +220,19 @@ tgetpass(const char *prompt, int timeout, unsigned int flags,
216220
if (write(output, "\a", 1) != 1)
217221
goto restore;
218222
}
219-
if (prompt) {
223+
if (prompt != NULL) {
220224
if (write(output, prompt, strlen(prompt)) < 0)
221225
goto restore;
222226
}
223227

224228
if (timeout > 0)
225229
alarm((unsigned int)timeout);
226-
pass = getln(input, buf, sizeof(buf), feedback, &errval);
230+
pass = getln(input, buf, sizeof(buf), cbreak, feedback, &errval);
227231
alarm(0);
228232
save_errno = errno;
229233

230-
if (neednl || pass == NULL) {
234+
if (cbreak || pass == NULL) {
235+
/* No newline was displayed. */
231236
if (write(output, "\n", 1) != 1)
232237
goto restore;
233238
}
@@ -345,7 +350,7 @@ sudo_askpass(const char *askpass, const char *prompt)
345350

346351
/* Get response from child (askpass). */
347352
(void) close(pfd[1]);
348-
pass = getln(pfd[0], buf, sizeof(buf), 0, &errval);
353+
pass = getln(pfd[0], buf, sizeof(buf), false, false, &errval);
349354
(void) close(pfd[0]);
350355

351356
tgetpass_display_error(errval);
@@ -392,7 +397,7 @@ last_chunk_len(const char *buf, ssize_t len)
392397
}
393398

394399
static char *
395-
getln(int fd, char *buf, size_t bufsiz, bool feedback,
400+
getln(int fd, char *buf, size_t bufsiz, bool cbreak, bool feedback,
396401
enum tgetpass_errval *errval)
397402
{
398403
ssize_t nr = -1;
@@ -414,28 +419,33 @@ getln(int fd, char *buf, size_t bufsiz, bool feedback,
414419
nr = read(fd, &c, 1);
415420
if (nr != 1 || c == '\n' || c == '\r')
416421
break;
417-
if (feedback) {
422+
if (cbreak) {
418423
if (c == sudo_term_eof) {
419424
nr = 0;
420425
break;
421426
} else if (c == sudo_term_kill) {
422427
while (cp > buf) {
423-
if (write(fd, "\b \b", 3) != 3)
424-
break;
428+
if (feedback) {
429+
if (write(fd, "\b \b", 3) != 3)
430+
break;
431+
}
425432
cp -= last_chunk_len(buf, cp - buf);
426433
}
427434
cp = buf;
428435
continue;
429436
} else if (c == sudo_term_erase) {
430437
if (cp > buf) {
431-
ignore_result(write(fd, "\b \b", 3));
438+
if (feedback)
439+
ignore_result(write(fd, "\b \b", 3));
432440
cp -= last_chunk_len(buf, cp - buf);
433441
}
434442
continue;
435443
}
436444
*cp++ = c;
437-
if (last_chunk_len(buf, cp - buf) == 1)
438-
ignore_result(write(fd, "*", 1));
445+
if (feedback) {
446+
if (last_chunk_len(buf, cp - buf) == 1)
447+
ignore_result(write(fd, "*", 1));
448+
}
439449
} else {
440450
*cp++ = c;
441451
}

0 commit comments

Comments
 (0)