From: Luca Boccassi Date: Mon, 29 Jun 2020 20:45:26 +0000 (+0100) Subject: cryptsetup: add option to use via dlopen in libmount X-Git-Tag: v2.36-rc2~10^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=488fd4c3dfdc33db6dca91ebe95e1607f7d1ed12;p=thirdparty%2Futil-linux.git 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 --- diff --git a/Makefile.am b/Makefile.am index f4f0ab44b3..429b6289a7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 diff --git a/configure.ac b/configure.ac index b40fcd0ff6..5a049d74ed 100644 --- a/configure.ac +++ b/configure.ac @@ -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])] diff --git a/libmount/mount.pc.in b/libmount/mount.pc.in index 50e02df8e0..37ff59bf2d 100644 --- a/libmount/mount.pc.in +++ b/libmount/mount.pc.in @@ -20,3 +20,4 @@ Version: @LIBMOUNT_VERSION@ Requires.private: blkid @LIBSELINUX@ @LIBCRYPTSETUP@ Cflags: -I${includedir}/libmount Libs: -L${libdir} -lmount +Libs.private: @LIBDL@ diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am index a2b218d5ac..eaa69c135e 100644 --- a/libmount/src/Makemodule.am +++ b/libmount/src/Makemodule.am @@ -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) \ diff --git a/libmount/src/context_veritydev.c b/libmount/src/context_veritydev.c index 47290479c5..24a837b46a 100644 --- a/libmount/src/context_veritydev.c +++ b/libmount/src/context_veritydev.c @@ -14,9 +14,29 @@ #if defined(HAVE_CRYPTSETUP) +#ifdef CRYPTSETUP_VIA_DLOPEN +#include +#endif #include #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); +#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; + } + + /* 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 = crypt_deactivate_by_name(crypt_dev, src, flags); - if (!rc) - cxt->flags &= ~MNT_FL_VERITYDEV_READY; + 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); } - crypt_free(crypt_dev); +#ifdef CRYPTSETUP_VIA_DLOPEN + dlclose(dl); +#endif DBG(VERITY, ul_debugobj(cxt, "deleted [rc=%d]", rc)); diff --git a/sys-utils/mount.8 b/sys-utils/mount.8 index 76b96dad42..190335c239 100644 --- a/sys-utils/mount.8 +++ b/sys-utils/mount.8 @@ -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