Files
systemd/src/shared/cryptsetup-util.c
Lennart Poettering cd7c207795 tree-wide: add dlopen ELF notes to all dlopen() deps of ours
Use 'recommended' priority for the default compression library, to
indicate that it should be prioritized over the other ones, as it
will be used to compress journals/core files.
Also use 'recommended' for kmod, as systems will likely fail to boot
if it's missing from the initrd.
Use 'suggested' for everything else.

There is one dlopen'ed TPM library that has the name generated
at runtime (depending on the driver), so that cannot be added, as it
needs to be known at build time.
Also when we support multiple ABI versions list them all, as for the
same reason we cannot know which one will be used at build time.

$ dlopen-notes.py build/libsystemd.so.0.39.0 build/src/shared/libsystemd-shared-256.so
libarchive.so.13 suggested
libbpf.so.0 suggested
libbpf.so.1 suggested
libcryptsetup.so.12 suggested
libdw.so.1 suggested
libelf.so.1 suggested
libfido2.so.1 suggested
libgcrypt.so.20 suggested
libidn2.so.0 suggested
libip4tc.so.2 suggested
libkmod.so.2 recommended
liblz4.so.1 suggested
liblzma.so.5 suggested
libp11-kit.so.0 suggested
libpcre2-8.so.0 suggested
libpwquality.so.1 suggested
libqrencode.so.3 suggested
libqrencode.so.4 suggested
libtss2-esys.so.0 suggested
libtss2-mu.so.0 suggested
libtss2-rc.so.0 suggested
libzstd.so.1 recommended

Co-authored-by: Luca Boccassi <bluca@debian.org>
2024-05-08 11:07:36 +01:00

