Compare commits

..

No commits in common. "d2841d563eadf7ab318cf2ecdabfb03d39977fb3" and "5e258d734ad02c2aa7888e3fcf047442d6b0f0f0" have entirely different histories.

18 changed files with 144 additions and 336 deletions

23
TODO
View File

@ -20,24 +20,12 @@ Janitorial Clean-ups:
Features: Features:
* add wrapper for mount() that uses O_PATH on the destination and than mounts
to /proc/self/fd/xxx so that we basically have a mount() with O_NOFOLLOW like
behaviour. (in case of bind mounts do it on both source and target)
* add root=tmpfs that mounts a tmpfs to /sysroot (to be used in combination
with usr=…, for a similar effect as systemd.volatile=yes but without the
"hide-out" effect). Also, add root=gpt-auto-late support or so, that is like
root=gpt-auto but initially mounts a tmpfs to /sysroot, and then revisits
later after systemd-repart ran. Usecase: let's ship images with only /usr
partition, then on first boot create the root partition. In this case we want
to read the repart data from /usr before the root partition exists. Add
usr=gpt-auto that automatically finds a /usr partition.
* homed: keep an fd to the homedir open at all times, to keep the fs pinned
(autofs and such) while user is loged in.
* nss-systemd: also synthesize shadow records for users/groups * nss-systemd: also synthesize shadow records for users/groups
* nspawn: move "incoming mount" directory to /run/host, move "inaccessible"
nodes to /run/host, move notify socket (for sd_notify() between payload and
container manager)
* make use of new glibc 2.32 APIs sigabbrev_np() and strerrorname_np(). * make use of new glibc 2.32 APIs sigabbrev_np() and strerrorname_np().
* cryptsetup: if keyfile specified in crypttab is AF_UNIX socket, connect to it * cryptsetup: if keyfile specified in crypttab is AF_UNIX socket, connect to it
@ -77,6 +65,9 @@ Features:
often for one, let's turn it off entirely for a while. Use that for the often for one, let's turn it off entirely for a while. Use that for the
/proc/self/mountinfo logic. /proc/self/mountinfo logic.
* move our systemd-user PAM snippet to /usr/, which PAM appears to support
these days
* nspawn: support time namespaces * nspawn: support time namespaces
* systemd-firstboot: make sure to always use chase_symlinks() before * systemd-firstboot: make sure to always use chase_symlinks() before

View File

@ -1370,18 +1370,15 @@
<listitem><para>Configures how to set up standard input, output and error output for the container <listitem><para>Configures how to set up standard input, output and error output for the container
payload, as well as the <filename>/dev/console</filename> device for the container. Takes one of payload, as well as the <filename>/dev/console</filename> device for the container. Takes one of
<option>interactive</option>, <option>read-only</option>, <option>passive</option>, <option>interactive</option>, <option>read-only</option>, <option>passive</option>, or
<option>pipe</option> or <option>autopipe</option>. If <option>interactive</option>, a pseudo-TTY is <option>pipe</option>. If <option>interactive</option>, a pseudo-TTY is allocated and made available
allocated and made available as <filename>/dev/console</filename> in the container. It is then as <filename>/dev/console</filename> in the container. It is then bi-directionally connected to the
bi-directionally connected to the standard input and output passed to standard input and output passed to <command>systemd-nspawn</command>. <option>read-only</option> is
<command>systemd-nspawn</command>. <option>read-only</option> is similar but only the output of the similar but only the output of the container is propagated and no input from the caller is read. If
container is propagated and no input from the caller is read. If <option>passive</option>, a pseudo <option>passive</option>, a pseudo TTY is allocated, but it is not connected anywhere. Finally, in
TTY is allocated, but it is not connected anywhere. In <option>pipe</option> mode no pseudo TTY is <option>pipe</option> mode no pseudo TTY is allocated, but the standard input, output and error
allocated, but the standard input, output and error output file descriptors passed to output file descriptors passed to <command>systemd-nspawn</command> are passed on — as they are — to
<command>systemd-nspawn</command> are passed on — as they are — to the container payload, see the the container payload, see the following paragraph. Defaults to <option>interactive</option> if
following paragraph. Finally, <option>autopipe</option> mode operates like
<option>interactive</option> when <command>systemd-nspawn</command> is invoked on a terminal, and
like <option>pipe</option> otherwise. Defaults to <option>interactive</option> if
<command>systemd-nspawn</command> is invoked from a terminal, and <option>read-only</option> <command>systemd-nspawn</command> is invoked from a terminal, and <option>read-only</option>
otherwise.</para> otherwise.</para>

