cryptsetup: add option to use via dlopen in libmount

Enabling libcrypsetup in libmount had several unintended side
effects.
First of all, it increases the Debian minimal image size by
~2.5% (5.6MB worth of new libraries).
Then, due to libcryptsetup linkage to OpenSSL and libjson-c,
it causes incompatibilities with external programs linking
against both libmount and a private, static, old version of
OpenSSL, or external programs linking against libjansson or
json-glib, which have one symbol in common with libjson-c.

If ./configure is ran with --with-crypsetup=dlopen,
instead of linking to libcrypsetup, use dlopen to resolve
the symbols at runtime only when the verity feature is
used, thus avoiding clashes and keeping images size down.

Fixes #1081

Signed-off-by: Luca Boccassi <luca.boccassi@microsoft.com>
This commit is contained in:
Luca Boccassi 2020-06-29 21:45:26 +01:00
parent 2b41c409e7
commit 488fd4c3df
6 changed files with 190 additions and 21 deletions

View File

@ -151,9 +151,16 @@ edit_cmd += -e 's|@LIBSELINUX[@]||g'
endif
if HAVE_CRYPTSETUP
if CRYPTSETUP_VIA_DLOPEN
edit_cmd += -e 's|@LIBCRYPTSETUP[@]||g'
edit_cmd += -e 's|@LIBDL[@]|-ldl|g'
else
edit_cmd += -e 's|@LIBCRYPTSETUP[@]|libcryptsetup|g'
edit_cmd += -e 's|@LIBDL[@]||g'
endif
else
edit_cmd += -e 's|@LIBCRYPTSETUP[@]||g'
edit_cmd += -e 's|@LIBDL[@]||g'
endif
if USE_VENDORDIR

View File

