]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
ask-password-api: many fixes to ask_password_tty()
authorLennart Poettering <lennart@poettering.net>
Wed, 14 Feb 2018 17:41:37 +0000 (18:41 +0100)
committerLennart Poettering <lennart@poettering.net>
Wed, 14 Feb 2018 17:47:20 +0000 (18:47 +0100)
A couple of fixes:

1. always bzero_explicit() away what we remove from the passphrase
   buffer. The UTF-8 code assumes the string remains NUL-terminated, and
   we hence should enforce that. memzero() would do too here, but let's
   be paranoid after all this is key material.

2. when clearing '*' characters from string, do so counting UTF-8
   codepoints properly. We already have code in place to count UTF-8
   codepoints when generating '*' characters, hence we should take the
   same care when clearing them again.

3. Treat NUL on input as an alternative terminator to newline or EOF.

4. When removing characters from the password always also reset the
   "codepoint" index properly.

src/shared/ask-password-api.c

index 845fd9dc08e814f672ff9e5f323bbc98337eb56f..a0ee05e2d8a871de05a42f46c29d0b0438b0c5e1 100644 (file)
@@ -201,6 +201,27 @@ static void backspace_chars(int ttyfd, size_t p) {
         }
 }
 
+static void backspace_string(int ttyfd, const char *str) {
+        size_t m;
+
+        assert(str);
+
+        if (ttyfd < 0)
+                return;
+
+        /* Backspaces back for enough characters to entirely undo printing of the specified string. */
+
+        m = utf8_n_codepoints(str);
+        if (m == (size_t) -1)
+                m = strlen(str); /* Not a valid UTF-8 string? If so, let's backspace the number of bytes output. Most
+                                  * likely this happened because we are not in an UTF-8 locale, and in that case that
+                                  * is the correct thing to do. And even if it's not, terminals tend to stop
+                                  * backspacing at the leftmost column, hence backspacing too much should be mostly
+                                  * OK. */
+
+        backspace_chars(ttyfd, m);
+}
+
 int ask_password_tty(
                 int ttyfd,
                 const char *message,
@@ -286,9 +307,9 @@ int ask_password_tty(
         };
 
         for (;;) {
-                char c;
                 int sleep_for = -1, k;
                 ssize_t n;
+                char c;
 
                 if (until > 0) {
                         usec_t y;
@@ -335,33 +356,56 @@ int ask_password_tty(
                         r = -errno;
                         goto finish;
 
-                } else if (n == 0)
-                        break;
+                }
 
-                if (c == '\n')
+                /* We treat EOF, newline and NUL byte all as valid end markers */
+                if (n == 0 || c == '\n' || c == 0)
                         break;
-                else if (c == 21) { /* C-u */
+
+                if (c == 21) { /* C-u */
 
                         if (!(flags & ASK_PASSWORD_SILENT))
-                                backspace_chars(ttyfd, p);
-                        p = 0;
+                                backspace_string(ttyfd, passphrase);
+
+                        explicit_bzero(passphrase, sizeof(passphrase));
+                        p = codepoint = 0;
 
                 } else if (IN_SET(c, '\b', 127)) {
 
                         if (p > 0) {
+                                size_t q;
 
                                 if (!(flags & ASK_PASSWORD_SILENT))
                                         backspace_chars(ttyfd, 1);
 
-                                p--;
+                                /* Remove a full UTF-8 codepoint from the end. For that, figure out where the last one
+                                 * begins */
+                                q = 0;
+                                for (;;) {
+                                        size_t z;
+
+                                        z = utf8_encoded_valid_unichar(passphrase + q);
+                                        if (z == 0) {
+                                                q = (size_t) -1; /* Invalid UTF8! */
+                                                break;
+                                        }
+
+                                        if (q + z >= p) /* This one brings us over the edge */
+                                                break;
+
+                                        q += z;
+                                }
+
+                                p = codepoint = q == (size_t) -1 ? p - 1 : q;
+                                explicit_bzero(passphrase + p, sizeof(passphrase) - p);
+
                         } else if (!dirty && !(flags & ASK_PASSWORD_SILENT)) {
 
                                 flags |= ASK_PASSWORD_SILENT;
 
-                                /* There are two ways to enter silent
-                                 * mode. Either by pressing backspace
-                                 * as first key (and only as first
-                                 * key), or ... */
+                                /* There are two ways to enter silent mode. Either by pressing backspace as first key
+                                 * (and only as first key), or ... */
+
                                 if (ttyfd >= 0)
                                         (void) loop_write(ttyfd, "(no echo) ", 10, false);
 
@@ -370,23 +414,25 @@ int ask_password_tty(
 
                 } else if (c == '\t' && !(flags & ASK_PASSWORD_SILENT)) {
 
-                        backspace_chars(ttyfd, p);
+                        backspace_string(ttyfd, passphrase);
                         flags |= ASK_PASSWORD_SILENT;
 
                         /* ... or by pressing TAB at any time. */
 
                         if (ttyfd >= 0)
                                 (void) loop_write(ttyfd, "(no echo) ", 10, false);
-                } else {
-                        if (p >= sizeof(passphrase)-1) {
-                                if (ttyfd >= 0)
-                                        (void) loop_write(ttyfd, "\a", 1, false);
-                                continue;
-                        }
 
+                } else if (p >= sizeof(passphrase)-1) {
+
+                        /* Reached the size limit */
+                        if (ttyfd >= 0)
+                                (void) loop_write(ttyfd, "\a", 1, false);
+
+                } else {
                         passphrase[p++] = c;
 
                         if (!(flags & ASK_PASSWORD_SILENT) && ttyfd >= 0) {
+                                /* Check if we got a complete UTF-8 character now. If so, let's output one '*'. */
                                 n = utf8_encoded_valid_unichar(passphrase + codepoint);
                                 if (n >= 0) {
                                         codepoint = p;
@@ -397,11 +443,12 @@ int ask_password_tty(
                         dirty = true;
                 }
 
+                /* Let's forget this char, just to not keep needlessly copies of key material around */
                 c = 'x';
         }
 
         x = strndup(passphrase, p);
-        explicit_bzero(passphrase, p);
+        explicit_bzero(passphrase, sizeof(passphrase));
         if (!x) {
                 r = -ENOMEM;
                 goto finish;