View File

@ -882,17 +882,6 @@ libm = cc.find_library('m')
libdl = cc.find_library('dl') libdl = cc.find_library('dl')
libcrypt = cc.find_library('crypt') libcrypt = cc.find_library('crypt')
crypt_header = conf.get('HAVE_CRYPT_H') == 1 ? \
'''#include <crypt.h>''' : '''#include <unistd.h>'''
foreach ident : [
['crypt_ra', crypt_header],
['crypt_gensalt_ra', crypt_header]]
have = cc.has_function(ident[0], prefix : ident[1], args : '-D_GNU_SOURCE',
dependencies : libcrypt)
conf.set10('HAVE_' + ident[0].to_upper(), have)
endforeach
libcap = dependency('libcap', required : false) libcap = dependency('libcap', required : false)
if not libcap.found() if not libcap.found()
# Compat with Ubuntu 14.04 which ships libcap w/o .pc file # Compat with Ubuntu 14.04 which ships libcap w/o .pc file

View File

@ -802,7 +802,7 @@ static int write_root_shadow(const char *shadow_path, const char *hashed_passwor
static int process_root_args(void) { static int process_root_args(void) {
_cleanup_close_ int lock = -1; _cleanup_close_ int lock = -1;
_cleanup_(erase_and_freep) char *_hashed_password = NULL; struct crypt_data cd = {};
const char *password, *hashed_password; const char *password, *hashed_password;
const char *etc_passwd, *etc_shadow; const char *etc_passwd, *etc_shadow;
int r; int r;
@ -866,13 +866,20 @@ static int process_root_args(void) {
password = "x"; password = "x";
hashed_password = arg_root_password; hashed_password = arg_root_password;
} else if (arg_root_password) { } else if (arg_root_password) {
r = hash_password(arg_root_password, &_hashed_password); _cleanup_free_ char *salt = NULL;
if (r < 0) /* hashed_password points inside cd after crypt_r returns so cd has function scope. */
return log_error_errno(r, "Failed to hash password: %m");
password = "x"; password = "x";
hashed_password = _hashed_password;
r = make_salt(&salt);
if (r < 0)
return log_error_errno(r, "Failed to get salt: %m");
errno = 0;
hashed_password = crypt_r(arg_root_password, salt, &cd);
if (!hashed_password)
return log_error_errno(errno == 0 ? SYNTHETIC_ERRNO(EINVAL) : errno,
"Failed to encrypt password: %m");
} else if (arg_delete_root_password) } else if (arg_delete_root_password)
password = hashed_password = ""; password = hashed_password = "";
else else

View File

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1+ */ /* SPDX-License-Identifier: LGPL-2.1+ */
#include "dns-domain.h" #include "dns-domain.h"
#include "errno-util.h"
#include "home-util.h" #include "home-util.h"
#include "libcrypt-util.h" #include "libcrypt-util.h"
#include "memory-util.h" #include "memory-util.h"
@ -133,3 +134,35 @@ int bus_message_append_secret(sd_bus_message *m, UserRecord *secret) {
return sd_bus_message_append(m, "s", formatted); return sd_bus_message_append(m, "s", formatted);
} }
int test_password_one(const char *hashed_password, const char *password) {
struct crypt_data cc = {};
const char *k;
bool b;
errno = 0;
k = crypt_r(password, hashed_password, &cc);
if (!k) {
explicit_bzero_safe(&cc, sizeof(cc));
return errno_or_else(EINVAL);
}
b = streq(k, hashed_password);
explicit_bzero_safe(&cc, sizeof(cc));
return b;
}
int test_password_many(char **hashed_password, const char *password) {
char **hpw;
int r;
STRV_FOREACH(hpw, hashed_password) {
r = test_password_one(*hpw, password);
if (r < 0)
return r;
if (r > 0)
return true;
}
return false;
}

View File

@ -21,3 +21,6 @@ int bus_message_append_secret(sd_bus_message *m, UserRecord *secret);
/* Many of our operations might be slow due to crypto, fsck, recursive chown() and so on. For these /* Many of our operations might be slow due to crypto, fsck, recursive chown() and so on. For these
* operations permit a *very* long timeout */ * operations permit a *very* long timeout */
#define HOME_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE) #define HOME_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE)
int test_password_one(const char *hashed_password, const char *password);
int test_password_many(char **hashed_password, const char *password);

View File

@ -70,23 +70,31 @@ static int add_fido2_salt(
size_t secret_size) { size_t secret_size) {
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL; _cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *hashed = NULL; _cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_free_ char *unix_salt = NULL;
struct crypt_data cd = {};
char *k;
int r; int r;
r = make_salt(&unix_salt);
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
/* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends /* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
* expect a NUL terminated string, and we use a binary key */ * expect a NUL terminated string, and we use a binary key */
r = base64mem(secret, secret_size, &base64_encoded); r = base64mem(secret, secret_size, &base64_encoded);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to base64 encode secret key: %m"); return log_error_errno(r, "Failed to base64 encode secret key: %m");
r = hash_password(base64_encoded, &hashed); errno = 0;
if (r < 0) k = crypt_r(base64_encoded, unix_salt, &cd);
if (!k)
return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m"); return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
r = json_build(&e, JSON_BUILD_OBJECT( r = json_build(&e, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("credential", JSON_BUILD_BASE64(cid, cid_size)), JSON_BUILD_PAIR("credential", JSON_BUILD_BASE64(cid, cid_size)),
JSON_BUILD_PAIR("salt", JSON_BUILD_BASE64(fido2_salt, fido2_salt_size)), JSON_BUILD_PAIR("salt", JSON_BUILD_BASE64(fido2_salt, fido2_salt_size)),
JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(hashed)))); JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(k))));
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to build FIDO2 salt JSON key object: %m"); return log_error_errno(r, "Failed to build FIDO2 salt JSON key object: %m");

