From: Yu Watanabe Date: Sun, 17 Aug 2025 14:03:44 +0000 (+0900) Subject: Require libxcrypt-4.4.0 or newer and drop support of libcrypt X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5863641bb718777c289d3693bfa9bddd8ffc32b6;p=thirdparty%2Fsystemd.git Require libxcrypt-4.4.0 or newer and drop support of libcrypt 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. --- diff --git a/README b/README index 83493b04177..0fd153bebbb 100644 --- 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) diff --git a/meson.build b/meson.build index cc26bbd63e2..cf23ed51efe 100644 --- a/meson.build +++ b/meson.build @@ -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 ''', 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 index 00000000000..64570f70460 --- /dev/null +++ b/src/include/musl/crypt.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include_next + +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 index 00000000000..d44113fdc6e --- /dev/null +++ b/src/libc/musl/crypt.c @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/src/libc/musl/meson.build b/src/libc/musl/meson.build index d40a2f19115..4fabac7bcc9 100644 --- a/src/libc/musl/meson.build +++ b/src/libc/musl/meson.build @@ -5,6 +5,7 @@ if get_option('libc') != 'musl' endif libc_wrapper_sources += files( + 'crypt.c', 'getopt.c', 'printf.c', 'stdio.c', diff --git a/src/shared/libcrypt-util.c b/src/shared/libcrypt-util.c index 26e907f866d..6910c5f5276 100644 --- a/src/shared/libcrypt-util.c +++ b/src/shared/libcrypt-util.c @@ -1,33 +1,23 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #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); } diff --git a/src/test/test-libcrypt-util.c b/src/test/test-libcrypt-util.c index 9f171a2415a..92142fc9efa 100644 --- a/src/test/test-libcrypt-util.c +++ b/src/test/test-libcrypt-util.c @@ -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")); }