361 lines
13 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "alloc-util.h"
#include "cryptsetup-util.h"
#include "dlfcn-util.h"
#include "log.h"
#include "parse-util.h"
#if HAVE_LIBCRYPTSETUP
static void *cryptsetup_dl = NULL;
DLSYM_FUNCTION(crypt_activate_by_passphrase);
#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
DLSYM_FUNCTION(crypt_activate_by_signed_key);
#endif
DLSYM_FUNCTION(crypt_activate_by_volume_key);
DLSYM_FUNCTION(crypt_deactivate_by_name);
DLSYM_FUNCTION(crypt_format);
DLSYM_FUNCTION(crypt_free);
DLSYM_FUNCTION(crypt_get_cipher);
DLSYM_FUNCTION(crypt_get_cipher_mode);
DLSYM_FUNCTION(crypt_get_data_offset);
DLSYM_FUNCTION(crypt_get_device_name);
DLSYM_FUNCTION(crypt_get_dir);
DLSYM_FUNCTION(crypt_get_type);
DLSYM_FUNCTION(crypt_get_uuid);
DLSYM_FUNCTION(crypt_get_verity_info);
DLSYM_FUNCTION(crypt_get_volume_key_size);
DLSYM_FUNCTION(crypt_init);
DLSYM_FUNCTION(crypt_init_by_name);
DLSYM_FUNCTION(crypt_keyslot_add_by_volume_key);
DLSYM_FUNCTION(crypt_keyslot_destroy);
DLSYM_FUNCTION(crypt_keyslot_max);
DLSYM_FUNCTION(crypt_load);
DLSYM_FUNCTION(crypt_resize);
#if HAVE_CRYPT_RESUME_BY_VOLUME_KEY
DLSYM_FUNCTION(crypt_resume_by_volume_key);
#endif
DLSYM_FUNCTION(crypt_set_data_device);
DLSYM_FUNCTION(crypt_set_debug_level);
DLSYM_FUNCTION(crypt_set_log_callback);
#if HAVE_CRYPT_SET_METADATA_SIZE
DLSYM_FUNCTION(crypt_set_metadata_size);
#endif
DLSYM_FUNCTION(crypt_set_pbkdf_type);
DLSYM_FUNCTION(crypt_suspend);
DLSYM_FUNCTION(crypt_token_json_get);
DLSYM_FUNCTION(crypt_token_json_set);
#if HAVE_CRYPT_TOKEN_MAX
DLSYM_FUNCTION(crypt_token_max);
#endif
DLSYM_FUNCTION(crypt_token_status);
DLSYM_FUNCTION(crypt_volume_key_get);
#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE
DLSYM_FUNCTION(crypt_reencrypt_init_by_passphrase);
#endif
#if HAVE_CRYPT_REENCRYPT
DISABLE_WARNING_DEPRECATED_DECLARATIONS;
DLSYM_FUNCTION(crypt_reencrypt);
REENABLE_WARNING;
#endif
DLSYM_FUNCTION(crypt_metadata_locking);
#if HAVE_CRYPT_SET_DATA_OFFSET
DLSYM_FUNCTION(crypt_set_data_offset);
#endif
DLSYM_FUNCTION(crypt_header_restore);
DLSYM_FUNCTION(crypt_volume_key_keyring);
/* Unfortunately libcryptsetup provides neither an environment variable to redirect where to look for token
* modules, nor does it have an API to change the token lookup path at runtime. The maintainers suggest using
* ELF interposition instead (see https://gitlab.com/cryptsetup/cryptsetup/-/issues/846). Hence let's do
* that: let's interpose libcryptsetup's crypt_token_external_path() function with our own, that *does*
* honour an environment variable where to look for tokens. This is tremendously useful for debugging
* libcryptsetup tokens: set the environment variable to your build dir and you can easily test token modules
* without jumping through various hoops. */
/* Do this only on new enough compilers that actually support the "symver" attribute. Given this is a debug
* feature, let's simply not bother on older compilers */
#if BUILD_MODE_DEVELOPER && defined(__has_attribute) && __has_attribute(symver)
const char *my_crypt_token_external_path(void); /* prototype for our own implementation */
/* We use the "symver" attribute to mark this implementation as the default implementation, and drop the
* SD_SHARED namespace we by default attach to our symbols via a version script. */
__attribute__((symver("crypt_token_external_path@@")))
_public_ const char *my_crypt_token_external_path(void) {
const char *e;
e = secure_getenv("SYSTEMD_CRYPTSETUP_TOKEN_PATH");
if (e)
return e;
/* Now chain invoke the original implementation. */
if (cryptsetup_dl) {
typeof(crypt_token_external_path) *func;
func = (typeof(crypt_token_external_path)*) dlsym(cryptsetup_dl, "crypt_token_external_path");
if (func)
return func();
}
return NULL;
}
#endif
static void cryptsetup_log_glue(int level, const char *msg, void *usrptr) {
switch (level) {
case CRYPT_LOG_NORMAL:
level = LOG_NOTICE;
break;
case CRYPT_LOG_ERROR:
level = LOG_ERR;
break;
case CRYPT_LOG_VERBOSE:
level = LOG_INFO;
break;
case CRYPT_LOG_DEBUG:
level = LOG_DEBUG;
break;
default:
log_error("Unknown libcryptsetup log level: %d", level);
level = LOG_ERR;
}
log_full(level, "%s", msg);
}
void cryptsetup_enable_logging(struct crypt_device *cd) {
/* It's OK to call this with a NULL parameter, in which case libcryptsetup will set the default log
* function.
*
* Note that this is also called from dlopen_cryptsetup(), which we call here too. Sounds like an
* endless loop, but isn't because we break it via the check for 'cryptsetup_dl' early in
* dlopen_cryptsetup(). */
if (dlopen_cryptsetup() < 0)
return; /* If this fails, let's gracefully ignore the issue, this is just debug logging after
* all, and if this failed we already generated a debug log message that should help
* to track things down. */
sym_crypt_set_log_callback(cd, cryptsetup_log_glue, NULL);
sym_crypt_set_debug_level(DEBUG_LOGGING ? CRYPT_DEBUG_ALL : CRYPT_DEBUG_NONE);
}
int cryptsetup_set_minimal_pbkdf(struct crypt_device *cd) {
/* With CRYPT_PBKDF_NO_BENCHMARK flag set .time_ms member is ignored
* while .iterations must be set at least to recommended minimum value. */
static const struct crypt_pbkdf_type minimal_pbkdf = {
.hash = "sha512",
.type = CRYPT_KDF_PBKDF2,
.iterations = 1000, /* recommended minimum count for pbkdf2
* according to NIST SP 800-132, ch. 5.2 */
.flags = CRYPT_PBKDF_NO_BENCHMARK
};
int r;
/* Sets a minimal PKBDF in case we already have a high entropy key. */
r = dlopen_cryptsetup();
if (r < 0)
return r;
r = sym_crypt_set_pbkdf_type(cd, &minimal_pbkdf);
if (r < 0)
return r;
return 0;
}
int cryptsetup_get_token_as_json(
struct crypt_device *cd,
int idx,
const char *verify_type,
JsonVariant **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
const char *text;
int r;
assert(cd);
/* Extracts and parses the LUKS2 JSON token data from a LUKS2 device. Optionally verifies the type of
* the token. Returns:
*
* -EINVAL → token index out of range or "type" field missing
* -ENOENT → token doesn't exist
* -EMEDIUMTYPE → "verify_type" specified and doesn't match token's type
*/
r = dlopen_cryptsetup();
if (r < 0)
return r;
r = sym_crypt_token_json_get(cd, idx, &text);
if (r < 0)
return r;
r = json_parse(text, 0, &v, NULL, NULL);
if (r < 0)
return r;
if (verify_type) {
JsonVariant *w;
w = json_variant_by_key(v, "type");
if (!w)
return -EINVAL;
if (!streq_ptr(json_variant_string(w), verify_type))
return -EMEDIUMTYPE;
}
if (ret)
*ret = TAKE_PTR(v);
return 0;
}
int cryptsetup_add_token_json(struct crypt_device *cd, JsonVariant *v) {
_cleanup_free_ char *text = NULL;
int r;
r = dlopen_cryptsetup();
if (r < 0)
return r;
r = json_variant_format(v, 0, &text);
if (r < 0)
return log_debug_errno(r, "Failed to format token data for LUKS: %m");
log_debug("Adding token text <%s>", text);
r = sym_crypt_token_json_set(cd, CRYPT_ANY_TOKEN, text);
if (r < 0)
return log_debug_errno(r, "Failed to write token data to LUKS: %m");
return 0;
}
#endif
int dlopen_cryptsetup(void) {
#if HAVE_LIBCRYPTSETUP
int r;
/* libcryptsetup added crypt_reencrypt() in 2.2.0, and marked it obsolete in 2.4.0, replacing it with
* crypt_reencrypt_run(), which takes one extra argument but is otherwise identical. The old call is
* still available though, and given we want to support 2.2.0 for a while longer, we'll stick to the
* old symbol. However, the old symbols now has a GCC deprecation decorator, hence let's turn off
* warnings about this for now. */
DISABLE_WARNING_DEPRECATED_DECLARATIONS;
ELF_NOTE_DLOPEN("cryptsetup",
"Support for disk encryption, integrity, and authentication",
ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
"libcryptsetup.so.12");
r = dlopen_many_sym_or_warn(
&cryptsetup_dl, "libcryptsetup.so.12", LOG_DEBUG,
DLSYM_ARG(crypt_activate_by_passphrase),
#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
DLSYM_ARG(crypt_activate_by_signed_key),
#endif
DLSYM_ARG(crypt_activate_by_volume_key),
DLSYM_ARG(crypt_deactivate_by_name),
DLSYM_ARG(crypt_format),
DLSYM_ARG(crypt_free),
DLSYM_ARG(crypt_get_cipher),
DLSYM_ARG(crypt_get_cipher_mode),
DLSYM_ARG(crypt_get_data_offset),
DLSYM_ARG(crypt_get_device_name),
DLSYM_ARG(crypt_get_dir),
DLSYM_ARG(crypt_get_type),
DLSYM_ARG(crypt_get_uuid),
DLSYM_ARG(crypt_get_verity_info),
DLSYM_ARG(crypt_get_volume_key_size),
DLSYM_ARG(crypt_init),
DLSYM_ARG(crypt_init_by_name),
DLSYM_ARG(crypt_keyslot_add_by_volume_key),
DLSYM_ARG(crypt_keyslot_destroy),
DLSYM_ARG(crypt_keyslot_max),
DLSYM_ARG(crypt_load),
DLSYM_ARG(crypt_resize),
#if HAVE_CRYPT_RESUME_BY_VOLUME_KEY
DLSYM_ARG(crypt_resume_by_volume_key),
#endif
DLSYM_ARG(crypt_set_data_device),
DLSYM_ARG(crypt_set_debug_level),
DLSYM_ARG(crypt_set_log_callback),
#if HAVE_CRYPT_SET_METADATA_SIZE
DLSYM_ARG(crypt_set_metadata_size),
#endif
DLSYM_ARG(crypt_set_pbkdf_type),
DLSYM_ARG(crypt_suspend),
DLSYM_ARG(crypt_token_json_get),
DLSYM_ARG(crypt_token_json_set),
#if HAVE_CRYPT_TOKEN_MAX
DLSYM_ARG(crypt_token_max),
#endif
DLSYM_ARG(crypt_token_status),
DLSYM_ARG(crypt_volume_key_get),
#if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE
DLSYM_ARG(crypt_reencrypt_init_by_passphrase),
#endif
#if HAVE_CRYPT_REENCRYPT
DLSYM_ARG(crypt_reencrypt),
#endif
DLSYM_ARG(crypt_metadata_locking),
#if HAVE_CRYPT_SET_DATA_OFFSET
DLSYM_ARG(crypt_set_data_offset),
#endif
DLSYM_ARG(crypt_header_restore),
DLSYM_ARG(crypt_volume_key_keyring));
if (r <= 0)
return r;
REENABLE_WARNING;
/* Redirect the default logging calls of libcryptsetup to our own logging infra. (Note that
* libcryptsetup also maintains per-"struct crypt_device" log functions, which we'll also set
* whenever allocating a "struct crypt_device" context. Why set both? To be defensive: maybe some
* other code loaded into this process also changes the global log functions of libcryptsetup, who
* knows? And if so, we still want our own objects to log via our own infra, at the very least.) */
cryptsetup_enable_logging(NULL);
return 1;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "cryptsetup support is not compiled in.");
#endif
}
int cryptsetup_get_keyslot_from_token(JsonVariant *v) {
int keyslot, r;
JsonVariant *w;
/* Parses the "keyslots" field of a LUKS2 token object. The field can be an array, but here we assume
* that it contains a single element only, since that's the only way we ever generate it
* ourselves. */
w = json_variant_by_key(v, "keyslots");
if (!w)
return -ENOENT;
if (!json_variant_is_array(w) || json_variant_elements(w) != 1)
return -EMEDIUMTYPE;
w = json_variant_by_index(w, 0);
if (!w)
return -ENOENT;
if (!json_variant_is_string(w))
return -EMEDIUMTYPE;
r = safe_atoi(json_variant_string(w), &keyslot);
if (r < 0)
return r;
if (keyslot < 0)
return -EINVAL;
return keyslot;
}