View File

@ -134,7 +134,10 @@ static int add_pkcs11_encrypted_key(
const void *decrypted_key, size_t decrypted_key_size) { const void *decrypted_key, size_t decrypted_key_size) {
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL; _cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *hashed = NULL; _cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_free_ char *salt = NULL;
struct crypt_data cd = {};
char *k;
int r; int r;
assert(v); assert(v);
@ -144,20 +147,25 @@ static int add_pkcs11_encrypted_key(
assert(decrypted_key); assert(decrypted_key);
assert(decrypted_key_size > 0); assert(decrypted_key_size > 0);
r = make_salt(&salt);
if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
/* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends /* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends
* expect a NUL terminated string, and we use a binary key */ * expect a NUL terminated string, and we use a binary key */
r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to base64 encode secret key: %m"); return log_error_errno(r, "Failed to base64 encode secret key: %m");
r = hash_password(base64_encoded, &hashed); errno = 0;
if (r < 0) k = crypt_r(base64_encoded, salt, &cd);
if (!k)
return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m"); return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
r = json_build(&e, JSON_BUILD_OBJECT( r = json_build(&e, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("uri", JSON_BUILD_STRING(uri)), JSON_BUILD_PAIR("uri", JSON_BUILD_STRING(uri)),
JSON_BUILD_PAIR("data", JSON_BUILD_BASE64(encrypted_key, encrypted_key_size)), JSON_BUILD_PAIR("data", JSON_BUILD_BASE64(encrypted_key, encrypted_key_size)),
JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(hashed)))); JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(k))));
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to build encrypted JSON key object: %m"); return log_error_errno(r, "Failed to build encrypted JSON key object: %m");

View File

