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.
This commit is contained in:
Yu Watanabe
2026-01-02 12:55:53 +09:00
parent 8608d2e910
commit 5863641bb7
7 changed files with 135 additions and 126 deletions
+1 -1
View File
@@ -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)
+6 -15
View 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',
+13
View File
@@ -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
+111
View File
@@ -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;
}
+1
View File
@@ -5,6 +5,7 @@ if get_option('libc') != 'musl'
endif
libc_wrapper_sources += files(
'crypt.c',
'getopt.c',
'printf.c',
'stdio.c',
+2 -96
View 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);
}
+1 -14
View 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"));
}