@ -2516,6 +2516,7 @@ AC_ARG_WITH([cryptsetup],
AS_IF([test "x$with_cryptsetup" = xno], [
AM_CONDITIONAL([HAVE_CRYPTSETUP], [false])
AM_CONDITIONAL([CRYPTSETUP_VIA_DLOPEN], [false])
], [
PKG_CHECK_MODULES([CRYPTSETUP], [libcryptsetup],
[AC_DEFINE([HAVE_CRYPTSETUP], [1], [Define if cryptsetup is available])
@ -2528,11 +2529,22 @@ AS_IF([test "x$with_cryptsetup" = xno], [
AC_CHECK_LIB([cryptsetup], [crypt_activate_by_signed_key], [
AC_DEFINE_UNQUOTED([HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY], [1], [Define if crypt_activate_by_signed_key exist in -lcryptsetup])
])
AS_IF([test "x$with_cryptsetup" = xdlopen], [
LIBS="-ldl $LIBS"
AC_CHECK_LIB([dl], [dlsym], [
AC_DEFINE([CRYPTSETUP_VIA_DLOPEN], [1], [Define if cryptsetup is to be loaded via dlopen])
AM_CONDITIONAL([CRYPTSETUP_VIA_DLOPEN], [true])
], [AC_MSG_ERROR([libdl required to build with cryptsetup support])])
], [
AM_CONDITIONAL([CRYPTSETUP_VIA_DLOPEN], [false])
])
CFLAGS="$SAVE_CFLAGS"
LIBS="$SAVE_LIBS"
have_cryptsetup=yes],
[have_cryptsetup=no
AM_CONDITIONAL([HAVE_CRYPTSETUP], [false])])
AM_CONDITIONAL([HAVE_CRYPTSETUP], [false])
AM_CONDITIONAL([CRYPTSETUP_VIA_DLOPEN], [false])
])
AS_CASE([$with_cryptsetup:$have_cryptsetup],
[yes:no], [AC_MSG_WARN([cryptsetup selected but libcryptsetup not found])]

View File

@ -20,3 +20,4 @@ Version: @LIBMOUNT_VERSION@
Requires.private: blkid @LIBSELINUX@ @LIBCRYPTSETUP@
Cflags: -I${includedir}/libmount
Libs: -L${libdir} -lmount
Libs.private: @LIBDL@

View File

@ -44,9 +44,16 @@ libmount_la_LIBADD = \
libcommon.la \
libblkid.la \
$(SELINUX_LIBS) \
$(CRYPTSETUP_LIBS) \
$(REALTIME_LIBS)
if HAVE_CRYPTSETUP
if CRYPTSETUP_VIA_DLOPEN
libmount_la_LIBADD += -ldl
else
libmount_la_LIBADD += $(CRYPTSETUP_LIBS)
endif
endif
libmount_la_CFLAGS = \
$(AM_CFLAGS) \
$(SOLIB_CFLAGS) \

View File

@ -14,9 +14,29 @@
#if defined(HAVE_CRYPTSETUP)
#ifdef CRYPTSETUP_VIA_DLOPEN
#include <dlfcn.h>
#endif
#include <libcryptsetup.h>
#include "path.h"
#ifdef CRYPTSETUP_VIA_DLOPEN
static void *get_symbol(struct libmnt_context *cxt, void *dl, const char *name, int *rc)
{
char *dl_error = NULL;
void *sym = dlsym(dl, name);
*rc = 0;
if ((dl_error = dlerror()) == NULL)
return sym;
DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected but cannot dlopen symbol %s: %s", name, dl_error));
*rc = -ENOTSUP;
return NULL;
}
#endif
/* Taken from https://gitlab.com/cryptsetup/cryptsetup/blob/master/lib/utils_crypt.c#L225 */
static size_t crypt_hex_to_bytes(const char *hex, char **result)
{
@ -58,6 +78,33 @@ int mnt_context_setup_veritydev(struct libmnt_context *cxt)
/* Use the same default for FEC parity bytes as cryptsetup uses */
uint64_t offset = 0, fec_offset = 0, fec_roots = 2;
struct stat hash_sig_st;
#ifdef CRYPTSETUP_VIA_DLOPEN
/* To avoid linking libmount to libcryptsetup, and keep the default dependencies list down, use dlopen */
void *dl = NULL;
int (*sym_crypt_init_data_device)(struct crypt_device **, const char *, const char *) = NULL;
int (*sym_crypt_load)(struct crypt_device *, const char *, void *) = NULL;
int (*sym_crypt_get_volume_key_size)(struct crypt_device *) = NULL;
#ifdef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
int (*sym_crypt_activate_by_signed_key)(struct crypt_device *, const char *, const char *, size_t, const char *, size_t, uint32_t) = NULL;
#endif
int (*sym_crypt_activate_by_volume_key)(struct crypt_device *, const char *, const char *, size_t, uint32_t) = NULL;
void (*sym_crypt_free)(struct crypt_device *) = NULL;
int (*sym_crypt_init_by_name)(struct crypt_device **, const char *) = NULL;
int (*sym_crypt_get_verity_info)(struct crypt_device *, struct crypt_params_verity *) = NULL;
int (*sym_crypt_volume_key_get)(struct crypt_device *, int, char *, size_t *, const char *, size_t) = NULL;
#else
int (*sym_crypt_init_data_device)(struct crypt_device **, const char *, const char *) = &crypt_init_data_device;
int (*sym_crypt_load)(struct crypt_device *, const char *, void *) = &crypt_load;
int (*sym_crypt_get_volume_key_size)(struct crypt_device *) = &crypt_get_volume_key_size;
#ifdef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
int (*sym_crypt_activate_by_signed_key)(struct crypt_device *, const char *, const char *, size_t, const char *, size_t, uint32_t) = &crypt_activate_by_signed_key;
#endif
int (*sym_crypt_activate_by_volume_key)(struct crypt_device *, const char *, const char *, size_t, uint32_t) = &crypt_activate_by_volume_key;
void (*sym_crypt_free)(struct crypt_device *) = &crypt_free;
int (*sym_crypt_init_by_name)(struct crypt_device **, const char *) = &crypt_init_by_name;
int (*sym_crypt_get_verity_info)(struct crypt_device *, struct crypt_params_verity *) = &crypt_get_verity_info;
int (*sym_crypt_volume_key_get)(struct crypt_device *, int, char *, size_t *, const char *, size_t) = &crypt_volume_key_get;
#endif
assert(cxt);
assert(cxt->fs);
@ -186,10 +233,61 @@ int mnt_context_setup_veritydev(struct libmnt_context *cxt)
rc = -EINVAL;
}
#ifdef CRYPTSETUP_VIA_DLOPEN
if (rc == 0) {
int dl_flags = RTLD_LAZY | RTLD_LOCAL;
/* glibc extension: mnt_context_deferred_delete_veritydev is called immediately after, don't unload on dl_close */
#ifdef RTLD_NODELETE
dl_flags |= RTLD_NODELETE;
#endif
/* glibc extension: might help to avoid further symbols clashes */
#ifdef RTLD_DEEPBIND
dl_flags |= RTLD_DEEPBIND;
#endif
dl = dlopen("libcryptsetup.so.12", dl_flags);
if (!dl) {
DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected but cannot dlopen libcryptsetup"));
rc = -ENOTSUP;
}
}
/* clear errors first, then load all the libcryptsetup symbols */
dlerror();
if (rc == 0)
*(void **)(&sym_crypt_init_data_device) = get_symbol(cxt, dl, "crypt_init_data_device", &rc);
if (rc == 0)
*(void **)(&sym_crypt_load) = get_symbol(cxt, dl, "crypt_load", &rc);
if (rc == 0)
*(void **)(&sym_crypt_get_volume_key_size) = get_symbol(cxt, dl, "crypt_get_volume_key_size", &rc);
#ifdef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
if (rc == 0)
*(void **)(&sym_crypt_activate_by_signed_key) = get_symbol(cxt, dl, "crypt_activate_by_signed_key", &rc);
#endif
if (rc == 0)
*(void **)(&sym_crypt_activate_by_volume_key) = get_symbol(cxt, dl, "crypt_activate_by_volume_key", &rc);
if (rc == 0)
*(void **)(&sym_crypt_free) = get_symbol(cxt, dl, "crypt_free", &rc);
if (rc == 0)
*(void **)(&sym_crypt_init_by_name) = get_symbol(cxt, dl, "crypt_init_by_name", &rc);
if (rc == 0)
*(void **)(&sym_crypt_get_verity_info) = get_symbol(cxt, dl, "crypt_get_verity_info", &rc);
if (rc == 0)
*(void **)(&sym_crypt_volume_key_get) = get_symbol(cxt, dl, "crypt_volume_key_get", &rc);
#endif
if (rc)
goto done;
rc = crypt_init_data_device(&crypt_dev, hash_device, backing_file);
rc = (*sym_crypt_init_data_device)(&crypt_dev, hash_device, backing_file);
if (rc)
goto done;
@ -199,11 +297,11 @@ int mnt_context_setup_veritydev(struct libmnt_context *cxt)
crypt_params.fec_roots = fec_roots;
crypt_params.fec_device = fec_device;
crypt_params.flags = 0;
rc = crypt_load(crypt_dev, CRYPT_VERITY, &crypt_params);
rc = (*sym_crypt_load)(crypt_dev, CRYPT_VERITY, &crypt_params);
if (rc < 0)
goto done;
hash_size = crypt_get_volume_key_size(crypt_dev);
hash_size = (*sym_crypt_get_volume_key_size)(crypt_dev);
if (crypt_hex_to_bytes(root_hash, &root_hash_binary) != hash_size) {
DBG(VERITY, ul_debugobj(cxt, "root hash %s is not of length %zu", root_hash, hash_size));
rc = -EINVAL;
@ -211,14 +309,14 @@ int mnt_context_setup_veritydev(struct libmnt_context *cxt)
}
if (hash_sig) {
#ifdef HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
rc = crypt_activate_by_signed_key(crypt_dev, mapper_device, root_hash_binary, hash_size,
rc = (*sym_crypt_activate_by_signed_key)(crypt_dev, mapper_device, root_hash_binary, hash_size,
hash_sig, hash_sig_size, CRYPT_ACTIVATE_READONLY);
#else
rc = -EINVAL;
DBG(VERITY, ul_debugobj(cxt, "verity.roothashsig=%s passed but libcryptsetup does not provide crypt_activate_by_signed_key()", hash_sig));
#endif
} else
rc = crypt_activate_by_volume_key(crypt_dev, mapper_device, root_hash_binary, hash_size,
rc = (*sym_crypt_activate_by_volume_key)(crypt_dev, mapper_device, root_hash_binary, hash_size,
CRYPT_ACTIVATE_READONLY);
/*
* If the mapper device already exists, and if libcryptsetup supports it, get the root
@ -232,10 +330,10 @@ int mnt_context_setup_veritydev(struct libmnt_context *cxt)
*/
if (rc == -EEXIST) {
DBG(VERITY, ul_debugobj(cxt, "%s already in use as /dev/mapper/%s", backing_file, mapper_device));
crypt_free(crypt_dev);
rc = crypt_init_by_name(&crypt_dev, mapper_device);
(*sym_crypt_free)(crypt_dev);
rc = (*sym_crypt_init_by_name)(&crypt_dev, mapper_device);
if (!rc) {
rc = crypt_get_verity_info(crypt_dev, &crypt_params);
rc = (*sym_crypt_get_verity_info)(crypt_dev, &crypt_params);
if (!rc) {
key = calloc(hash_size, 1);
if (!key) {
@ -245,7 +343,7 @@ int mnt_context_setup_veritydev(struct libmnt_context *cxt)
}
if (!rc) {
keysize = hash_size;
rc = crypt_volume_key_get(crypt_dev, CRYPT_ANY_SLOT, key, &keysize, NULL, 0);
rc = (*sym_crypt_volume_key_get)(crypt_dev, CRYPT_ANY_SLOT, key, &keysize, NULL, 0);
}
if (!rc) {
DBG(VERITY, ul_debugobj(cxt, "comparing root hash of existing device with %s", root_hash));
@ -290,7 +388,12 @@ int mnt_context_setup_veritydev(struct libmnt_context *cxt)
}
done:
crypt_free(crypt_dev);
if (sym_crypt_free)
(*sym_crypt_free)(crypt_dev);
#ifdef CRYPTSETUP_VIA_DLOPEN
if (dl)
dlclose(dl);
#endif
free(root_hash_binary);
free(mapper_device_full);
free(mapper_device);
@ -309,7 +412,22 @@ int mnt_context_deferred_delete_veritydev(struct libmnt_context *cxt)
struct crypt_device *crypt_dev = NULL;
/* If mounting failed delete immediately, otherwise setup auto cleanup for user umount */
uint32_t flags = mnt_context_get_status(cxt) ? CRYPT_DEACTIVATE_DEFERRED : 0;
int rc;
#ifdef CRYPTSETUP_VIA_DLOPEN
void *dl = NULL;
int dl_flags = RTLD_LAZY | RTLD_LOCAL;
/* glibc extension: might help to avoid further symbols clashes */
#ifdef RTLD_DEEPBIND
dl_flags |= RTLD_DEEPBIND;
#endif
int (*sym_crypt_init_by_name)(struct crypt_device **, const char *) = NULL;
int (*sym_crypt_deactivate_by_name)(struct crypt_device *, const char *, uint32_t) = NULL;
void (*sym_crypt_free)(struct crypt_device *) = NULL;
#else
int (*sym_crypt_init_by_name)(struct crypt_device **, const char *) = &crypt_init_by_name;
int (*sym_crypt_deactivate_by_name)(struct crypt_device *, const char *, uint32_t) = &crypt_deactivate_by_name;
void (*sym_crypt_free)(struct crypt_device *) = &crypt_free;
#endif
int rc = 0;
assert(cxt);
assert(cxt->fs);
@ -321,14 +439,38 @@ int mnt_context_deferred_delete_veritydev(struct libmnt_context *cxt)
if (!src)
return -EINVAL;
rc = crypt_init_by_name(&crypt_dev, src);
if (!rc) {
rc = crypt_deactivate_by_name(crypt_dev, src, flags);
if (!rc)
cxt->flags &= ~MNT_FL_VERITYDEV_READY;
#ifdef CRYPTSETUP_VIA_DLOPEN
dl = dlopen("libcryptsetup.so.12", dl_flags);
if (!dl) {
DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected but cannot dlopen libcryptsetup"));
return -ENOTSUP;
}
crypt_free(crypt_dev);
/* clear errors first */
dlerror();
if (!rc)
*(void **)(&sym_crypt_init_by_name) = get_symbol(cxt, dl, "crypt_init_by_name", &rc);
if (!rc)
*(void **)(&sym_crypt_deactivate_by_name) = get_symbol(cxt, dl, "crypt_deactivate_by_name", &rc);
if (!rc)
*(void **)(&sym_crypt_free) = get_symbol(cxt, dl, "crypt_free", &rc);
#endif
if (!rc) {
rc = (*sym_crypt_init_by_name)(&crypt_dev, src);
if (!rc) {
rc = (*sym_crypt_deactivate_by_name)(crypt_dev, src, flags);
if (!rc)
cxt->flags &= ~MNT_FL_VERITYDEV_READY;
}
(*sym_crypt_free)(crypt_dev);
}
#ifdef CRYPTSETUP_VIA_DLOPEN
dlclose(dl);
#endif
DBG(VERITY, ul_debugobj(cxt, "deleted [rc=%d]", rc));

View File

@ -2554,8 +2554,8 @@ checking of block devices using kernel crypto API. The
.B mount
command can open
the dm-verity device and do the integrity verification before on the device
filesystem is mounted. Requires libcryptsetup with in libmount. If
libcryptsetup supports extracting the root hash of an already mounted device,
filesystem is mounted. Requires libcryptsetup with in libmount (optionally via dlopen).
If libcryptsetup supports extracting the root hash of an already mounted device,
existing devices will be automatically reused in case of a match.
Mount options for dm-verity:
.TP