@ -183,7 +183,9 @@ static int print_qr_code(const char *secret) {
} }
int identity_add_recovery_key(JsonVariant **v) { int identity_add_recovery_key(JsonVariant **v) {
_cleanup_(erase_and_freep) char *password = NULL, *hashed = NULL; _cleanup_(erase_and_freep) char *unix_salt = NULL, *password = NULL;
struct crypt_data cd = {};
char *k;
int r; int r;
assert(v); assert(v);
@ -194,12 +196,17 @@ int identity_add_recovery_key(JsonVariant **v) {
return r; return r;
/* Let's UNIX hash it */ /* Let's UNIX hash it */
r = hash_password(password, &hashed); r = make_salt(&unix_salt);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to generate salt: %m");
errno = 0;
k = crypt_r(password, unix_salt, &cd);
if (!k)
return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m"); return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m");
/* Let's now add the "privileged" version of the recovery key */ /* Let's now add the "privileged" version of the recovery key */
r = add_privileged(v, hashed); r = add_privileged(v, k);
if (r < 0) if (r < 0)
return r; return r;

View File

@ -17,7 +17,6 @@
#include "homework-mount.h" #include "homework-mount.h"
#include "homework-pkcs11.h" #include "homework-pkcs11.h"
#include "homework.h" #include "homework.h"
#include "libcrypt-util.h"
#include "main-func.h" #include "main-func.h"
#include "memory-util.h" #include "memory-util.h"
#include "missing_magic.h" #include "missing_magic.h"

View File

@ -3,7 +3,6 @@
#include "bus-common-errors.h" #include "bus-common-errors.h"
#include "errno-util.h" #include "errno-util.h"
#include "home-util.h" #include "home-util.h"
#include "libcrypt-util.h"
#include "pwquality-util.h" #include "pwquality-util.h"
#include "strv.h" #include "strv.h"
#include "user-record-pwquality.h" #include "user-record-pwquality.h"

View File

@ -806,13 +806,20 @@ int user_record_make_hashed_password(UserRecord *h, char **secret, bool extend)
} }
STRV_FOREACH(i, secret) { STRV_FOREACH(i, secret) {
_cleanup_(erase_and_freep) char *hashed = NULL; _cleanup_free_ char *salt = NULL;
struct crypt_data cd = {};
char *k;
r = hash_password(*i, &hashed); r = make_salt(&salt);
if (r < 0) if (r < 0)
return r; return r;
r = strv_consume(&np, TAKE_PTR(hashed)); errno = 0;
k = crypt_r(*i, salt, &cd);
if (!k)
return errno_or_else(EINVAL);
r = strv_extend(&np, k);
if (r < 0) if (r < 0)
return r; return r;
} }

View File

