]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/shared/ask-password-api.c
ask-password: prevent buffer overrow when reading from keyring
[thirdparty/systemd.git] / src / shared / ask-password-api.c
index 681fd0d618c913c2632f1a9551a0e3afb3dbb488..6c0a36990291832e7c2c36953515c5ecb91448c2 100644 (file)
 #include "fd-util.h"
 #include "fileio.h"
 #include "format-util.h"
+#include "fs-util.h"
 #include "io-util.h"
 #include "log.h"
 #include "macro.h"
+#include "memory-util.h"
 #include "missing.h"
 #include "mkdir.h"
 #include "process-util.h"
@@ -40,9 +42,9 @@
 #include "strv.h"
 #include "terminal-util.h"
 #include "time-util.h"
+#include "tmpfile-util.h"
 #include "umask-util.h"
 #include "utf8.h"
-#include "util.h"
 
 #define KEYRING_TIMEOUT_USEC ((5 * USEC_PER_MINUTE) / 2)
 
@@ -75,20 +77,25 @@ static int retrieve_key(key_serial_t serial, char ***ret) {
                 n = keyctl(KEYCTL_READ, (unsigned long) serial, (unsigned long) p, (unsigned long) m, 0);
                 if (n < 0)
                         return -errno;
-
                 if (n < m)
                         break;
 
-                explicit_bzero(p, n);
-                free(p);
+                explicit_bzero_safe(p, m);
+
+                if (m > LONG_MAX / 2) /* overflow check */
+                        return -ENOMEM;
                 m *= 2;
+                if ((long) (size_t) m != m) /* make sure that this still fits if converted to size_t */
+                        return -ENOMEM;
+
+                free(p);
         }
 
         l = strv_parse_nulstr(p, n);
         if (!l)
                 return -ENOMEM;
 
-        explicit_bzero(p, n);
+        explicit_bzero_safe(p, n);
 
         *ret = l;
         return 0;
@@ -124,7 +131,7 @@ static int add_to_keyring(const char *keyname, AskPasswordFlags flags, char **pa
                 return r;
 
         serial = add_key("user", keyname, p, n, KEY_SPEC_USER_KEYRING);
-        explicit_bzero(p, n);
+        explicit_bzero_safe(p, n);
         if (serial == -1)
                 return -errno;
 
@@ -133,6 +140,9 @@ static int add_to_keyring(const char *keyname, AskPasswordFlags flags, char **pa
                    (unsigned long) DIV_ROUND_UP(KEYRING_TIMEOUT_USEC, USEC_PER_SEC), 0, 0) < 0)
                 log_debug_errno(errno, "Failed to adjust timeout: %m");
 
+        /* Tell everyone to check the keyring */
+        (void) touch("/run/systemd/ask-password");
+
         log_debug("Added key to keyring as %" PRIi32 ".", serial);
 
         return 1;
@@ -151,7 +161,7 @@ static int add_to_keyring_and_log(const char *keyname, AskPasswordFlags flags, c
         return 0;
 }
 
