]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
cryptsetup: add option to use via dlopen in libmount
authorLuca Boccassi <luca.boccassi@microsoft.com>
Mon, 29 Jun 2020 20:45:26 +0000 (21:45 +0100)
committerLuca Boccassi <luca.boccassi@microsoft.com>
Tue, 7 Jul 2020 15:43:55 +0000 (16:43 +0100)
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>
Makefile.am
configure.ac
libmount/mount.pc.in
libmount/src/Makemodule.am
libmount/src/context_veritydev.c
sys-utils/mount.8

index f4f0ab44b364eeb8ee6603c614b69a3e09993ce4..429b6289a72e62ddfe661924cbc77359856308d0 100644 (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
index b40fcd0ff60f5e6af43e5948338eca9980bc2650..5a049d74edd9c57be964afa6644a38136799cb62 100644 (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])]
index 50e02df8e067e37ddaaf463d79cb5db12cea73c9..37ff59bf2ddaf0dfe964043e11d6d669fd22bba8 100644 (file)
@@ -20,3 +20,4 @@ Version: @LIBMOUNT_VERSION@
 Requires.private: blkid @LIBSELINUX@ @LIBCRYPTSETUP@
 Cflags: -I${includedir}/libmount
 Libs: -L${libdir} -lmount
+Libs.private: @LIBDL@
index a2b218d5acbd604bd3ca1a5e45c8621353902f1c..eaa69c135ed5a629df002eae351e38619913719a 100644 (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) \
index 47290479c597758352f51c78e1fd4c851a05d267..24a837b46ac6b3d42a5b3b26e4b2a8a5a01101ce 100644 (file)
 
 #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);
+#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));
 
index 76b96dad4220d46429a1e7cac1652eb1353fd90b..190335c239bf1cd7d5303bcfc4a93d52d5a2dd93 100644 (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