@ -53,6 +53,12 @@ int stub_pid1(sd_id128_t uuid) {
assert_se(sigfillset(&fullmask) >= 0); assert_se(sigfillset(&fullmask) >= 0);
assert_se(sigprocmask(SIG_BLOCK, &fullmask, &oldmask) >= 0); assert_se(sigprocmask(SIG_BLOCK, &fullmask, &oldmask) >= 0);
/* Surrender the terminal this stub may control so that child processes can have a controlling terminal
* without resorting to setsid hacks. */
r = ioctl(STDIN_FILENO, TIOCNOTTY);
if (r < 0 && errno != ENOTTY)
return log_error_errno(errno, "Failed to surrender controlling terminal: %m");
pid = fork(); pid = fork();
if (pid < 0) if (pid < 0)
return log_error_errno(errno, "Failed to fork child pid: %m"); return log_error_errno(errno, "Failed to fork child pid: %m");
@ -60,10 +66,7 @@ int stub_pid1(sd_id128_t uuid) {
if (pid == 0) { if (pid == 0) {
/* Return in the child */ /* Return in the child */
assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) >= 0); assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) >= 0);
setsid();
if (setsid() < 0)
return log_error_errno(errno, "Failed to become session leader in payload process: %m");
return 0; return 0;
} }
@ -73,12 +76,6 @@ int stub_pid1(sd_id128_t uuid) {
(void) close_all_fds(NULL, 0); (void) close_all_fds(NULL, 0);
log_open(); log_open();
if (ioctl(STDIN_FILENO, TIOCNOTTY) < 0) {
if (errno != ENOTTY)
log_warning_errno(errno, "Unexpected error from TIOCNOTTY ioctl in init stub process, ignoring: %m");
} else
log_warning("Expected TIOCNOTTY to fail, but it succeeded in init stub process, ignoring.");
/* Flush out /proc/self/environ, so that we don't leak the environment from the host into the container. Also, /* Flush out /proc/self/environ, so that we don't leak the environment from the host into the container. Also,
* set $container= and $container_uuid= so that clients in the container that query it from /proc/1/environ * set $container= and $container_uuid= so that clients in the container that query it from /proc/1/environ
* find them set. */ * find them set. */

View File

@ -11,12 +11,10 @@
#endif #endif
#include <stdlib.h> #include <stdlib.h>
#include <sys/file.h> #include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/personality.h> #include <sys/personality.h>
#include <sys/prctl.h> #include <sys/prctl.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <termios.h>
#include <unistd.h> #include <unistd.h>
#include "sd-bus.h" #include "sd-bus.h"
@ -256,11 +254,10 @@ STATIC_DESTRUCTOR_REGISTER(arg_sysctl, strv_freep);
static int handle_arg_console(const char *arg) { static int handle_arg_console(const char *arg) {
if (streq(arg, "help")) { if (streq(arg, "help")) {
puts("autopipe\n" puts("interactive\n"
"interactive\n" "read-only\n"
"passive\n" "passive\n"
"pipe\n" "pipe");
"read-only");
return 0; return 0;
} }
@ -270,20 +267,9 @@ static int handle_arg_console(const char *arg) {
arg_console_mode = CONSOLE_READ_ONLY; arg_console_mode = CONSOLE_READ_ONLY;
else if (streq(arg, "passive")) else if (streq(arg, "passive"))
arg_console_mode = CONSOLE_PASSIVE; arg_console_mode = CONSOLE_PASSIVE;
else if (streq(arg, "pipe")) { else if (streq(arg, "pipe"))
if (isatty(STDIN_FILENO) > 0 && isatty(STDOUT_FILENO) > 0)
log_full(arg_quiet ? LOG_DEBUG : LOG_NOTICE,
"Console mode 'pipe' selected, but standard input/output are connected to an interactive TTY. "
"Most likely you want to use 'interactive' console mode for proper interactivity and shell job control. "
"Proceeding anyway.");
arg_console_mode = CONSOLE_PIPE; arg_console_mode = CONSOLE_PIPE;
} else if (streq(arg, "autopipe")) {
if (isatty(STDIN_FILENO) > 0 && isatty(STDOUT_FILENO) > 0)
arg_console_mode = CONSOLE_INTERACTIVE;
else else
arg_console_mode = CONSOLE_PIPE;
} else
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown console mode: %s", optarg); return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown console mode: %s", optarg);
arg_settings_mask |= SETTING_CONSOLE_MODE; arg_settings_mask |= SETTING_CONSOLE_MODE;
@ -2283,12 +2269,10 @@ static int setup_pts(const char *dest) {
} }
static int setup_stdio_as_dev_console(void) { static int setup_stdio_as_dev_console(void) {
_cleanup_close_ int terminal = -1; int terminal;
int r; int r;
/* We open the TTY in O_NOCTTY mode, so that we do not become controller yet. We'll do that later terminal = open_terminal("/dev/console", O_RDWR);
* explicitly, if we are configured to. */
terminal = open_terminal("/dev/console", O_RDWR|O_NOCTTY);
if (terminal < 0) if (terminal < 0)
return log_error_errno(terminal, "Failed to open console: %m"); return log_error_errno(terminal, "Failed to open console: %m");
@ -2300,7 +2284,6 @@ static int setup_stdio_as_dev_console(void) {
/* invalidates 'terminal' on success and failure */ /* invalidates 'terminal' on success and failure */
r = rearrange_stdio(terminal, terminal, terminal); r = rearrange_stdio(terminal, terminal, terminal);
TAKE_FD(terminal);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to move console to stdin/stdout/stderr: %m"); return log_error_errno(r, "Failed to move console to stdin/stdout/stderr: %m");
@ -3383,7 +3366,8 @@ static int inner_child(
* wait until the parent is ready with the * wait until the parent is ready with the
* setup, too... */ * setup, too... */
if (!barrier_place_and_sync(barrier)) /* #5 */ if (!barrier_place_and_sync(barrier)) /* #5 */
return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Parent died too early"); return log_error_errno(SYNTHETIC_ERRNO(ESRCH),
"Parent died too early");
if (arg_chdir) if (arg_chdir)
if (chdir(arg_chdir) < 0) if (chdir(arg_chdir) < 0)
@ -3395,13 +3379,6 @@ static int inner_child(
return r; return r;
} }
if (arg_console_mode != CONSOLE_PIPE) {
/* So far our pty wasn't controlled by any process. Finally, it's time to change that, if we
* are configured for that. Acquire it as controlling tty. */
if (ioctl(STDIN_FILENO, TIOCSCTTY) < 0)
return log_error_errno(errno, "Failed to acquire controlling TTY: %m");
}
log_debug("Inner child completed, invoking payload."); log_debug("Inner child completed, invoking payload.");
/* Now, explicitly close the log, so that we then can close all remaining fds. Closing the log explicitly first /* Now, explicitly close the log, so that we then can close all remaining fds. Closing the log explicitly first

View File

@ -1,29 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1+ */ /* SPDX-License-Identifier: LGPL-2.1+ */
#if HAVE_CRYPT_H
/* libxcrypt is a replacement for glibc's libcrypt, and libcrypt might be
* removed from glibc at some point. As part of the removal, defines for
* crypt(3) are dropped from unistd.h, and we must include crypt.h instead.
*
* Newer versions of glibc (v2.0+) already ship crypt.h with a definition
* of crypt(3) as well, so we simply include it if it is present. MariaDB,
* MySQL, PostgreSQL, Perl and some other wide-spread packages do it the
* same way since ages without any problems.
*/
# include <crypt.h>
#else
# include <unistd.h>
#endif
#include <errno.h> #include <errno.h>
#include <stdlib.h> #include <stdlib.h>
#include "alloc-util.h" #include "alloc-util.h"
#include "errno-util.h"
#include "libcrypt-util.h" #include "libcrypt-util.h"
#include "log.h" #include "log.h"
#include "macro.h" #include "macro.h"
#include "memory-util.h"
#include "missing_stdlib.h" #include "missing_stdlib.h"
#include "random-util.h" #include "random-util.h"
#include "string-util.h" #include "string-util.h"
@ -31,12 +14,12 @@
int make_salt(char **ret) { int make_salt(char **ret) {
#if HAVE_CRYPT_GENSALT_RA #ifdef XCRYPT_VERSION_MAJOR
const char *e; const char *e;
char *salt; char *salt;
/* If we have crypt_gensalt_ra() we default to the "preferred method" (i.e. usually yescrypt). /* If we have libxcrypt we default to the "preferred method" (i.e. usually yescrypt), and generate it
* crypt_gensalt_ra() is usually provided by libxcrypt. */ * with crypt_gensalt_ra(). */
e = secure_getenv("SYSTEMD_CRYPT_PREFIX"); e = secure_getenv("SYSTEMD_CRYPT_PREFIX");
if (!e) if (!e)
@ -51,7 +34,8 @@ int make_salt(char **ret) {
*ret = salt; *ret = salt;
return 0; return 0;
#else #else
/* If crypt_gensalt_ra() is not available, we use SHA512 and generate the salt on our own. */ /* If libxcrypt is not used, we use SHA512 and generate the salt on our own since crypt_gensalt_ra()
* is not available. */
static const char table[] = static const char table[] =
"abcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyz"
@ -69,8 +53,6 @@ int make_salt(char **ret) {
assert_cc(sizeof(table) == 64U + 1U); 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. */ /* Insist on the best randomness by setting RANDOM_BLOCK, this is about keeping passwords secret after all. */
r = genuine_random_bytes(raw, sizeof(raw), RANDOM_BLOCK); r = genuine_random_bytes(raw, sizeof(raw), RANDOM_BLOCK);
if (r < 0) if (r < 0)
@ -92,73 +74,6 @@ int make_salt(char **ret) {
#endif #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(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_full(const char *password, void **cd_data, int *cd_size, char **ret) {
_cleanup_free_ char *salt = NULL;
_cleanup_(erase_and_freep) void *_cd_data = NULL;
char *p;
int r, _cd_size = 0;
assert(!!cd_data == !!cd_size);
r = make_salt(&salt);
if (r < 0)
return log_debug_errno(r, "Failed to generate salt: %m");
errno = 0;
p = crypt_ra(password, salt, cd_data ?: &_cd_data, cd_size ?: &_cd_size);
if (!p)
return log_debug_errno(errno_or_else(SYNTHETIC_ERRNO(EINVAL)),
CRYPT_RA_NAME "() failed: %m");
p = strdup(p);
if (!p)
return -ENOMEM;
*ret = p;
return 0;
}
bool looks_like_hashed_password(const char *s) { bool looks_like_hashed_password(const char *s) {
/* Returns false if the specified string is certainly not a hashed UNIX password. crypt(5) lists /* Returns false if the specified string is certainly not a hashed UNIX password. crypt(5) lists
* various hashing methods. We only reject (return false) strings which are documented to have * various hashing methods. We only reject (return false) strings which are documented to have
@ -174,35 +89,3 @@ bool looks_like_hashed_password(const char *s) {
return !STR_IN_SET(s, "x", "*"); return !STR_IN_SET(s, "x", "*");
} }
int test_password_one(const char *hashed_password, const char *password) {
_cleanup_(erase_and_freep) void *cd_data = NULL;
int cd_size = 0;
const char *k;
errno = 0;
k = crypt_ra(password, hashed_password, &cd_data, &cd_size);
if (!k) {
if (errno == ENOMEM)
return -ENOMEM;
/* Unknown or unavailable hashing method or string too short */
return 0;
}
return streq(k, hashed_password);
}
int test_password_many(char **hashed_password, const char *password) {
char **hpw;
int r;
STRV_FOREACH(hpw, hashed_password) {
r = test_password_one(*hpw, password);
if (r < 0)
return r;
if (r > 0)
return true;
}
return false;
}

View File

@ -1,13 +1,22 @@
/* SPDX-License-Identifier: LGPL-2.1+ */ /* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once #pragma once
#if HAVE_CRYPT_H
/* libxcrypt is a replacement for glibc's libcrypt, and libcrypt might be
* removed from glibc at some point. As part of the removal, defines for
* crypt(3) are dropped from unistd.h, and we must include crypt.h instead.
*
* Newer versions of glibc (v2.0+) already ship crypt.h with a definition
* of crypt(3) as well, so we simply include it if it is present. MariaDB,
* MySQL, PostgreSQL, Perl and some other wide-spread packages do it the
* same way since ages without any problems.
*/
#include <crypt.h>
#endif
#include <stdbool.h> #include <stdbool.h>
#include <stdlib.h>
int make_salt(char **ret); int make_salt(char **ret);
int hash_password_full(const char *password, void **cd_data, int *cd_size, char **ret);
static inline int hash_password(const char *password, char **ret) {
return hash_password_full(password, NULL, NULL, ret);
}
bool looks_like_hashed_password(const char *s); bool looks_like_hashed_password(const char *s);
int test_password_one(const char *hashed_password, const char *password);
int test_password_many(char **hashed_password, const char *password);

View File

@ -301,10 +301,6 @@ tests += [
[], [],
[]], []],
[['src/test/test-libcrypt-util.c'],
[],
[]],
[['src/test/test-offline-passwd.c', [['src/test/test-offline-passwd.c',
'src/shared/offline-passwd.c', 'src/shared/offline-passwd.c',
'src/shared/offline-passwd.h'], 'src/shared/offline-passwd.h'],

View File

@ -1,102 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#if HAVE_CRYPT_H
# include <crypt.h>
#else
# include <unistd.h>
#endif
#include "strv.h"
#include "tests.h"
#include "libcrypt-util.h"
static int test_hash_password(void) {
log_info("/* %s */", __func__);
/* As a warmup exercise, check if we can hash passwords. */
bool have_sane_hash = false;
const char *hash;
FOREACH_STRING(hash,
"ew3bU1.hoKk4o",
"$1$gc5rWpTB$wK1aul1PyBn9AX1z93stk1",
"$2b$12$BlqcGkB/7BFvNMXKGxDea.5/8D6FTny.cbNcHW/tqcrcyo6ZJd8u2",
"$5$lGhDrcrao9zb5oIK$05KlOVG3ocknx/ThreqXE/gk.XzFFBMTksc4t2CPDUD",
"$6$c7wB/3GiRk0VHf7e$zXJ7hN0aLZapE.iO4mn/oHu6.prsXTUG/5k1AxpgR85ELolyAcaIGRgzfwJs3isTChMDBjnthZyaMCfCNxo9I.",
"$y$j9T$$9cKOWsAm4m97WiYk61lPPibZpy3oaGPIbsL4koRe/XD") {
int b;
b = test_password_one(hash, "ppp");
log_info("%s: %s", hash, yes_no(b));
#if defined(XCRYPT_VERSION_MAJOR)
/* xcrypt is supposed to always implement all methods. */
assert_se(b);
#endif
if (b && IN_SET(hash[1], '6', 'y'))
have_sane_hash = true;
}
return have_sane_hash;
}
static void test_hash_password_full(void) {
log_info("/* %s */", __func__);
_cleanup_free_ void *cd_data = NULL;
const char *i;
int cd_size = 0;
log_info("sizeof(struct crypt_data): %zu bytes", sizeof(struct crypt_data));
for (unsigned c = 0; c < 2; c++)
FOREACH_STRING(i, "abc123", "h⸿sło") {
_cleanup_free_ char *hashed;
if (c == 0)
assert_se(hash_password_full(i, &cd_data, &cd_size, &hashed) == 0);
else
assert_se(hash_password_full(i, NULL, NULL, &hashed) == 0);
log_debug("\"%s\"\"%s\"", i, hashed);
log_info("crypt_r[a] buffer size: %i bytes", cd_size);
assert_se(test_password_one(hashed, i) == true);
assert_se(test_password_one(i, hashed) <= 0); /* We get an error for non-utf8 */
assert_se(test_password_one(hashed, "foobar") == false);
assert_se(test_password_many(STRV_MAKE(hashed), i) == true);
assert_se(test_password_many(STRV_MAKE(hashed), "foobar") == false);
assert_se(test_password_many(STRV_MAKE(hashed, hashed, hashed), "foobar") == false);
assert_se(test_password_many(STRV_MAKE("$y$j9T$dlCXwkX0GC5L6B8Gf.4PN/$VCyEH",
hashed,
"$y$j9T$SAayASazWZIQeJd9AS02m/$"),
i) == true);
assert_se(test_password_many(STRV_MAKE("$W$j9T$dlCXwkX0GC5L6B8Gf.4PN/$VCyEH", /* no such method exists... */
hashed,
"$y$j9T$SAayASazWZIQeJd9AS02m/$"),
i) == true);
assert_se(test_password_many(STRV_MAKE("$y$j9T$dlCXwkX0GC5L6B8Gf.4PN/$VCyEH",
hashed,
"$y$j9T$SAayASazWZIQeJd9AS02m/$"),
"") == false);
assert_se(test_password_many(STRV_MAKE("$W$j9T$dlCXwkX0GC5L6B8Gf.4PN/$VCyEH", /* no such method exists... */
hashed,
"$y$j9T$SAayASazWZIQeJd9AS02m/$"),
"") == false);
}
}
int main(int argc, char *argv[]) {
test_setup_logging(LOG_DEBUG);
#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
if (!test_hash_password())
return log_tests_skipped("crypt doesn't support yescrypt or sha512crypt");
test_hash_password_full();
return 0;
}