-int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret) {
+static int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret) {
 
         key_serial_t serial;
         int r;
@@ -211,7 +221,7 @@ int ask_password_tty(
                 usec_t until,
                 AskPasswordFlags flags,
                 const char *flag_file,
-                char **ret) {
+                char ***ret) {
 
         enum {
                 POLL_TTY,
@@ -223,6 +233,7 @@ int ask_password_tty(
         _cleanup_close_ int cttyfd = -1, notify = -1;
         struct termios old_termios, new_termios;
         char passphrase[LINE_MAX + 1] = {}, *x;
+        _cleanup_strv_free_erase_ char **l = NULL;
         struct pollfd pollfd[_POLL_MAX];
         size_t p = 0, codepoint = 0;
         int r;
@@ -235,14 +246,25 @@ int ask_password_tty(
         if (!message)
                 message = "Password:";
 
-        if (flag_file) {
+        if (flag_file || ((flags & ASK_PASSWORD_ACCEPT_CACHED) && keyname)) {
                 notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
                 if (notify < 0)
                         return -errno;
-
+        }
+        if (flag_file) {
                 if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0)
                         return -errno;
         }
+        if ((flags & ASK_PASSWORD_ACCEPT_CACHED) && keyname) {
+                r = ask_password_keyring(keyname, flags, ret);
+                if (r >= 0)
+                        return 0;
+                else if (r != -ENOKEY)
+                        return r;
+
+                if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_ATTRIB /* for mtime */) < 0)
+                        return -errno;
+        }
 
         /* If the caller didn't specify a TTY, then use the controlling tty, if we can. */
         if (ttyfd < 0)
@@ -289,9 +311,9 @@ int ask_password_tty(
         };
 
         for (;;) {
+                _cleanup_(erase_char) char c;
                 int sleep_for = -1, k;
                 ssize_t n;
-                char c;
 
                 if (until > 0) {
                         usec_t y;
@@ -324,9 +346,17 @@ int ask_password_tty(
                         goto finish;
                 }
 
-                if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0)
+                if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0 && keyname) {
                         (void) flush_fd(notify);
 
+                        r = ask_password_keyring(keyname, flags, ret);
+                        if (r >= 0) {
+                                r = 0;
+                                goto finish;
+                        } else if (r != -ENOKEY)
+                                goto finish;
+                }
+
                 if (pollfd[POLL_TTY].revents == 0)
                         continue;
 
@@ -349,7 +379,7 @@ int ask_password_tty(
                         if (!(flags & ASK_PASSWORD_SILENT))
                                 backspace_string(ttyfd, passphrase);
 
-                        explicit_bzero(passphrase, sizeof(passphrase));
+                        explicit_bzero_safe(passphrase, sizeof(passphrase));
                         p = codepoint = 0;
 
                 } else if (IN_SET(c, '\b', 127)) {
@@ -360,13 +390,13 @@ int ask_password_tty(
                                 if (!(flags & ASK_PASSWORD_SILENT))
                                         backspace_chars(ttyfd, 1);
 
-                                /* Remove a full UTF-8 codepoint from the end. For that, figure out where the last one
-                                 * begins */
+                                /* 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);
+                                        z = utf8_encoded_valid_unichar(passphrase + q, (size_t) -1);
                                         if (z == 0) {
                                                 q = (size_t) -1; /* Invalid UTF8! */
                                                 break;
@@ -379,14 +409,14 @@ int ask_password_tty(
                                 }
 
                                 p = codepoint = q == (size_t) -1 ? p - 1 : q;
-                                explicit_bzero(passphrase + p, sizeof(passphrase) - p);
+                                explicit_bzero_safe(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);
@@ -415,31 +445,35 @@ int ask_password_tty(
 
                         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);
+                                n = utf8_encoded_valid_unichar(passphrase + codepoint, (size_t) -1);
                                 if (n >= 0) {
+                                        if (flags & ASK_PASSWORD_ECHO)
+                                                (void) loop_write(ttyfd, passphrase + codepoint, n, false);
+                                        else
+                                                (void) loop_write(ttyfd, "*", 1, false);
                                         codepoint = p;
-                                        (void) loop_write(ttyfd, (flags & ASK_PASSWORD_ECHO) ? &c : "*", 1, false);
                                 }
                         }
 
                         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, sizeof(passphrase));
+        explicit_bzero_safe(passphrase, sizeof(passphrase));
         if (!x) {
                 r = -ENOMEM;
                 goto finish;
         }
 
+        r = strv_consume(&l, x);
+        if (r < 0)
+                goto finish;
+
         if (keyname)
-                (void) add_to_keyring_and_log(keyname, flags, STRV_MAKE(x));
+                (void) add_to_keyring_and_log(keyname, flags, l);
 
-        *ret = x;
+        *ret = TAKE_PTR(l);
         r = 0;
 
 finish:
@@ -455,7 +489,7 @@ static int create_socket(char **ret) {
         _cleanup_free_ char *path = NULL;
         union sockaddr_union sa = {};
         _cleanup_close_ int fd = -1;
-        int salen;
+        int salen, r;
 
         assert(ret);
 
@@ -475,8 +509,9 @@ static int create_socket(char **ret) {
                         return -errno;
         }
 
-        if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &const_int_one, sizeof(const_int_one)) < 0)
-                return -errno;
+        r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true);
+        if (r < 0)
+                return r;
 
         *ret = TAKE_PTR(path);
         return TAKE_FD(fd);
@@ -494,14 +529,15 @@ int ask_password_agent(
         enum {
                 FD_SOCKET,
                 FD_SIGNAL,
+                FD_INOTIFY,
                 _FD_MAX
         };
 
-        _cleanup_close_ int socket_fd = -1, signal_fd = -1, fd = -1;
+        _cleanup_close_ int socket_fd = -1, signal_fd = -1, notify = -1, fd = -1;
         char temp[] = "/run/systemd/ask-password/tmp.XXXXXX";
         char final[sizeof(temp)] = "";
         _cleanup_free_ char *socket_name = NULL;
-        _cleanup_strv_free_ char **l = NULL;
+        _cleanup_strv_free_erase_ char **l = NULL;
         _cleanup_fclose_ FILE *f = NULL;
         struct pollfd pollfd[_FD_MAX];
         sigset_t mask, oldmask;
@@ -518,6 +554,25 @@ int ask_password_agent(
 
         (void) mkdir_p_label("/run/systemd/ask-password", 0755);
 
+        if ((flags & ASK_PASSWORD_ACCEPT_CACHED) && keyname) {
+                r = ask_password_keyring(keyname, flags, ret);
+                if (r >= 0) {
+                        r = 0;
+                        goto finish;
+                } else if (r != -ENOKEY)
+                        goto finish;
+
+                notify = inotify_init1(IN_CLOEXEC | IN_NONBLOCK);
+                if (notify < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+                if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_ATTRIB /* for mtime */) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+        }
+
         fd = mkostemp_safe(temp);
         if (fd < 0) {
                 r = fd;
@@ -588,6 +643,8 @@ int ask_password_agent(
         pollfd[FD_SOCKET].events = POLLIN;
         pollfd[FD_SIGNAL].fd = signal_fd;
         pollfd[FD_SIGNAL].events = POLLIN;
+        pollfd[FD_INOTIFY].fd = notify;
+        pollfd[FD_INOTIFY].events = POLLIN;
 
         for (;;) {
                 char passphrase[LINE_MAX+1];
@@ -609,7 +666,7 @@ int ask_password_agent(
                         goto finish;
                 }
 
-                k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1);
+                k = poll(pollfd, notify >= 0 ? _FD_MAX : _FD_MAX - 1, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1);
                 if (k < 0) {
                         if (errno == EINTR)
                                 continue;
@@ -628,14 +685,26 @@ int ask_password_agent(
                         goto finish;
                 }
 
+                if (notify >= 0 && pollfd[FD_INOTIFY].revents != 0) {
+                        (void) flush_fd(notify);
+
+                        r = ask_password_keyring(keyname, flags, ret);
+                        if (r >= 0) {
+                                r = 0;
+                                goto finish;
+                        } else if (r != -ENOKEY)
+                                goto finish;
+                }
+
+                if (pollfd[FD_SOCKET].revents == 0)
+                        continue;
+
                 if (pollfd[FD_SOCKET].revents != POLLIN) {
                         r = -EIO;
                         goto finish;
                 }
 
-                zero(iovec);
-                iovec.iov_base = passphrase;
-                iovec.iov_len = sizeof(passphrase);
+                iovec = IOVEC_MAKE(passphrase, sizeof(passphrase));
 
                 zero(control);
                 zero(msghdr);
@@ -677,10 +746,10 @@ int ask_password_agent(
                 if (passphrase[0] == '+') {
                         /* An empty message refers to the empty password */
                         if (n == 1)
-                                l = strv_new("", NULL);
+                                l = strv_new("");
                         else
                                 l = strv_parse_nulstr(passphrase+1, n-1);
-                        explicit_bzero(passphrase, n);
+                        explicit_bzero_safe(passphrase, n);
                         if (!l) {
                                 r = -ENOMEM;
                                 goto finish;
@@ -735,29 +804,17 @@ int ask_password_auto(
 
         assert(ret);
 
-        if ((flags & ASK_PASSWORD_ACCEPT_CACHED) && keyname) {
+        if ((flags & ASK_PASSWORD_ACCEPT_CACHED) &&
+            keyname &&
+            ((flags & ASK_PASSWORD_NO_TTY) || !isatty(STDIN_FILENO)) &&
+            (flags & ASK_PASSWORD_NO_AGENT)) {
                 r = ask_password_keyring(keyname, flags, ret);
                 if (r != -ENOKEY)
                         return r;
         }
 
-        if (!(flags & ASK_PASSWORD_NO_TTY) && isatty(STDIN_FILENO)) {
-                char *s = NULL, **l = NULL;
-
-                r = ask_password_tty(-1, message, keyname, until, flags, NULL, &s);
-                if (r < 0)
-                        return r;
-
-                r = strv_push(&l, s);
-                if (r < 0) {
-                        string_erase(s);
-                        free(s);
-                        return -ENOMEM;
-                }
-
-                *ret = l;
-                return 0;
-        }
+        if (!(flags & ASK_PASSWORD_NO_TTY) && isatty(STDIN_FILENO))
+                return ask_password_tty(-1, message, keyname, until, flags, NULL, ret);
 
         if (!(flags & ASK_PASSWORD_NO_AGENT))
                 return ask_password_agent(message, icon, id, keyname, until, flags, ret);