Richard W.M. Jones
2020-Jun-01 10:31 UTC
[Libguestfs] server: Fix reading passwords interactively.
https://bugzilla.redhat.com/show_bug.cgi?id=1842440 Patches 1 and 2 address fairly obvious bugs in how we handle reading passwords from stdin. There are other ways we may consider fixing these bugs: - Should password=- always open /dev/tty and ignore stdin entirely? - Should we make password=-0/-1/-2 work by skipping the close? Or perhaps reopen the file descriptors on /dev/null after reading the password? (This seems over-complicated to me, I think -0/-1/-2 is always likely to be a user error). Rich.
Richard W.M. Jones
2020-Jun-01 10:31 UTC
[Libguestfs] [PATCH nbdkit 1/3] server: Disallow password=- from non-tty and fix error message (RHBZ#1842440).
This command fails with an incorrect error message:
$ nbdkit ssh host=localhost /nosuchfile password=- --run 'qemu-img info
$nbd' </dev/null
password:
nbdkit: error: could not read password from stdin: Inappropriate ioctl for
device
The error (ENOTTY Inappropriate ioctl for device) is actually a
leftover errno from the previous isatty call. This happens because
getline(3) can return -1 both for error and EOF. In the EOF case it
does not set errno so we get the previous random value.
Also:
$ echo -n '' | nbdkit ssh host=localhost /nosuchfile password=- --run
'qemu-img info $nbd'
password:
nbdkit: error: could not read password from stdin: Inappropriate ioctl for
device
$ echo -n '1' | nbdkit ssh host=localhost /nosuchfile password=- --run
'qemu-img info $nbd'
password:
[password is read with no error]
This raises the question of what password=- actually means. It's
documented as "read a password interactively", with the word
"interactively" going back to at least nbdkit 1.2, and therefore I
think we should reject attempts to use password=- from non-ttys.
Since at least nbdkit 1.2 we have allowed passwords to be read from
files (password=+FILENAME), and since nbdkit 1.16 you can read
passwords from arbitrary file descriptors (password=-FD).
Another justification for the interactive-only nature of password=- is
that it prints a ?password: ? prompt.
So I believe it is fair to ban password=- unless the input is a tty.
This commit also fixes the error message by handling the case where
getline returns -1 without setting errno.
After this change:
$ ./nbdkit ssh host=localhost /nosuchfile password=- --run 'qemu-img info
$nbd'
password: <--- press ^D
nbdkit: error: could not read password from stdin: end of file
$ echo -n '' | ./nbdkit ssh host=localhost /nosuchfile password=-
--run 'qemu-img info $nbd'
nbdkit: error: stdin is not a tty, cannot read password interactively
$ echo -n '1' | ./nbdkit ssh host=localhost /nosuchfile password=-
--run 'qemu-img info $nbd'
nbdkit: error: stdin is not a tty, cannot read password interactively
$ ./nbdkit ssh host=localhost /nosuchfile password=- --run 'qemu-img info
$nbd'
password: <--- press return key
[zero length password is read]
Thanks: Ming Xie, Pino Toscano.
---
docs/nbdkit-plugin.pod | 8 ++-
server/public.c | 107 ++++++++++++++++++++++++++---------------
2 files changed, 74 insertions(+), 41 deletions(-)
diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod
index f5e6dd01..612688ab 100644
--- a/docs/nbdkit-plugin.pod
+++ b/docs/nbdkit-plugin.pod
@@ -1242,8 +1242,12 @@ or from a file descriptor inherited by nbdkit:
nbdkit myplugin password=-99
-(If the password begins with a C<-> or C<+> character then it must
be
-passed in a file).
+=head3 Notes on reading passwords
+
+If the password begins with a C<-> or C<+> character then it must
be
+passed in a file.
+
+C<password=-> can only be used when stdin is a terminal.
=head2 Safely interacting with stdin and stdout
diff --git a/server/public.c b/server/public.c
index bcf1a3a2..dafdfbae 100644
--- a/server/public.c
+++ b/server/public.c
@@ -413,53 +413,18 @@ nbdkit_stdio_safe (void)
}
/* Read a password from configuration value. */
+static int read_password_interactive (char **password);
static int read_password_from_fd (const char *what, int fd, char **password);
int
nbdkit_read_password (const char *value, char **password)
{
- int tty, err;
- struct termios orig, temp;
- ssize_t r;
- size_t n;
-
*password = NULL;
- /* Read from stdin. */
+ /* Read from stdin interactively. */
if (strcmp (value, "-") == 0) {
- if (!nbdkit_stdio_safe ()) {
- nbdkit_error ("stdin is not available for reading password");
+ if (read_password_interactive (password) == -1)
return -1;
- }
-
- printf ("password: ");
-
- /* Set no echo. */
- tty = isatty (0);
- if (tty) {
- tcgetattr (0, &orig);
- temp = orig;
- temp.c_lflag &= ~ECHO;
- tcsetattr (0, TCSAFLUSH, &temp);
- }
-
- r = getline (password, &n, stdin);
- err = errno;
-
- /* Restore echo. */
- if (tty)
- tcsetattr (0, TCSAFLUSH, &orig);
-
- /* Complete the printf above. */
- printf ("\n");
-
- if (r == -1) {
- errno = err;
- nbdkit_error ("could not read password from stdin: %m");
- return -1;
- }
- if (*password && r > 0 && (*password)[r-1] ==
'\n')
- (*password)[r-1] = '\0';
}
/* Read from numbered file descriptor. */
@@ -501,6 +466,62 @@ nbdkit_read_password (const char *value, char **password)
return 0;
}
+static int
+read_password_interactive (char **password)
+{
+ int err;
+ struct termios orig, temp;
+ ssize_t r;
+ size_t n;
+
+ if (!nbdkit_stdio_safe ()) {
+ nbdkit_error ("stdin is not available for reading password");
+ return -1;
+ }
+
+ if (!isatty (0)) {
+ nbdkit_error ("stdin is not a tty, cannot read password
interactively");
+ return -1;
+ }
+
+ printf ("password: ");
+
+ /* Set no echo. This is best-effort, ignore errors. */
+ tcgetattr (0, &orig);
+ temp = orig;
+ temp.c_lflag &= ~ECHO;
+ tcsetattr (0, TCSAFLUSH, &temp);
+
+ /* To distinguish between error and EOF we have to check errno.
+ * getline can return -1 and errno = 0 which means we got end of
+ * file.
+ */
+ errno = 0;
+ r = getline (password, &n, stdin);
+ err = errno;
+
+ /* Restore echo. */
+ tcsetattr (0, TCSAFLUSH, &orig);
+
+ /* Complete the printf above. */
+ printf ("\n");
+
+ if (r == -1) {
+ if (err == 0)
+ nbdkit_error ("could not read password from stdin: end of
file");
+ else {
+ errno = err;
+ nbdkit_error ("could not read password from stdin: %m");
+ }
+ return -1;
+ }
+
+ if (*password && r > 0 && (*password)[r-1] ==
'\n')
+ (*password)[r-1] = '\0';
+
+ return 0;
+}
+
static int
read_password_from_fd (const char *what, int fd, char **password)
{
@@ -515,10 +536,18 @@ read_password_from_fd (const char *what, int fd, char
**password)
close (fd);
return -1;
}
+
+ /* To distinguish between error and EOF we have to check errno.
+ * getline can return -1 and errno = 0 which means we got end of
+ * file, which is simply a zero length password.
+ */
+ errno = 0;
r = getline (password, &n, fp);
err = errno;
+
fclose (fp);
- if (r == -1) {
+
+ if (r == -1 && err != 0) {
errno = err;
nbdkit_error ("could not read password from %s: %m", what);
return -1;
--
2.25.0
Richard W.M. Jones
2020-Jun-01 10:31 UTC
[Libguestfs] [PATCH nbdkit 2/3] server: Disallow -FD for stdin/stdout/stderr.
$ ./nbdkit ssh host=localhost /nosuchfile password=-0 --run 'qemu-img info
$nbd'
abc
fcntl: Bad file descriptor
The reason for this is that we close the file descriptor after reading
the password. Closing stdin causes bad stuff to happen.
---
docs/nbdkit-plugin.pod | 5 +++++
server/public.c | 4 ++--
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod
index 612688ab..7b8a5393 100644
--- a/docs/nbdkit-plugin.pod
+++ b/docs/nbdkit-plugin.pod
@@ -1249,6 +1249,11 @@ passed in a file.
C<password=-> can only be used when stdin is a terminal.
+C<password=-FD> cannot be used with stdin, stdout or stderr
+(ie. C<-0>, C<-1> or C<-2>). The reason is that after
reading the
+password the file descriptor is closed, which causes bad stuff to
+happen.
+
=head2 Safely interacting with stdin and stdout
int nbdkit_stdio_safe (void);
diff --git a/server/public.c b/server/public.c
index dafdfbae..2e36e43a 100644
--- a/server/public.c
+++ b/server/public.c
@@ -433,8 +433,8 @@ nbdkit_read_password (const char *value, char **password)
if (nbdkit_parse_int ("password file descriptor", &value[1],
&fd) == -1)
return -1;
- if (fd == STDIN_FILENO && !nbdkit_stdio_safe ()) {
- nbdkit_error ("stdin is not available for reading password");
+ if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) {
+ nbdkit_error ("cannot use password -FD for
stdin/stdout/stderr");
return -1;
}
if (read_password_from_fd (&value[1], fd, password) == -1)
--
2.25.0
Richard W.M. Jones
2020-Jun-01 10:31 UTC
[Libguestfs] [PATCH nbdkit 3/3] todo: Suggest password=-tty as future enhancement.
password=+/dev/tty works already, but it does not print a ?password: ?
prompt or suppress echo.
---
TODO | 3 +++
1 file changed, 3 insertions(+)
diff --git a/TODO b/TODO
index 02627069..c1a6ec0a 100644
--- a/TODO
+++ b/TODO
@@ -103,6 +103,9 @@ General ideas for improvements
realloc is suspect)
- add more iterators, map function, etc, as required.
+* Add password=-tty to mean read a password interactively from /dev/tty.
+ This is slightly different from password=+/dev/tty
+
Suggestions for plugins
-----------------------
--
2.25.0
Richard W.M. Jones
2020-Jun-01 13:09 UTC
[Libguestfs] server: Fix reading passwords interactively.
I had an idea that I would make an alternate version of this patch where password=- always reads from /dev/tty. But looking at the source code of sudo that seems really complicated to do across all platforms (and even if we only made it work on Linux). Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 100 libraries supported. http://fedoraproject.org/wiki/MinGW
Eric Blake
2020-Jun-01 17:18 UTC
Re: [Libguestfs] [PATCH nbdkit 1/3] server: Disallow password=- from non-tty and fix error message (RHBZ#1842440).
On 6/1/20 5:31 AM, Richard W.M. Jones wrote:> This command fails with an incorrect error message: > > $ nbdkit ssh host=localhost /nosuchfile password=- --run 'qemu-img info $nbd' </dev/null > password: > nbdkit: error: could not read password from stdin: Inappropriate ioctl for device > > The error (ENOTTY Inappropriate ioctl for device) is actually a > leftover errno from the previous isatty call. This happens because > getline(3) can return -1 both for error and EOF. In the EOF case it > does not set errno so we get the previous random value. > > Also: > > $ echo -n '' | nbdkit ssh host=localhost /nosuchfile password=- --run 'qemu-img info $nbd' > password: > nbdkit: error: could not read password from stdin: Inappropriate ioctl for device > > $ echo -n '1' | nbdkit ssh host=localhost /nosuchfile password=- --run 'qemu-img info $nbd' > password: > [password is read with no error] > > This raises the question of what password=- actually means. It's > documented as "read a password interactively", with the word > "interactively" going back to at least nbdkit 1.2, and therefore I > think we should reject attempts to use password=- from non-ttys.Makes sense.> Since at least nbdkit 1.2 we have allowed passwords to be read from > files (password=+FILENAME), and since nbdkit 1.16 you can read > passwords from arbitrary file descriptors (password=-FD). > > Another justification for the interactive-only nature of password=- is > that it prints a “password: ” prompt. > > So I believe it is fair to ban password=- unless the input is a tty.I agree with that decision.> > This commit also fixes the error message by handling the case where > getline returns -1 without setting errno. > > After this change: > > $ ./nbdkit ssh host=localhost /nosuchfile password=- --run 'qemu-img info $nbd' > password: <--- press ^D > nbdkit: error: could not read password from stdin: end of file > $ echo -n '' | ./nbdkit ssh host=localhost /nosuchfile password=- --run 'qemu-img info $nbd' > nbdkit: error: stdin is not a tty, cannot read password interactively > $ echo -n '1' | ./nbdkit ssh host=localhost /nosuchfile password=- --run 'qemu-img info $nbd' > nbdkit: error: stdin is not a tty, cannot read password interactively > $ ./nbdkit ssh host=localhost /nosuchfile password=- --run 'qemu-img info $nbd' > password: <--- press return key > [zero length password is read] > > Thanks: Ming Xie, Pino Toscano. > --- > docs/nbdkit-plugin.pod | 8 ++- > server/public.c | 107 ++++++++++++++++++++++++++--------------- > 2 files changed, 74 insertions(+), 41 deletions(-) >> +static int > +read_password_interactive (char **password) > +{ > + int err; > + struct termios orig, temp; > + ssize_t r; > + size_t n; > + > + if (!nbdkit_stdio_safe ()) { > + nbdkit_error ("stdin is not available for reading password"); > + return -1; > + } > + > + if (!isatty (0)) {Could spell it STDIN_FILENO if desired, but that's trivial.> + /* To distinguish between error and EOF we have to check errno. > + * getline can return -1 and errno = 0 which means we got end of > + * file. > + */ > + errno = 0; > + r = getline (password, &n, stdin); > + err = errno; > + > + /* Restore echo. */ > + tcsetattr (0, TCSAFLUSH, &orig); > + > + /* Complete the printf above. */ > + printf ("\n"); > + > + if (r == -1) { > + if (err == 0) > + nbdkit_error ("could not read password from stdin: end of file");Is this actually an error? EOF on a tty merely means that the password is the empty string (perhaps because the user intentionally pressed ^D instead of enter to stop further input)...> + else { > + errno = err; > + nbdkit_error ("could not read password from stdin: %m"); > + } > + return -1; > + } > + > + if (*password && r > 0 && (*password)[r-1] == '\n') > + (*password)[r-1] = '\0';...especially since you already allow an interactive user to pass an empty password by pressing solely enter. The rest of the patch looks fine; I'll leave it up to you what you want to do about handling an empty password interactively. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org
Eric Blake
2020-Jun-01 17:20 UTC
Re: [Libguestfs] [PATCH nbdkit 2/3] server: Disallow -FD for stdin/stdout/stderr.
On 6/1/20 5:31 AM, Richard W.M. Jones wrote:> $ ./nbdkit ssh host=localhost /nosuchfile password=-0 --run 'qemu-img info $nbd' > abc > fcntl: Bad file descriptor > > The reason for this is that we close the file descriptor after reading > the password. Closing stdin causes bad stuff to happen. > --- > docs/nbdkit-plugin.pod | 5 +++++ > server/public.c | 4 ++-- > 2 files changed, 7 insertions(+), 2 deletions(-) >Makes sense.> diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod > index 612688ab..7b8a5393 100644 > --- a/docs/nbdkit-plugin.pod > +++ b/docs/nbdkit-plugin.pod > @@ -1249,6 +1249,11 @@ passed in a file. > > C<password=-> can only be used when stdin is a terminal. > > +C<password=-FD> cannot be used with stdin, stdout or stderr > +(ie. C<-0>, C<-1> or C<-2>). The reason is that after reading the > +password the file descriptor is closed, which causes bad stuff to > +happen.Sure, we could always skip closing on those three, but it's just as easy to not permit it in the first place :)> + > =head2 Safely interacting with stdin and stdout > > int nbdkit_stdio_safe (void); > diff --git a/server/public.c b/server/public.c > index dafdfbae..2e36e43a 100644 > --- a/server/public.c > +++ b/server/public.c > @@ -433,8 +433,8 @@ nbdkit_read_password (const char *value, char **password) > > if (nbdkit_parse_int ("password file descriptor", &value[1], &fd) == -1) > return -1; > - if (fd == STDIN_FILENO && !nbdkit_stdio_safe ()) { > - nbdkit_error ("stdin is not available for reading password"); > + if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) {Could shorten to if (fd <= STDERR_FILENO) if desired, but I'm fine either way. ACK. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org
Eric Blake
2020-Jun-01 17:20 UTC
Re: [Libguestfs] [PATCH nbdkit 3/3] todo: Suggest password=-tty as future enhancement.
On 6/1/20 5:31 AM, Richard W.M. Jones wrote:> password=+/dev/tty works already, but it does not print a “password: ” > prompt or suppress echo. > --- > TODO | 3 +++ > 1 file changed, 3 insertions(+)ACK.> > diff --git a/TODO b/TODO > index 02627069..c1a6ec0a 100644 > --- a/TODO > +++ b/TODO > @@ -103,6 +103,9 @@ General ideas for improvements > realloc is suspect) > - add more iterators, map function, etc, as required. > > +* Add password=-tty to mean read a password interactively from /dev/tty. > + This is slightly different from password=+/dev/tty > + > Suggestions for plugins > ----------------------- > >-- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3226 Virtualization: qemu.org | libvirt.org
Seemingly Similar Threads
- [PATCH nbdkit 1/3] server: Disallow password=- from non-tty and fix error message (RHBZ#1842440).
- [nbdkit PATCH v2 0/3] more consistent stdin/out handling
- [nbdkit PATCH 0/2] stdin/out cleanups
- [PATCH nbdkit 5/9 patch split 1/5] Create libnbdkit.so.
- Re: [PATCH nbdkit 1/3] server: Disallow password=- from non-tty and fix error message (RHBZ#1842440).