]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Require libxcrypt-4.4.0 or newer and drop support of libcrypt
authorYu Watanabe <watanabe.yu+github@gmail.com>
Sun, 17 Aug 2025 14:03:44 +0000 (23:03 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 2 Jan 2026 03:55:53 +0000 (12:55 +0900)
libcrypt was no longer built by default since glibc-2.38, and it has been
completely removed since glibc-2.39.

Let's always use libxcrypt, unless when building with musl. As already
major distribution already have libxcrypt-4.4.x, hence let's also bump
the required minimum version to 4.4.0.

libxcrypt cannot be built with musl, hence the previous fallback logic
in libcrypt-util.c are moved to musl/crypt.c.

Note, libxcrypt-4.4.0 was released on 2018-11-20.
See also #38608.

README
meson.build
src/include/musl/crypt.h [new file with mode: 0644]
src/libc/musl/crypt.c [new file with mode: 0644]
src/libc/musl/meson.build
src/shared/libcrypt-util.c
src/test/test-libcrypt-util.c

diff --git a/README b/README
index 83493b041776accf450691a63fbf5345fa47d7a5..0fd153bebbbfc065fe027bb7d8f1a5c56e3e809a 100644 (file)
--- a/README
+++ b/README
@@ -212,7 +212,7 @@ REQUIREMENTS:
         newer though. TL;DR: turn audit off, still.
 
         glibc >= 2.31
-        libxcrypt or glibc (<= 2.38 built with --enable-crypt)
+        libxcrypt >= 4.4.0
         libmount >= 2.30 (from util-linux)
                 (util-linux *must* be built without --enable-libmount-support-mtab)
         libseccomp >= 2.4.0 (optional)
index cc26bbd63e2f62111f22884ed0f295478bfe83e3..cf23ed51efee5a4091f8aa8cd40d257dfe832c5b 100644 (file)
@@ -1046,23 +1046,14 @@ else
         libatomic = []
 endif
 
-libcrypt = dependency('libcrypt', 'libxcrypt', required : false)
-if not libcrypt.found()
-        # fallback to use find_library() if libcrypt is provided by glibc, e.g. for LibreELEC.
-        libcrypt = cc.find_library('crypt')
+if get_option('libc') == 'musl'
+        libcrypt = []
+else
+        libcrypt = dependency('libcrypt', 'libxcrypt',
+                              required : true,
+                              version : '>=4.4.0')
 endif
 
-foreach func : [
-        'crypt_ra',               # since libxcrypt-4.0.0
-        'crypt_gensalt_ra',       # since libxcrypt-4.0.0
-        'crypt_preferred_method', # since libxcrypt-4.4.0
-]
-
-        have = cc.has_function(func, prefix : '''#include <crypt.h>''', args : '-D_GNU_SOURCE',
-                               dependencies : libcrypt)
-        conf.set10('HAVE_' + func.to_upper(), have)
-endforeach
-
 bpf_framework = get_option('bpf-framework')
 bpf_compiler = get_option('bpf-compiler')
 libbpf = dependency('libbpf',
diff --git a/src/include/musl/crypt.h b/src/include/musl/crypt.h
new file mode 100644 (file)
index 0000000..64570f7
--- /dev/null
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include_next <crypt.h>
+
+const char* missing_crypt_preferred_method(void);
+#define crypt_preferred_method missing_crypt_preferred_method
+
+char* missing_crypt_gensalt_ra(const char *prefix, unsigned long count, const char *rbytes, int nrbytes);
+#define crypt_gensalt_ra missing_crypt_gensalt_ra
+
+char* missing_crypt_ra(const char *phrase, const char *setting, void **data, int *size);
+#define crypt_ra missing_crypt_ra
diff --git a/src/libc/musl/crypt.c b/src/libc/musl/crypt.c
new file mode 100644 (file)
index 0000000..d44113f
--- /dev/null
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <assert.h>
+#include <crypt.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/random.h>
+
+const char* missing_crypt_preferred_method(void) {
+        return "$6$";
+}
+
+char* missing_crypt_gensalt_ra(const char *prefix, unsigned long count, const char *rbytes, int nrbytes) {
+        static const char table[] =
+                "abcdefghijklmnopqrstuvwxyz"
+                "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                "0123456789"
+                "./";
+
+        static_assert(sizeof(table) == 64U + 1U);
+
+        /* This doesn't do anything but SHA512, and silently ignore all arguments, to make it legacy-free and
+         * minimize the implementation. */
+
+        /* Insist on the best randomness by getrandom(), this is about keeping passwords secret after all. */
+        uint8_t raw[16];
+        for (size_t i = 0; i < sizeof(raw);) {
+                size_t n = sizeof(raw) - i;
+                ssize_t l = getrandom(raw + i, n, 0);
+                if (l < 0)
+                        return NULL;
+                if (l == 0) {
+                        /* Weird, should never happen. */
+                        errno = EIO;
+                        return NULL;
+                }
+
+                if ((size_t) l == n)
+                        break; /* Done reading, success. */
+
+                i += l;
+                /* Interrupted by a signal; keep going. */
+        }
+
+        /* "$6$" + salt + "$" + NUL */
+        char *salt = malloc(3 + sizeof(raw) + 1 + 1);
+        if (!salt) {
+                errno = ENOMEM;
+                return NULL;
+        }
+
+        /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
+        char *p = stpcpy(salt, "$6$");
+        for (size_t i = 0; i < sizeof(raw); i++)
+                *p++ = table[raw[i] & 63];
+        *p++ = '$';
+        *p = '\0';
+
+        return salt;
+}
+
+char* missing_crypt_ra(const char *phrase, const char *setting, void **data, int *size) {
+        struct crypt_data *buf = NULL;
+        bool allocated = false;
+
+        if (!phrase || !setting || !data || !size) {
+                errno = EINVAL;
+                return NULL;
+        }
+
+        if (*data) {
+                if (*size != sizeof(struct crypt_data)) {
+                        errno = EINVAL;
+                        return NULL;
+                }
+
+                buf = *data;
+        } else {
+                if (*size != 0) {
+                        errno = EINVAL;
+                        return NULL;
+                }
+
+                buf = calloc(1, sizeof(struct crypt_data));
+                if (!buf) {
+                        errno = ENOMEM;
+                        return NULL;
+                }
+
+                allocated = true;
+        }
+
+        /* crypt_r may return a pointer to an invalid hashed password on error. Our callers expect NULL on
+         * error, so let's just return that. */
+
+        char *t = crypt_r(phrase, setting, buf);
+        if (!t || t[0] == '*') {
+                if (allocated)
+                        free(buf);
+                return NULL;
+        }
+
+        if (allocated) {
+                *data = buf;
+                *size = sizeof(struct crypt_data);
+        }
+        return t;
+}
index d40a2f19115efd7da727bdceb689b490e273aeff..4fabac7bcc9df8dd643b15aff0425617b1efe2bd 100644 (file)
@@ -5,6 +5,7 @@ if get_option('libc') != 'musl'
 endif
 
 libc_wrapper_sources += files(
+        'crypt.c',
         'getopt.c',
         'printf.c',
         'stdio.c',
index 26e907f866d9684c2885689c736d15b92d538f01..6910c5f5276a0068fb58edbdd8e6477e97bfaea4 100644 (file)
@@ -1,33 +1,23 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include <crypt.h>
-#include <stdlib.h>
 
 #include "alloc-util.h"
 #include "errno-util.h"
 #include "libcrypt-util.h"
 #include "log.h"
-#include "random-util.h"        /* IWYU pragma: keep */
 #include "string-util.h"
 #include "strv.h"
 
 int make_salt(char **ret) {
-        assert(ret);
-
-#if HAVE_CRYPT_GENSALT_RA
         const char *e;
         char *salt;
 
-        /* If we have crypt_gensalt_ra() we default to the "preferred method" (i.e. usually yescrypt).
-         * crypt_gensalt_ra() is usually provided by libxcrypt. */
+        assert(ret);
 
         e = secure_getenv("SYSTEMD_CRYPT_PREFIX");
         if (!e)
-#if HAVE_CRYPT_PREFERRED_METHOD
                 e = crypt_preferred_method();
-#else
-                e = "$6$";
-#endif
 
         log_debug("Generating salt for hash prefix: %s", e);
 
@@ -37,91 +27,8 @@ int make_salt(char **ret) {
 
         *ret = salt;
         return 0;
-#else
-        /* If crypt_gensalt_ra() is not available, we use SHA512 and generate the salt on our own. */
-
-        static const char table[] =
-                "abcdefghijklmnopqrstuvwxyz"
-                "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-                "0123456789"
-                "./";
-
-        uint8_t raw[16];
-        char *salt, *j;
-        size_t i;
-        int r;
-
-        /* This is a bit like crypt_gensalt_ra(), but doesn't require libcrypt, and doesn't do anything but
-         * SHA512, i.e. is legacy-free and minimizes our deps. */
-
-        assert_cc(sizeof(table) == 64U + 1U);
-
-        log_debug("Generating fallback salt for hash prefix: $6$");
-
-        /* Insist on the best randomness by setting RANDOM_BLOCK, this is about keeping passwords secret after all. */
-        r = crypto_random_bytes(raw, sizeof(raw));
-        if (r < 0)
-                return r;
-
-        salt = new(char, 3+sizeof(raw)+1+1);
-        if (!salt)
-                return -ENOMEM;
-
-        /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
-        j = stpcpy(salt, "$6$");
-        for (i = 0; i < sizeof(raw); i++)
-                j[i] = table[raw[i] & 63];
-        j[i++] = '$';
-        j[i] = 0;
-
-        *ret = salt;
-        return 0;
-#endif
-}
-
-#if HAVE_CRYPT_RA
-#  define CRYPT_RA_NAME "crypt_ra"
-#else
-#  define CRYPT_RA_NAME "crypt_r"
-
-/* Provide a poor man's fallback that uses a fixed size buffer. */
-
-static char* systemd_crypt_ra(const char *phrase, const char *setting, void **data, int *size) {
-        assert(phrase);
-        assert(setting);
-        assert(data);
-        assert(size);
-
-        /* We allocate the buffer because crypt(3) says: struct crypt_data may be quite large (32kB in this
-         * implementation of libcrypt; over 128kB in some other implementations). This is large enough that
-         * it may be unwise to allocate it on the stack. */
-
-        if (!*data) {
-                *data = new0(struct crypt_data, 1);
-                if (!*data) {
-                        errno = ENOMEM;
-                        return NULL;
-                }
-
-                *size = (int) (sizeof(struct crypt_data));
-        }
-
-        char *t = crypt_r(phrase, setting, *data);
-        if (!t)
-                return NULL;
-
-        /* crypt_r may return a pointer to an invalid hashed password on error. Our callers expect NULL on
-         * error, so let's just return that. */
-        if (t[0] == '*')
-                return NULL;
-
-        return t;
 }
 
-#define crypt_ra systemd_crypt_ra
-
-#endif
-
 int hash_password(const char *password, char **ret) {
         _cleanup_free_ char *salt = NULL;
         _cleanup_(erase_and_freep) void *cd_data = NULL;
@@ -138,8 +45,7 @@ int hash_password(const char *password, char **ret) {
         errno = 0;
         p = crypt_ra(password, salt, &cd_data, &cd_size);
         if (!p)
-                return log_debug_errno(errno_or_else(SYNTHETIC_ERRNO(EINVAL)),
-                                       CRYPT_RA_NAME "() failed: %m");
+                return log_debug_errno(errno_or_else(SYNTHETIC_ERRNO(EINVAL)), "crypt_ra() failed: %m");
 
         return strdup_to(ret, p);
 }
index 9f171a2415a29fa786bcd4726a1b07c22ebde37b..92142fc9efae53572cc3930c46b9840f98375a70 100644 (file)
@@ -7,13 +7,7 @@
 #include "tests.h"
 
 TEST(crypt_preferred_method) {
-        log_info("crypt_preferred_method: %s",
-#if HAVE_CRYPT_PREFERRED_METHOD
-                 crypt_preferred_method()
-#else
-                 "(not available)"
-#endif
-        );
+        log_info("crypt_preferred_method: %s", crypt_preferred_method());
 }
 
 TEST(make_salt) {
@@ -31,10 +25,6 @@ TEST(make_salt) {
 }
 
 TEST(hash_password) {
-#if defined(__powerpc__) && !defined(XCRYPT_VERSION_MAJOR)
-        return log_tests_skipped("crypt_r() causes a buffer overflow on ppc64el, see https://github.com/systemd/systemd/pull/16981#issuecomment-691203787");
-#endif
-
         /* As a warm-up exercise, check if we can hash passwords. */
         FOREACH_STRING(hash,
                        "ew3bU1.hoKk4o",
@@ -50,9 +40,6 @@ TEST(hash_password) {
                         ASSERT_OK_ZERO(test_password_one(hash, "ppp"));
                         continue;
                 }
-#elif !defined(XCRYPT_VERSION_MAJOR)
-                ASSERT_OK(test_password_one(hash, "ppp"));
-                continue;
 #endif
                 ASSERT_OK_POSITIVE(test_password_one(hash, "ppp"));
         }