chiark / gitweb /
tree-wide: drop 'This file is part of systemd' blurb
[elogind.git] / src / basic / khash.c
index c7884207ff5fcddb65513711e09d24fa317725ec..08859832f614bd76449b51bc64ebfba06de6956f 100644 (file)
@@ -1,20 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of elogind.
-
   Copyright 2016 Lennart Poettering
-
-  elogind is free software; you can redistribute it and/or modify it
-  under the terms of the GNU Lesser General Public License as published by
-  the Free Software Foundation; either version 2.1 of the License, or
-  (at your option) any later version.
-
-  elogind is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with elogind; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
 #include <linux/if_alg.h>
@@ -42,6 +28,66 @@ struct khash {
         bool digest_valid;
 };
 
+int khash_supported(void) {
+        static const union {
+                struct sockaddr sa;
+                struct sockaddr_alg alg;
+        } sa = {
+                .alg.salg_family = AF_ALG,
+                .alg.salg_type = "hash",
+                .alg.salg_name = "sha256", /* a very common algorithm */
+        };
+
+        static int cached = -1;
+
+        if (cached < 0) {
+                _cleanup_close_ int fd1 = -1, fd2 = -1;
+                uint8_t buf[LONGEST_DIGEST+1];
+
+                fd1 = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
+                if (fd1 < 0) {
+                        /* The kernel returns EAFNOSUPPORT if AF_ALG is not supported at all */
+                        if (IN_SET(errno, EAFNOSUPPORT, EOPNOTSUPP))
+                                return (cached = false);
+
+                        return -errno;
+                }
+
+                if (bind(fd1, &sa.sa, sizeof(sa)) < 0) {
+                        /* The kernel returns ENOENT if the selected algorithm is not supported at all. We use a check
+                         * for SHA256 as a proxy for whether the whole API is supported at all. After all it's one of
+                         * the most common hash functions, and if it isn't supported, that's ample indication that
+                         * something is really off. */
+
+                        if (IN_SET(errno, ENOENT, EOPNOTSUPP))
+                                return (cached = false);
+
+                        return -errno;
+                }
+
+                fd2 = accept4(fd1, NULL, 0, SOCK_CLOEXEC);
+                if (fd2 < 0) {
+                        if (errno == EOPNOTSUPP)
+                                return (cached = false);
+
+                        return -errno;
+                }
+
+                if (recv(fd2, buf, sizeof(buf), 0) < 0) {
+                        /* On some kernels we get ENOKEY for non-keyed hash functions (such as sha256), let's refuse
+                         * using the API in those cases, since the kernel is
+                         * broken. https://github.com/systemd/systemd/issues/8278 */
+
+                        if (IN_SET(errno, ENOKEY, EOPNOTSUPP))
+                                return (cached = false);
+                }
+
+                cached = true;
+        }
+
+        return cached;
+}
+
 int khash_new_with_key(khash **ret, const char *algorithm, const void *key, size_t key_size) {
         union {
                 struct sockaddr sa;
@@ -53,6 +99,7 @@ int khash_new_with_key(khash **ret, const char *algorithm, const void *key, size
 
         _cleanup_(khash_unrefp) khash *h = NULL;
         _cleanup_close_ int fd = -1;
+        int supported;
         ssize_t n;
 
         assert(ret);
@@ -66,6 +113,12 @@ int khash_new_with_key(khash **ret, const char *algorithm, const void *key, size
         if (strlen(algorithm) >= sizeof(sa.alg.salg_name))
                 return -EOPNOTSUPP;
 
+        supported = khash_supported();
+        if (supported < 0)
+                return supported;
+        if (supported == 0)
+                return -EOPNOTSUPP;
+
         fd = socket(AF_ALG, SOCK_SEQPACKET|SOCK_CLOEXEC, 0);
         if (fd < 0)
                 return -errno;
@@ -126,9 +179,7 @@ khash* khash_unref(khash *h) {
 
         safe_close(h->fd);
         free(h->algorithm);
-        free(h);
-
-        return NULL;
+        return mfree(h);
 }
 
 int khash_dup(khash *h, khash **ret) {
@@ -143,15 +194,14 @@ int khash_dup(khash *h, khash **ret) {
 
         copy->fd = -1;
         copy->algorithm = strdup(h->algorithm);
-        if (!copy)
+        if (!copy->algorithm)
                 return -ENOMEM;
 
         copy->fd = accept4(h->fd, NULL, 0, SOCK_CLOEXEC);
         if (copy->fd < 0)
                 return -errno;
 
-        *ret = copy;
-        copy = NULL;
+        *ret = TAKE_PTR(copy);
 
         return 0;
 }