]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
libmount: add support for verity devices via libcryptsetup
authorLuca Boccassi <luca.boccassi@microsoft.com>
Fri, 8 Nov 2019 17:02:09 +0000 (17:02 +0000)
committerLuca Boccassi <luca.boccassi@microsoft.com>
Thu, 5 Dec 2019 10:39:21 +0000 (10:39 +0000)
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.

14 files changed:
Makefile.am
configure.ac
libmount/docs/libmount-sections.txt
libmount/mount.pc.in
libmount/python/pylibmount.c
libmount/src/Makemodule.am
libmount/src/context.c
libmount/src/context_mount.c
libmount/src/context_veritydev.c [new file with mode: 0644]
libmount/src/init.c
libmount/src/libmount.h.in
libmount/src/mountP.h
libmount/src/optmap.c
sys-utils/mount.8

index 95e832cdc2871c263f36738cfe8242f7657ca708..8d94b081165c9aedb67ab20ad4b29d74c8cb3e16 100644 (file)
@@ -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)
 
index e8a03bf0096d9eac7b353891b25be54ef5df5560..34923e349d3708766160cbcce2b7e70eaa7b4c7c 100644 (file)
@@ -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],
index 990c0394f2b71adbb674141da02303754dd20070..52e61edc4e8a188519a55e6fc68911801e842df6 100644 (file)
@@ -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
 <SUBSECTION>
 MS_BIND
 MS_DIRSYNC
index d5f0d4b55c1cc1d8a8b423c298cac8527603126a..50e02df8e067e37ddaaf463d79cb5db12cea73c9 100644 (file)
@@ -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
index a2e12868488f60dbc8de26bdb1af835640db5cdc..e724edd14993d9c95fa2f61f40d8b0d59886b478 100644 (file)
@@ -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)
index a59b98b558a1d1cfacc5563ae9b2d18fbb6a9e40..a2b218d5acbd604bd3ca1a5e45c8621353902f1c 100644 (file)
@@ -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
index 977842f7d89af6fd1e406caed503ba58cd12eb2f..43a88c7c7919f167b79347bec9584abc4b05f6ef 100644 (file)
@@ -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;
index 7ee747e603c0005b10d60ecbe675c55364863976..ecfc78b144d642e36f74c53b90e00b6658b50cad 100644 (file)
@@ -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 (file)
index 0000000..fb5adde
--- /dev/null
@@ -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 <libcryptsetup.h>
+
+/* 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;
+}
index a22922229039b62ff98fc35799ccd897ab6918b2..2410fcc3a29ebd7adcf69ea19be0ba4dbca87084 100644 (file)
@@ -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 }
 };
index 19d4c5b53cd1fb5bdfe4ac0981db9e88828218ee..0198ecf3d78597006dcacc17394a8771c7a30760 100644 (file)
@@ -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)
index dd16f8c96c97e0403ed18bae1305b92046341f8a..9e7ad2b7145040df5abf19d73f08c13524aaad07 100644 (file)
@@ -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/<FOO> 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);
index 6136e27be180e41acf48ffb9349c17785a72f497..a8a6d7793d431fbb211176cb59acb694b99dee8b 100644 (file)
@@ -179,6 +179,10 @@ static const struct libmnt_optmap userspace_opts_map[] =
 
    { "helper=", MNT_MS_HELPER },                          /* /sbin/mount.<helper> */
 
+   { "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 }
 };
 
index 9d31d8245b3fe2e4758ebd9a59ff13e1f2e12649..d405db8bedd015ab44d9bce192669de76c17f1c5 100644 (file)
@@ -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