From: Luca Boccassi Date: Fri, 8 Nov 2019 17:02:09 +0000 (+0000) Subject: libmount: add support for verity devices via libcryptsetup X-Git-Tag: v2.35-rc1~28^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e6a498877cf83fddd837b8f22c936f4f9b61b2d7;p=thirdparty%2Futil-linux.git libmount: add support for verity devices via libcryptsetup The following new options are added: verity.hashdevice verity.roothash verity.hashoffset The source path will be used as a dm-verity object, and will be opened using libcryptsetup APIs. A new --with-cryptsetup build-time option is added, which adds a dependency on libcryptsetup. To ease bootstrapping, given libcryptsetup build-depends on util-linux for libuuid, if --with-cryptsetup=yes but libcryptsetup is not installed only a warning will be printed at configure time rather than an error. This way stage0/first stage/ring0 builds can use the same configure options but avoid installing cryptsetup to get a working base set, and then rebuild util-linux in the next step of the boostrapping process. If verity options are selected but cannot be fullfilled due to lack of dependencies, mounting a volume will fail even if using a loop device would work as a fallback, to avoid silently skipping integrity checks. --- diff --git a/Makefile.am b/Makefile.am index 95e832cdc2..8d94b08116 100644 --- a/Makefile.am +++ b/Makefile.am @@ -144,6 +144,12 @@ else edit_cmd += -e 's|@LIBSELINUX[@]||g' endif +if HAVE_CRYPTSETUP +edit_cmd += -e 's|@LIBCRYPTSETUP[@]|libcryptsetup|g' +else +edit_cmd += -e 's|@LIBCRYPTSETUP[@]||g' +endif + CLEANFILES += $(PATHFILES) EXTRA_DIST += $(PATHFILES:=.in) diff --git a/configure.ac b/configure.ac index e8a03bf009..34923e349d 100644 --- a/configure.ac +++ b/configure.ac @@ -2426,6 +2426,30 @@ AS_IF([test "x$enable_colors_default" = xyes], [ ]) +AC_ARG_WITH([cryptsetup], + AS_HELP_STRING([--with-cryptsetup], [compile with cryptsetup support]), + [], [with_cryptsetup=no] +) + +AS_IF([test "x$with_cryptsetup" = xno], [ + AM_CONDITIONAL([HAVE_CRYPTSETUP], [false]) +], [ + PKG_CHECK_MODULES([CRYPTSETUP], [libcryptsetup], + [AC_DEFINE([HAVE_CRYPTSETUP], [1], [Define if cryptsetup is available]) + UL_PKG_STATIC([CRYPTSETUP_LIBS_STATIC], [libcryptsetup]) + AM_CONDITIONAL([HAVE_CRYPTSETUP], [true]) + have_cryptsetup=yes], + [have_cryptsetup=no + AM_CONDITIONAL([HAVE_CRYPTSETUP], [false])]) + + AS_CASE([$with_cryptsetup:$have_cryptsetup], + [yes:no], [AC_MSG_WARN([cryptsetup selected but libcryptsetup not found])] + ) +]) +AC_SUBST([CRYPTSETUP_LIBS]) +AC_SUBST([CRYPTSETUP_LIBS_STATIC]) + + AC_ARG_VAR([SUID_CFLAGS], [CFLAGS used for binaries which are usually with the suid bit]) AC_ARG_VAR([SUID_LDFLAGS], diff --git a/libmount/docs/libmount-sections.txt b/libmount/docs/libmount-sections.txt index 990c0394f2..52e61edc4e 100644 --- a/libmount/docs/libmount-sections.txt +++ b/libmount/docs/libmount-sections.txt @@ -156,6 +156,9 @@ MNT_MS_USER MNT_MS_USERS MNT_MS_XCOMMENT MNT_MS_XFSTABCOMM +MNT_MS_HASH_DEVICE +MNT_MS_ROOT_HASH +MNT_MS_HASH_OFFSET MS_BIND MS_DIRSYNC diff --git a/libmount/mount.pc.in b/libmount/mount.pc.in index d5f0d4b55c..50e02df8e0 100644 --- a/libmount/mount.pc.in +++ b/libmount/mount.pc.in @@ -17,6 +17,6 @@ includedir=@includedir@ Name: mount Description: mount library Version: @LIBMOUNT_VERSION@ -Requires.private: blkid @LIBSELINUX@ +Requires.private: blkid @LIBSELINUX@ @LIBCRYPTSETUP@ Cflags: -I${includedir}/libmount Libs: -L${libdir} -lmount diff --git a/libmount/python/pylibmount.c b/libmount/python/pylibmount.c index a2e1286848..e724edd149 100644 --- a/libmount/python/pylibmount.c +++ b/libmount/python/pylibmount.c @@ -251,6 +251,9 @@ PyMODINIT_FUNC initpylibmount(void) PyModule_AddIntConstant(m, "MNT_MS_USER", MNT_MS_USER); PyModule_AddIntConstant(m, "MNT_MS_USERS", MNT_MS_USERS); PyModule_AddIntConstant(m, "MNT_MS_XCOMMENT", MNT_MS_XCOMMENT); + PyModule_AddIntConstant(m, "MNT_MS_HASH_DEVICE", MNT_MS_HASH_DEVICE); + PyModule_AddIntConstant(m, "MNT_MS_ROOT_HASH", MNT_MS_ROOT_HASH); + PyModule_AddIntConstant(m, "MNT_MS_HASH_OFFSET", MNT_MS_HASH_OFFSET); /* * mount(2) MS_* masks (MNT_MAP_LINUX map) diff --git a/libmount/src/Makemodule.am b/libmount/src/Makemodule.am index a59b98b558..a2b218d5ac 100644 --- a/libmount/src/Makemodule.am +++ b/libmount/src/Makemodule.am @@ -28,6 +28,7 @@ if LINUX libmount_la_SOURCES += \ libmount/src/context.c \ libmount/src/context_loopdev.c \ + libmount/src/context_veritydev.c \ libmount/src/context_mount.c \ libmount/src/context_umount.c \ libmount/src/monitor.c @@ -43,11 +44,13 @@ libmount_la_LIBADD = \ libcommon.la \ libblkid.la \ $(SELINUX_LIBS) \ + $(CRYPTSETUP_LIBS) \ $(REALTIME_LIBS) libmount_la_CFLAGS = \ $(AM_CFLAGS) \ $(SOLIB_CFLAGS) \ + $(CRYPTSETUP_CFLAGS) \ -I$(ul_libblkid_incdir) \ -I$(ul_libmount_incdir) \ -I$(top_srcdir)/libmount/src diff --git a/libmount/src/context.c b/libmount/src/context.c index 977842f7d8..43a88c7c79 100644 --- a/libmount/src/context.c +++ b/libmount/src/context.c @@ -1801,10 +1801,21 @@ int mnt_context_prepare_srcpath(struct libmnt_context *cxt) goto end; } + /* - * Initialize loop device + * Initialize verity or loop device + * ENOTSUP means verity options were requested, but the library is built without + * libcryptsetup so integrity cannot be enforced, and this should be an error + * rather than a silent fallback to a simple loopdev mount */ - if (mnt_context_is_loopdev(cxt)) { + rc = mnt_context_is_veritydev(cxt); + if (rc == -ENOTSUP) { + goto end; + } else if (rc) { + rc = mnt_context_setup_veritydev(cxt); + if (rc) + goto end; + } else if (mnt_context_is_loopdev(cxt)) { rc = mnt_context_setup_loopdev(cxt); if (rc) goto end; diff --git a/libmount/src/context_mount.c b/libmount/src/context_mount.c index 7ee747e603..ecfc78b144 100644 --- a/libmount/src/context_mount.c +++ b/libmount/src/context_mount.c @@ -1108,6 +1108,11 @@ int mnt_context_do_mount(struct libmnt_context *cxt) } } #endif + + /* Cleanup will be immediate on failure, and deferred to umount on success */ + if (mnt_context_is_veritydev(cxt)) + mnt_context_deferred_delete_veritydev(cxt); + if (!mnt_context_switch_ns(cxt, ns_old)) return -MNT_ERR_NAMESPACE; diff --git a/libmount/src/context_veritydev.c b/libmount/src/context_veritydev.c new file mode 100644 index 0000000000..fb5adde211 --- /dev/null +++ b/libmount/src/context_veritydev.c @@ -0,0 +1,289 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * This file is part of libmount from util-linux project. + * + * Copyright (C) 2019 Microsoft Corporation + * + * libmount is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ + +#include "mountP.h" + +#if defined(HAVE_CRYPTSETUP) + +#include + +/* 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) +{ + char buf[3] = "xx\0", *endp, *bytes; + size_t i, len; + + len = strlen(hex); + if (len % 2) + return -EINVAL; + len /= 2; + + bytes = malloc(len); + if (!bytes) + return -ENOMEM; + + for (i = 0; i < len; i++) { + memcpy(buf, &hex[i * 2], 2); + bytes[i] = strtoul(buf, &endp, 16); + if (endp != &buf[2]) { + free(bytes); + return -EINVAL; + } + } + *result = bytes; + return i; +} + + +int mnt_context_setup_veritydev(struct libmnt_context *cxt) +{ + const char *backing_file, *optstr; + char *val = NULL, *key = NULL, *root_hash_binary = NULL, *mapper_device = NULL, + *mapper_device_full = NULL, *backing_file_basename = NULL, *root_hash = NULL, + *hash_device = NULL; + size_t len, hash_size, keysize = 0; + struct crypt_params_verity crypt_params = {}; + struct crypt_device *crypt_dev = NULL; + int rc = 0; + uint64_t offset = 0; + + assert(cxt); + assert(cxt->fs); + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + /* dm-verity volumes are read-only, and mount will fail if not set */ + mnt_context_set_mflags(cxt, (cxt->mountflags | MS_RDONLY)); + + backing_file = mnt_fs_get_srcpath(cxt->fs); + if (!backing_file) + return -EINVAL; + + /* To avoid clashes, prefix libmnt_ to all mapper devices */ + backing_file_basename = basename(backing_file); + mapper_device = calloc(strlen(backing_file_basename) + strlen("libmnt_") + 1, sizeof(char)); + if (!mapper_device) + return -ENOMEM; + strcat(mapper_device, "libmnt_"); + strcat(mapper_device, backing_file_basename); + + DBG(VERITY, ul_debugobj(cxt, "trying to setup verity device for %s", backing_file)); + + optstr = mnt_fs_get_user_options(cxt->fs); + + /* + * verity.hashdevice= + */ + if (rc == 0 && (cxt->user_mountflags & MNT_MS_HASH_DEVICE) && + mnt_optstr_get_option(optstr, "verity.hashdevice", &val, &len) == 0 && val) { + hash_device = strndup(val, len); + rc = hash_device ? 0 : -ENOMEM; + } + + /* + * verity.roothash= + */ + if (rc == 0 && (cxt->user_mountflags & MNT_MS_ROOT_HASH) && + mnt_optstr_get_option(optstr, "verity.roothash", &val, &len) == 0 && val) { + root_hash = strndup(val, len); + rc = root_hash ? 0 : -ENOMEM; + } + + /* + * verity.hashoffset= + */ + if (rc == 0 && (cxt->user_mountflags & MNT_MS_HASH_OFFSET) && + mnt_optstr_get_option(optstr, "verity.hashoffset", &val, &len) == 0) { + rc = mnt_parse_offset(val, len, &offset); + if (rc) { + DBG(VERITY, ul_debugobj(cxt, "failed to parse verity.hashoffset=")); + rc = -MNT_ERR_MOUNTOPT; + } + } + + if (rc) + goto done; + + rc = crypt_init_data_device(&crypt_dev, hash_device, backing_file); + if (rc) + goto done; + + memset(&crypt_params, 0, sizeof(struct crypt_params_verity)); + crypt_params.hash_area_offset = offset; + crypt_params.fec_area_offset = 0; + crypt_params.fec_roots = 0; + crypt_params.fec_device = NULL; + crypt_params.flags = 0; + rc = crypt_load(crypt_dev, CRYPT_VERITY, &crypt_params); + if (rc < 0) + goto done; + + hash_size = 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; + goto done; + } + rc = 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 + * hash associated with the existing one and compare it with the parameter passed by + * the user. If they match, then we can be sure the user intended to mount the exact + * same device, and simply reuse it and return success. + * The kernel does the refcounting for us. + * If libcryptsetup does not support getting the root hash out of an existing device, + * then return an error and tell the user that the device is already in use. + * Pass through only OOM errors or mismatching root hash errors. + */ + 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); + if (!rc) { + rc = crypt_get_verity_info(crypt_dev, &crypt_params); + if (!rc) { + key = calloc(hash_size, 1); + if (!key) { + rc = -ENOMEM; + goto done; + } + } + if (!rc) { + keysize = hash_size; + rc = 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)); + if (memcmp(key, root_hash_binary, hash_size)) { + DBG(VERITY, ul_debugobj(cxt, "existing device's hash does not match with %s", root_hash)); + rc = -EINVAL; + goto done; + } + } else { + DBG(VERITY, ul_debugobj(cxt, "libcryptsetup does not support extracting root hash of existing device")); + } + } + if (rc) { + rc = -EEXIST; + } else { + DBG(VERITY, ul_debugobj(cxt, "root hash of %s matches %s, reusing device", mapper_device, root_hash)); + } + } + + if (!rc) { + cxt->flags |= MNT_FL_VERITYDEV_READY; + mapper_device_full = calloc(strlen(mapper_device) + strlen("/dev/mapper/") + 1, sizeof(char)); + if (!mapper_device_full) + rc = -ENOMEM; + else { + strcat(mapper_device_full, "/dev/mapper/"); + strcat(mapper_device_full, mapper_device); + rc = mnt_fs_set_source(cxt->fs, mapper_device_full); + } + } + +done: + crypt_free(crypt_dev); + free(root_hash_binary); + free(mapper_device_full); + free(mapper_device); + free(hash_device); + free(root_hash); + free(key); + return rc; +} + +int mnt_context_deferred_delete_veritydev(struct libmnt_context *cxt) +{ + const char *src; + 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; + + assert(cxt); + assert(cxt->fs); + + if (!(cxt->flags & MNT_FL_VERITYDEV_READY)) + return 0; + + src = mnt_fs_get_srcpath(cxt->fs); + 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; + } + + crypt_free(crypt_dev); + + DBG(VERITY, ul_debugobj(cxt, "deleted [rc=%d]", rc)); + + return rc; +} + +#else + +int mnt_context_setup_veritydev(struct libmnt_context *cxt __attribute__ ((__unused__))) +{ + return 0; +} + +int mnt_context_deferred_delete_veritydev(struct libmnt_context *cxt __attribute__ ((__unused__))) +{ + return 0; +} +#endif + +int mnt_context_is_veritydev(struct libmnt_context *cxt) +{ + const char *src; + + assert(cxt); + + /* The mount flags have to be merged, otherwise we have to use + * expensive mnt_context_get_user_mflags() instead of cxt->user_mountflags. */ + assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED)); + + if (!cxt->fs) + return 0; + src = mnt_fs_get_srcpath(cxt->fs); + if (!src) + return 0; /* backing file not set */ + + if (cxt->user_mountflags & (MNT_MS_HASH_DEVICE | + MNT_MS_ROOT_HASH | + MNT_MS_HASH_OFFSET)) { +#ifndef HAVE_CRYPTSETUP + DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected but libmount built without libcryptsetup")); + return -ENOTSUP; +#else + DBG(VERITY, ul_debugobj(cxt, "veritydev specific options detected")); + return 1; +#endif + } + + if (!strncmp(src, "/dev/mapper/libmnt_", strlen("/dev/mapper/libmnt_"))) { +#ifndef HAVE_CRYPTSETUP + DBG(VERITY, ul_debugobj(cxt, "veritydev prefix detected in source device but libmount built without libcryptsetup")); + return -ENOTSUP; +#else + DBG(VERITY, ul_debugobj(cxt, "veritydev prefix detected in source device")); + return 1; +#endif + } + + return 0; +} diff --git a/libmount/src/init.c b/libmount/src/init.c index a229222290..2410fcc3a2 100644 --- a/libmount/src/init.c +++ b/libmount/src/init.c @@ -37,6 +37,7 @@ UL_DEBUG_DEFINE_MASKNAMES(libmount) = { "utils", MNT_DEBUG_UTILS, "misc library utils" }, { "monitor", MNT_DEBUG_MONITOR, "mount tables monitor" }, { "btrfs", MNT_DEBUG_BTRFS, "btrfs specific routines" }, + { "verity", MNT_DEBUG_VERITY, "verity specific routines" }, { NULL, 0 } }; diff --git a/libmount/src/libmount.h.in b/libmount/src/libmount.h.in index 19d4c5b53c..0198ecf3d7 100644 --- a/libmount/src/libmount.h.in +++ b/libmount/src/libmount.h.in @@ -904,6 +904,9 @@ extern int mnt_context_set_syscall_status(struct libmnt_context *cxt, int status #define MNT_MS_SIZELIMIT (1 << 15) #define MNT_MS_ENCRYPTION (1 << 16) #define MNT_MS_XFSTABCOMM (1 << 17) +#define MNT_MS_HASH_DEVICE (1 << 18) +#define MNT_MS_ROOT_HASH (1 << 19) +#define MNT_MS_HASH_OFFSET (1 << 20) /* * mount(2) MS_* masks (MNT_MAP_LINUX map) diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h index dd16f8c96c..9e7ad2b714 100644 --- a/libmount/src/mountP.h +++ b/libmount/src/mountP.h @@ -46,6 +46,7 @@ #define MNT_DEBUG_MONITOR (1 << 11) #define MNT_DEBUG_BTRFS (1 << 12) #define MNT_DEBUG_LOOP (1 << 13) +#define MNT_DEBUG_VERITY (1 << 14) #define MNT_DEBUG_ALL 0xFFFF @@ -379,6 +380,7 @@ struct libmnt_context #define MNT_FL_MOUNTOPTS_FIXED (1 << 27) #define MNT_FL_TABPATHS_CHECKED (1 << 28) #define MNT_FL_FORCED_RDONLY (1 << 29) /* mounted read-only on write-protected device */ +#define MNT_FL_VERITYDEV_READY (1 << 30) /* /dev/mapper/ initialized by the library */ /* default flags */ #define MNT_FL_DEFAULT 0 @@ -463,6 +465,11 @@ extern int mnt_context_save_template(struct libmnt_context *cxt); extern int mnt_context_apply_fs(struct libmnt_context *cxt, struct libmnt_fs *fs); +extern int mnt_context_is_veritydev(struct libmnt_context *cxt) + __attribute__((nonnull)); +extern int mnt_context_setup_veritydev(struct libmnt_context *cxt); +extern int mnt_context_deferred_delete_veritydev(struct libmnt_context *cxt); + /* tab_update.c */ extern int mnt_update_set_filename(struct libmnt_update *upd, const char *filename, int userspace_only); diff --git a/libmount/src/optmap.c b/libmount/src/optmap.c index 6136e27be1..a8a6d7793d 100644 --- a/libmount/src/optmap.c +++ b/libmount/src/optmap.c @@ -179,6 +179,10 @@ static const struct libmnt_optmap userspace_opts_map[] = { "helper=", MNT_MS_HELPER }, /* /sbin/mount. */ + { "verity.hashdevice=", MNT_MS_HASH_DEVICE, MNT_NOHLPS | MNT_NOMTAB }, /* mount a verity device */ + { "verity.roothash=", MNT_MS_ROOT_HASH, MNT_NOHLPS | MNT_NOMTAB }, /* verity device root hash */ + { "verity.hashoffset=", MNT_MS_HASH_OFFSET, MNT_NOHLPS | MNT_NOMTAB }, /* verity device hash offset */ + { NULL, 0, 0 } }; diff --git a/sys-utils/mount.8 b/sys-utils/mount.8 index 9d31d8245b..d405db8bed 100644 --- a/sys-utils/mount.8 +++ b/sys-utils/mount.8 @@ -2367,6 +2367,24 @@ Set the owner and group and mode of the file .I devices (default: uid=gid=0, mode=0444). The mode is given in octal. +.SS "Mount options for dm-verity"" +Mounting volumes using dm-verity for integrity verification is supported where appropriate +using the following options. Requires libcryptsetup. +If libcryptsetup supports extracting the root hash of an already mounted device, existing +devices will be automatically reused in case of a match. +.TP +\fBverity.hashdevice=\fP\,\fIpath\fP +Path to the hash tree device associated with the source volume to pass to dm-verity. +.TP +\fBverity.roothash=\fP\,\fIhex\fP +Hex-encoded hash of the root of +.I verity.hashdevice +.TP +\fBverity.hashoffset=\fP\,\fIoffset\fP +If the hash tree device is embedded in the source volume, +.I offset +(default: 0) is used by dm-verity to get to the tree. + .SH "THE LOOP DEVICE" One further possible type is a mount via the loop device. For example, the command