From 8c1be37e5b438bfdb640cfd39700bf074c66820c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 1 Dec 2016 20:25:26 +0100 Subject: util-lib: split out image dissecting code and loopback code from nspawn This adds two new APIs to systemd: - loop-util.h is a simple internal API for allocating, setting up and releasing loopback block devices. - dissect-image.h is an internal API for taking apart disk images and figuring out what the purpose of each partition is. Both APIs are basically refactored versions of similar code in nspawn. This rework should permit us to reuse this in other places than just nspawn in the future. Specifically: to implement RootImage= in the service image, similar to RootDirectory=, but operating on a disk image; to unify the gpt-auto-discovery generator code with the discovery logic in nspawn; to add new API to machined for determining the OS version of a disk image (i.e. not just running containers). This PR does not make any such changes however, it just provides the new reworked API. The reworked code is also slightly more powerful than the nspawn original one. When pointing it to an image or block device with a naked file system (i.e. no partition table) it will simply make it the root device. --- src/shared/dissect-image.h | 66 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/shared/dissect-image.h (limited to 'src/shared/dissect-image.h') diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h new file mode 100644 index 0000000000..04b19e8553 --- /dev/null +++ b/src/shared/dissect-image.h @@ -0,0 +1,66 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +#include "macro.h" + +typedef struct DissectedImage DissectedImage; +typedef struct DissectedPartition DissectedPartition; + +struct DissectedPartition { + bool found:1; + bool rw:1; + int partno; /* -1 if there was no partition and the images contains a file system directly */ + int architecture; /* Intended architecture: either native, secondary or unset (-1). */ + char *fstype; + char *node; +}; + +enum { + PARTITION_ROOT, + PARTITION_ROOT_SECONDARY, /* Secondary architecture */ + PARTITION_HOME, + PARTITION_SRV, + PARTITION_ESP, + PARTITION_SWAP, + _PARTITION_DESIGNATOR_MAX, + _PARTITION_DESIGNATOR_INVALID = -1 +}; + +typedef enum DissectedImageMountFlags { + DISSECTED_IMAGE_READ_ONLY = 1, + DISSECTED_IMAGE_DISCARD_ON_LOOP = 2, /* Turn on "discard" if on loop device and file system supports it */ +} DissectedImageMountFlags; + +struct DissectedImage { + DissectedPartition partitions[_PARTITION_DESIGNATOR_MAX]; +}; + +int dissect_image(int fd, DissectedImage **ret); + +DissectedImage* dissected_image_unref(DissectedImage *m); +DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref); + +int dissected_image_mount(DissectedImage *m, const char *dest, DissectedImageMountFlags flags); + +const char* partition_designator_to_string(int i) _const_; +int partition_designator_from_string(const char *name) _pure_; -- cgit v1.2.3-54-g00ecf From 18b5886e562a3702ed8923e568a7555d2ab1880a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 5 Dec 2016 16:26:48 +0100 Subject: dissect: add support for encrypted images This adds support to the image dissector to deal with encrypted images (only LUKS). Given that we now have a neatly isolated image dissector codebase, let's add a new feature to it: support for automatically dealing with encrypted images. This is then exposed in systemd-dissect and nspawn. It's pretty basic: only support for passphrase-based encryption. In order to ensure that "systemd-dissect --mount" results in mount points whose backing LUKS DM devices are cleaned up automatically we use the DM_DEV_REMOVE ioctl() directly on the device (in DM_DEFERRED_REMOVE mode). libgcryptsetup at the moment doesn't provide a proper API for this. Thankfully, the ioctl() API is pretty easy to use. --- Makefile.am | 10 +- src/dissect/dissect.c | 46 ++++- src/machine/image-dbus.c | 2 +- src/nspawn/nspawn.c | 9 +- src/shared/dissect-image.c | 457 +++++++++++++++++++++++++++++++++++++++------ src/shared/dissect-image.h | 22 ++- 6 files changed, 467 insertions(+), 79 deletions(-) (limited to 'src/shared/dissect-image.h') diff --git a/Makefile.am b/Makefile.am index c6adf3a65c..1895e33e05 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1086,7 +1086,8 @@ libshared_la_CFLAGS = \ $(ACL_CFLAGS) \ $(LIBIDN_CFLAGS) \ $(SECCOMP_CFLAGS) \ - $(BLKID_CFLAGS) + $(BLKID_CFLAGS) \ + $(LIBCRYPTSETUP_CFLAGS) libshared_la_LIBADD = \ libsystemd-internal.la \ @@ -1096,7 +1097,8 @@ libshared_la_LIBADD = \ $(ACL_LIBS) \ $(LIBIDN_LIBS) \ $(SECCOMP_LIBS) \ - $(BLKID_LIBS) + $(BLKID_LIBS) \ + $(LIBCRYPTSETUP_LIBS) rootlibexec_LTLIBRARIES += \ libsystemd-shared.la @@ -1119,6 +1121,7 @@ libsystemd_shared_la_CFLAGS = \ $(LIBIDN_CFLAGS) \ $(SECCOMP_CFLAGS) \ $(BLKID_CFLAGS) \ + $(LIBCRYPTSETUP_CFLAGS) \ -fvisibility=default # We can't use libshared_la_LIBADD here because it would @@ -1131,7 +1134,8 @@ libsystemd_shared_la_LIBADD = \ $(ACL_LIBS) \ $(LIBIDN_LIBS) \ $(SECCOMP_LIBS) \ - $(BLKID_LIBS) + $(BLKID_LIBS) \ + $(LIBCRYPTSETUP_LIBS) libsystemd_shared_la_LDFLAGS = \ $(AM_LDFLAGS) \ diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 93ece05948..5e6848acb4 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -34,7 +34,7 @@ static enum { } arg_action = ACTION_DISSECT; static const char *arg_image = NULL; static const char *arg_path = NULL; -static bool arg_read_only = false; +static DissectImageFlags arg_flags = DISSECT_IMAGE_DISCARD_ON_LOOP; static void help(void) { printf("%s [OPTIONS...] IMAGE\n" @@ -43,7 +43,8 @@ static void help(void) { " -h --help Show this help\n" " --version Show package version\n" " -m --mount Mount the image to the specified directory\n" - " -r --read-only Mount read-only\n", + " -r --read-only Mount read-only\n" + " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n", program_invocation_short_name, program_invocation_short_name); } @@ -52,6 +53,7 @@ static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, + ARG_DISCARD, }; static const struct option options[] = { @@ -59,6 +61,7 @@ static int parse_argv(int argc, char *argv[]) { { "version", no_argument, NULL, ARG_VERSION }, { "mount", no_argument, NULL, 'm' }, { "read-only", no_argument, NULL, 'r' }, + { "discard", required_argument, NULL, ARG_DISCARD }, {} }; @@ -83,7 +86,23 @@ static int parse_argv(int argc, char *argv[]) { break; case 'r': - arg_read_only = true; + arg_flags |= DISSECT_IMAGE_READ_ONLY; + break; + + case ARG_DISCARD: + if (streq(optarg, "disabled")) + arg_flags &= ~(DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_DISCARD|DISSECT_IMAGE_DISCARD_ON_CRYPTO); + else if (streq(optarg, "loop")) + arg_flags = (arg_flags & ~(DISSECT_IMAGE_DISCARD|DISSECT_IMAGE_DISCARD_ON_CRYPTO)) | DISSECT_IMAGE_DISCARD_ON_LOOP; + else if (streq(optarg, "all")) + arg_flags = (arg_flags & ~(DISSECT_IMAGE_DISCARD_ON_CRYPTO)) | DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD; + else if (streq(optarg, "crypt")) + arg_flags |= DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD | DISSECT_IMAGE_DISCARD_ON_CRYPTO; + else { + log_error("Unknown --discard= parameter: %s", optarg); + return -EINVAL; + } + break; case '?': @@ -104,7 +123,7 @@ static int parse_argv(int argc, char *argv[]) { } arg_image = argv[optind]; - arg_read_only = true; + arg_flags |= DISSECT_IMAGE_READ_ONLY; break; case ACTION_MOUNT: @@ -126,6 +145,7 @@ static int parse_argv(int argc, char *argv[]) { int main(int argc, char *argv[]) { _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; + _cleanup_(decrypted_image_unrefp) DecryptedImage *di = NULL; _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL; int r; @@ -136,7 +156,7 @@ int main(int argc, char *argv[]) { if (r <= 0) goto finish; - r = loop_device_make_by_path(arg_image, arg_read_only ? O_RDONLY : O_RDWR, &d); + r = loop_device_make_by_path(arg_image, (arg_flags & DISSECT_IMAGE_READ_ONLY) ? O_RDONLY : O_RDWR, &d); if (r < 0) { log_error_errno(r, "Failed to set up loopback device: %m"); goto finish; @@ -186,14 +206,24 @@ int main(int argc, char *argv[]) { } case ACTION_MOUNT: - r = dissected_image_mount(m, arg_path, - (arg_read_only ? DISSECTED_IMAGE_READ_ONLY : 0) | - DISSECTED_IMAGE_DISCARD_ON_LOOP); + r = dissected_image_decrypt_interactively(m, NULL, arg_flags, &di); + if (r < 0) + goto finish; + + r = dissected_image_mount(m, arg_path, arg_flags); if (r < 0) { log_error_errno(r, "Failed to mount image: %m"); goto finish; } + if (di) { + r = decrypted_image_relinquish(di); + if (r < 0) { + log_error_errno(r, "Failed to relinquish DM devices: %m"); + goto finish; + } + } + loop_device_relinquish(d); break; diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c index 400d8ec7b0..65953b368f 100644 --- a/src/machine/image-dbus.c +++ b/src/machine/image-dbus.c @@ -358,7 +358,7 @@ static int raw_image_get_os_release(Image *image, char ***ret, sd_bus_error *err if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) _exit(EXIT_FAILURE); - r = dissected_image_mount(m, t, DISSECTED_IMAGE_READ_ONLY); + r = dissected_image_mount(m, t, DISSECT_IMAGE_READ_ONLY); if (r < 0) _exit(EXIT_FAILURE); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 6ad20f7457..035456f45b 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -2365,7 +2365,7 @@ static int outer_child( return r; if (dissected_image) { - r = dissected_image_mount(dissected_image, directory, DISSECTED_IMAGE_DISCARD_ON_LOOP|(arg_read_only ? DISSECTED_IMAGE_READ_ONLY : 0)); + r = dissected_image_mount(dissected_image, directory, DISSECT_IMAGE_DISCARD_ON_LOOP|(arg_read_only ? DISSECT_IMAGE_READ_ONLY : 0)); if (r < 0) return r; } @@ -3410,8 +3410,9 @@ int main(int argc, char *argv[]) { _cleanup_release_lock_file_ LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT; bool interactive, veth_created = false, remove_tmprootdir = false; char tmprootdir[] = "/tmp/nspawn-root-XXXXXX"; - _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL; _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL; + _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL; + _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL; log_parse_environment(); log_open(); @@ -3652,6 +3653,10 @@ int main(int argc, char *argv[]) { goto finish; } + r = dissected_image_decrypt_interactively(dissected_image, NULL, 0, &decrypted_image); + if (r < 0) + goto finish; + /* Now that we mounted the image, let's try to remove it again, if it is ephemeral */ if (remove_image && unlink(arg_image) >= 0) remove_image = false; diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 7b65daa0eb..bc4e45be6e 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -17,19 +17,73 @@ along with systemd; If not, see . ***/ +#ifdef HAVE_LIBCRYPTSETUP +#include +#endif +#include #include #include "architecture.h" +#include "ask-password-api.h" #include "blkid-util.h" #include "dissect-image.h" +#include "fd-util.h" #include "gpt.h" #include "mount-util.h" #include "path-util.h" #include "stat-util.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "udev-util.h" +static int probe_filesystem(const char *node, char **ret_fstype) { +#ifdef HAVE_BLKID + _cleanup_blkid_free_probe_ blkid_probe b = NULL; + const char *fstype; + int r; + + b = blkid_new_probe_from_filename(node); + if (!b) + return -ENOMEM; + + blkid_probe_enable_superblocks(b, 1); + blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); + + errno = 0; + r = blkid_do_safeprobe(b); + if (r == -2 || r == 1) { + log_debug("Failed to identify any partition type on partition %s", node); + goto not_found; + } + if (r != 0) { + if (errno == 0) + return -EIO; + + return -errno; + } + + (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); + + if (fstype) { + char *t; + + t = strdup(fstype); + if (!t) + return -ENOMEM; + + *ret_fstype = t; + return 1; + } + +not_found: + *ret_fstype = NULL; + return 0; +#else + return -EOPNOTSUPP; +#endif +} + int dissect_image(int fd, DissectedImage **ret) { #ifdef HAVE_BLKID @@ -96,7 +150,7 @@ int dissect_image(int fd, DissectedImage **ret) { return -ENOMEM; (void) blkid_probe_lookup_value(b, "USAGE", &usage, NULL); - if (streq_ptr(usage, "filesystem")) { + if (STRPTR_IN_SET(usage, "filesystem", "crypto")) { _cleanup_free_ char *t = NULL, *n = NULL; const char *fstype = NULL; @@ -123,6 +177,8 @@ int dissect_image(int fd, DissectedImage **ret) { t = n = NULL; + m->encrypted = streq(fstype, "crypto_LUKS"); + *ret = m; m = NULL; @@ -385,52 +441,24 @@ int dissect_image(int fd, DissectedImage **ret) { return -ENXIO; } + blkid_free_probe(b); + b = NULL; + /* Fill in file system types if we don't know them yet. */ for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) { - const char *fstype; - - if (!m->partitions[i].found) /* not found? */ - continue; - - if (m->partitions[i].fstype) /* already know the type? */ - continue; + DissectedPartition *p = m->partitions + i; - if (!m->partitions[i].node) /* have no device node for? */ + if (!p->found) continue; - if (b) - blkid_free_probe(b); - - b = blkid_new_probe_from_filename(m->partitions[i].node); - if (!b) - return -ENOMEM; - - blkid_probe_enable_superblocks(b, 1); - blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); - - errno = 0; - r = blkid_do_safeprobe(b); - if (r == -2 || r == 1) { - log_debug("Failed to identify any partition type on partition %i", m->partitions[i].partno); - continue; - } - if (r != 0) { - if (errno == 0) - return -EIO; - - return -errno; + if (!p->fstype && p->node) { + r = probe_filesystem(p->node, &p->fstype); + if (r < 0) + return r; } - (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); - if (fstype) { - char *t; - - t = strdup(fstype); - if (!t) - return -ENOMEM; - - m->partitions[i].fstype = t; - } + if (streq_ptr(p->fstype, "crypto_LUKS")) + m->encrypted = true; } *ret = m; @@ -451,48 +479,79 @@ DissectedImage* dissected_image_unref(DissectedImage *m) { for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) { free(m->partitions[i].fstype); free(m->partitions[i].node); + free(m->partitions[i].decrypted_fstype); + free(m->partitions[i].decrypted_node); } free(m); return NULL; } -static int mount_partition(DissectedPartition *m, const char *where, const char *directory, DissectedImageMountFlags flags) { - const char *p, *options = NULL; +static int is_loop_device(const char *path) { + char s[strlen("/sys/dev/block/") + DECIMAL_STR_MAX(dev_t) + 1 + DECIMAL_STR_MAX(dev_t) + strlen("/../loop/")]; + struct stat st; + + assert(path); + + if (stat(path, &st) < 0) + return -errno; + + if (!S_ISBLK(st.st_mode)) + return -ENOTBLK; + + xsprintf(s, "/sys/dev/block/%u:%u/loop/", major(st.st_rdev), minor(st.st_rdev)); + if (access(s, F_OK) < 0) { + if (errno != ENOENT) + return -errno; + + /* The device itself isn't a loop device, but maybe it's a partition and its parent is? */ + xsprintf(s, "/sys/dev/block/%u:%u/../loop/", major(st.st_rdev), minor(st.st_rdev)); + if (access(s, F_OK) < 0) + return errno == ENOENT ? false : -errno; + } + + return true; +} + +static int mount_partition( + DissectedPartition *m, + const char *where, + const char *directory, + DissectImageFlags flags) { + + const char *p, *options = NULL, *node, *fstype; bool rw; assert(m); assert(where); - if (!m->found || !m->node || !m->fstype) + node = m->decrypted_node ?: m->node; + fstype = m->decrypted_fstype ?: m->fstype; + + if (!m->found || !node || !fstype) return 0; - rw = m->rw && !(flags & DISSECTED_IMAGE_READ_ONLY); + /* Stacked encryption? Yuck */ + if (streq_ptr(fstype, "crypto_LUKS")) + return -ELOOP; + + rw = m->rw && !(flags & DISSECT_IMAGE_READ_ONLY); if (directory) p = strjoina(where, directory); else p = where; - /* Not supported for now. */ - if (streq(m->fstype, "crypto_LUKS")) - return -EOPNOTSUPP; - - /* If this is a loopback device then let's mount the image with discard, so that the underlying file remains - * sparse when possible. */ - if ((flags & DISSECTED_IMAGE_DISCARD_ON_LOOP) && - STR_IN_SET(m->fstype, "btrfs", "ext4", "vfat", "xfs")) { - const char *l; - - l = path_startswith(m->node, "/dev"); - if (l && startswith(l, "loop")) - options = "discard"; - } + /* If requested, turn on discard support. */ + if (STR_IN_SET(fstype, "btrfs", "ext4", "vfat", "xfs") && + ((flags & DISSECT_IMAGE_DISCARD) || + ((flags & DISSECT_IMAGE_DISCARD_ON_LOOP) && is_loop_device(m->node)))) + options = "discard"; - return mount_verbose(LOG_DEBUG, m->node, p, m->fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), options); + return mount_verbose(LOG_DEBUG, node, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), options); } -int dissected_image_mount(DissectedImage *m, const char *where, DissectedImageMountFlags flags) { +int dissected_image_mount(DissectedImage *m, const char *where, DissectImageFlags flags) { int r; assert(m); @@ -536,6 +595,284 @@ int dissected_image_mount(DissectedImage *m, const char *where, DissectedImageMo return 0; } +#ifdef HAVE_LIBCRYPTSETUP +typedef struct DecryptedPartition { + struct crypt_device *device; + char *name; + bool relinquished; +} DecryptedPartition; + +struct DecryptedImage { + DecryptedPartition *decrypted; + size_t n_decrypted; + size_t n_allocated; +}; +#endif + +DecryptedImage* decrypted_image_unref(DecryptedImage* d) { +#ifdef HAVE_LIBCRYPTSETUP + size_t i; + int r; + + if (!d) + return NULL; + + for (i = 0; i < d->n_decrypted; i++) { + DecryptedPartition *p = d->decrypted + i; + + if (p->device && p->name && !p->relinquished) { + r = crypt_deactivate(p->device, p->name); + if (r < 0) + log_debug_errno(r, "Failed to deactivate encrypted partition %s", p->name); + } + + if (p->device) + crypt_free(p->device); + free(p->name); + } + + free(d); +#endif + return NULL; +} + +#ifdef HAVE_LIBCRYPTSETUP +static int decrypt_partition( + DissectedPartition *m, + const char *passphrase, + DissectImageFlags flags, + DecryptedImage *d) { + + _cleanup_free_ char *node = NULL, *name = NULL; + struct crypt_device *cd; + const char *suffix; + int r; + + assert(m); + assert(d); + + if (!m->found || !m->node || !m->fstype) + return 0; + + if (!streq(m->fstype, "crypto_LUKS")) + return 0; + + suffix = strrchr(m->node, '/'); + if (!suffix) + return -EINVAL; + suffix++; + if (isempty(suffix)) + return -EINVAL; + + name = strjoin(suffix, "-decrypted"); + if (!name) + return -ENOMEM; + if (!filename_is_valid(name)) + return -EINVAL; + + node = strjoin(crypt_get_dir(), "/", name); + if (!node) + return -ENOMEM; + + if (!GREEDY_REALLOC0(d->decrypted, d->n_allocated, d->n_decrypted + 1)) + return -ENOMEM; + + r = crypt_init(&cd, m->node); + if (r < 0) + return r; + + r = crypt_load(cd, CRYPT_LUKS1, NULL); + if (r < 0) + goto fail; + + r = crypt_activate_by_passphrase(cd, name, CRYPT_ANY_SLOT, passphrase, strlen(passphrase), + ((flags & DISSECT_IMAGE_READ_ONLY) ? CRYPT_ACTIVATE_READONLY : 0) | + ((flags & DISSECT_IMAGE_DISCARD_ON_CRYPTO) ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0)); + if (r == -EPERM) { + r = -EKEYREJECTED; + goto fail; + } + if (r < 0) + goto fail; + + d->decrypted[d->n_decrypted].name = name; + name = NULL; + + d->decrypted[d->n_decrypted].device = cd; + d->n_decrypted++; + + m->decrypted_node = node; + node = NULL; + + return 0; + +fail: + crypt_free(cd); + return r; +} +#endif + +int dissected_image_decrypt( + DissectedImage *m, + const char *passphrase, + DissectImageFlags flags, + DecryptedImage **ret) { + + _cleanup_(decrypted_image_unrefp) DecryptedImage *d = NULL; +#ifdef HAVE_LIBCRYPTSETUP + unsigned i; + int r; +#endif + + assert(m); + + /* Returns: + * + * = 0 → There was nothing to decrypt + * > 0 → Decrypted successfully + * -ENOKEY → There's some to decrypt but no key was supplied + * -EKEYREJECTED → Passed key was not correct + */ + + if (!m->encrypted) { + *ret = NULL; + return 0; + } + +#ifdef HAVE_LIBCRYPTSETUP + if (!passphrase) + return -ENOKEY; + + d = new0(DecryptedImage, 1); + if (!d) + return -ENOMEM; + + for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) { + DissectedPartition *p = m->partitions + i; + + if (!p->found) + continue; + + r = decrypt_partition(p, passphrase, flags, d); + if (r < 0) + return r; + + if (!p->decrypted_fstype && p->decrypted_node) { + r = probe_filesystem(p->decrypted_node, &p->decrypted_fstype); + if (r < 0) + return r; + } + } + + *ret = d; + d = NULL; + + return 1; +#else + return -EOPNOTSUPP; +#endif +} + +int dissected_image_decrypt_interactively( + DissectedImage *m, + const char *passphrase, + DissectImageFlags flags, + DecryptedImage **ret) { + + _cleanup_strv_free_erase_ char **z = NULL; + int n = 3, r; + + if (passphrase) + n--; + + for (;;) { + r = dissected_image_decrypt(m, passphrase, flags, ret); + if (r >= 0) + return r; + if (r == -EKEYREJECTED) + log_error_errno(r, "Incorrect passphrase, try again!"); + else if (r != -ENOKEY) { + log_error_errno(r, "Failed to decrypt image: %m"); + return r; + } + + if (--n < 0) { + log_error("Too many retries."); + return -EKEYREJECTED; + } + + z = strv_free(z); + + r = ask_password_auto("Please enter image passphrase!", NULL, "dissect", "dissect", USEC_INFINITY, 0, &z); + if (r < 0) + return log_error_errno(r, "Failed to query for passphrase: %m"); + + passphrase = z[0]; + } +} + +#ifdef HAVE_LIBCRYPTSETUP +static int deferred_remove(DecryptedPartition *p) { + + struct dm_ioctl dm = { + .version = { + DM_VERSION_MAJOR, + DM_VERSION_MINOR, + DM_VERSION_PATCHLEVEL + }, + .data_size = sizeof(dm), + .flags = DM_DEFERRED_REMOVE, + }; + + _cleanup_close_ int fd = -1; + + assert(p); + + /* Unfortunately, libcryptsetup doesn't provide a proper API for this, hence call the ioctl() directly. */ + + fd = open("/dev/mapper/control", O_RDWR|O_CLOEXEC); + if (fd < 0) + return -errno; + + strncpy(dm.name, p->name, sizeof(dm.name)); + + if (ioctl(fd, DM_DEV_REMOVE, &dm)) + return -errno; + + return 0; +} +#endif + +int decrypted_image_relinquish(DecryptedImage *d) { + +#ifdef HAVE_LIBCRYPTSETUP + size_t i; + int r; +#endif + + assert(d); + + /* Turns on automatic removal after the last use ended for all DM devices of this image, and sets a boolean so + * that we don't clean it up ourselves either anymore */ + +#ifdef HAVE_LIBCRYPTSETUP + for (i = 0; i < d->n_decrypted; i++) { + DecryptedPartition *p = d->decrypted + i; + + if (p->relinquished) + continue; + + r = deferred_remove(p); + if (r < 0) + return log_debug_errno(r, "Failed to mark %s for auto-removal: %m", p->name); + + p->relinquished = true; + } +#endif + + return 0; +} + static const char *const partition_designator_table[] = { [PARTITION_ROOT] = "root", [PARTITION_ROOT_SECONDARY] = "root-secondary", diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 04b19e8553..69484eb32c 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -25,6 +25,7 @@ typedef struct DissectedImage DissectedImage; typedef struct DissectedPartition DissectedPartition; +typedef struct DecryptedImage DecryptedImage; struct DissectedPartition { bool found:1; @@ -33,6 +34,8 @@ struct DissectedPartition { int architecture; /* Intended architecture: either native, secondary or unset (-1). */ char *fstype; char *node; + char *decrypted_node; + char *decrypted_fstype; }; enum { @@ -46,12 +49,15 @@ enum { _PARTITION_DESIGNATOR_INVALID = -1 }; -typedef enum DissectedImageMountFlags { - DISSECTED_IMAGE_READ_ONLY = 1, - DISSECTED_IMAGE_DISCARD_ON_LOOP = 2, /* Turn on "discard" if on loop device and file system supports it */ -} DissectedImageMountFlags; +typedef enum DissectImageFlags { + DISSECT_IMAGE_READ_ONLY = 1, + DISSECT_IMAGE_DISCARD_ON_LOOP = 2, /* Turn on "discard" if on loop device and file system supports it */ + DISSECT_IMAGE_DISCARD = 4, /* Turn on "discard" if file system supports it, on all block devices */ + DISSECT_IMAGE_DISCARD_ON_CRYPTO = 8, /* Turn on "discard" also on crypto devices */ +} DissectImageFlags; struct DissectedImage { + bool encrypted; DissectedPartition partitions[_PARTITION_DESIGNATOR_MAX]; }; @@ -60,7 +66,13 @@ int dissect_image(int fd, DissectedImage **ret); DissectedImage* dissected_image_unref(DissectedImage *m); DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref); -int dissected_image_mount(DissectedImage *m, const char *dest, DissectedImageMountFlags flags); +int dissected_image_decrypt(DissectedImage *m, const char *passphrase, DissectImageFlags flags, DecryptedImage **ret); +int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, DissectImageFlags flags, DecryptedImage **ret); +int dissected_image_mount(DissectedImage *m, const char *dest, DissectImageFlags flags); + +DecryptedImage* decrypted_image_unref(DecryptedImage *p); +DEFINE_TRIVIAL_CLEANUP_FUNC(DecryptedImage*, decrypted_image_unref); +int decrypted_image_relinquish(DecryptedImage *d); const char* partition_designator_to_string(int i) _const_; int partition_designator_from_string(const char *name) _pure_; -- cgit v1.2.3-54-g00ecf From 4623e8e6ac7c7a36b16ec2dc9ad8507fd820c9fa Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 7 Dec 2016 18:28:13 +0100 Subject: nspawn/dissect: automatically discover dm-verity verity partitions This adds support for discovering and making use of properly tagged dm-verity data integrity partitions. This extends both systemd-nspawn and systemd-dissect with a new --root-hash= switch that takes the root hash to use for the root partition, and is otherwise fully automatic. Verity partitions are discovered automatically by GPT table type UUIDs, as listed in https://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/ (which I updated prior to this change, to include new UUIDs for this purpose. mkosi with https://github.com/systemd/mkosi/pull/39 applied may generate images that carry the necessary integrity data. With that PR and this commit, the following simply lines suffice to boot up an integrity-protected container image: ``` # mkdir test # cd test # mkosi --verity # systemd-nspawn -i ./image.raw -bn ``` Note that mkosi writes the image file to "image.raw" next to a a file "image.roothash" that contains the root hash. systemd-nspawn will look for that file and use it if it exists, in case --root-hash= is not specified explicitly. --- src/dissect/dissect.c | 43 +++++++- src/machine/image-dbus.c | 2 +- src/nspawn/nspawn.c | 88 ++++++++++++++- src/shared/dissect-image.c | 245 +++++++++++++++++++++++++++++++++++++----- src/shared/dissect-image.h | 20 +++- src/shared/gpt.h | 17 ++- src/test/test-dissect-image.c | 2 +- 7 files changed, 378 insertions(+), 39 deletions(-) (limited to 'src/shared/dissect-image.h') diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 5e6848acb4..e3c96b7407 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -23,6 +23,7 @@ #include "architecture.h" #include "dissect-image.h" +#include "hexdecoct.h" #include "log.h" #include "loop-util.h" #include "string-util.h" @@ -35,6 +36,8 @@ static enum { static const char *arg_image = NULL; static const char *arg_path = NULL; static DissectImageFlags arg_flags = DISSECT_IMAGE_DISCARD_ON_LOOP; +static void *arg_root_hash = NULL; +static size_t arg_root_hash_size = 0; static void help(void) { printf("%s [OPTIONS...] IMAGE\n" @@ -44,7 +47,8 @@ static void help(void) { " --version Show package version\n" " -m --mount Mount the image to the specified directory\n" " -r --read-only Mount read-only\n" - " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n", + " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n" + " --root-hash=HASH Specify root hash for verity\n", program_invocation_short_name, program_invocation_short_name); } @@ -54,6 +58,7 @@ static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_DISCARD, + ARG_ROOT_HASH, }; static const struct option options[] = { @@ -62,10 +67,11 @@ static int parse_argv(int argc, char *argv[]) { { "mount", no_argument, NULL, 'm' }, { "read-only", no_argument, NULL, 'r' }, { "discard", required_argument, NULL, ARG_DISCARD }, + { "root-hash", required_argument, NULL, ARG_ROOT_HASH }, {} }; - int c; + int c, r; assert(argc >= 0); assert(argv); @@ -105,6 +111,25 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_ROOT_HASH: { + void *p; + size_t l; + + r = unhexmem(optarg, strlen(optarg), &p, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse root hash: %s", optarg); + if (l < sizeof(sd_id128_t)) { + log_error("Root hash must be at least 128bit long: %s", optarg); + free(p); + return -EINVAL; + } + + free(arg_root_hash); + arg_root_hash = p; + arg_root_hash_size = l; + break; + } + case '?': return -EINVAL; @@ -162,11 +187,15 @@ int main(int argc, char *argv[]) { goto finish; } - r = dissect_image(d->fd, &m); + r = dissect_image(d->fd, arg_root_hash, arg_root_hash_size, &m); if (r == -ENOPKG) { log_error_errno(r, "Couldn't identify a suitable partition table or file system in %s.", arg_image); goto finish; } + if (r == -EADDRNOTAVAIL) { + log_error_errno(r, "No root partition for specified root hash found in %s.", arg_image); + goto finish; + } if (r < 0) { log_error_errno(r, "Failed to dissect image: %m"); goto finish; @@ -179,6 +208,7 @@ int main(int argc, char *argv[]) { for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) { DissectedPartition *p = m->partitions + i; + int k; if (!p->found) continue; @@ -193,6 +223,10 @@ int main(int argc, char *argv[]) { if (p->architecture != _ARCHITECTURE_INVALID) printf(" for %s", architecture_to_string(p->architecture)); + k = PARTITION_VERITY_OF(i); + if (k >= 0) + printf(" %s verity", m->partitions[k].found ? "with" : "without"); + if (p->partno >= 0) printf(" on partition #%i", p->partno); @@ -206,7 +240,7 @@ int main(int argc, char *argv[]) { } case ACTION_MOUNT: - r = dissected_image_decrypt_interactively(m, NULL, arg_flags, &di); + r = dissected_image_decrypt_interactively(m, NULL, arg_root_hash, arg_root_hash_size, arg_flags, &di); if (r < 0) goto finish; @@ -232,5 +266,6 @@ int main(int argc, char *argv[]) { } finish: + free(arg_root_hash); return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c index 65953b368f..e2fb882393 100644 --- a/src/machine/image-dbus.c +++ b/src/machine/image-dbus.c @@ -336,7 +336,7 @@ static int raw_image_get_os_release(Image *image, char ***ret, sd_bus_error *err if (r < 0) return sd_bus_error_set_errnof(error, r, "Failed to set up loop block device for %s: %m", image->path); - r = dissect_image(d->fd, &m); + r = dissect_image(d->fd, NULL, 0, &m); if (r == -ENOPKG) return sd_bus_error_set_errnof(error, r, "Disk image %s not understood: %m", image->path); if (r < 0) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 9168228f4a..de05b6c5ef 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -61,6 +61,7 @@ #include "format-util.h" #include "fs-util.h" #include "gpt.h" +#include "hexdecoct.h" #include "hostname-util.h" #include "id128-util.h" #include "log.h" @@ -200,6 +201,8 @@ static bool arg_notify_ready = false; static bool arg_use_cgns = true; static unsigned long arg_clone_ns_flags = CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS; static MountSettingsMask arg_mount_settings = MOUNT_APPLY_APIVFS_RO; +static void *arg_root_hash = NULL; +static size_t arg_root_hash_size = 0; static void help(void) { printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" @@ -213,6 +216,7 @@ static void help(void) { " -x --ephemeral Run container with snapshot of root directory, and\n" " remove it after exit\n" " -i --image=PATH File system device or disk image for the container\n" + " --root-hash=HASH Specify verity root hash\n" " -a --as-pid2 Maintain a stub init as PID1, invoke binary as PID2\n" " -b --boot Boot up full system (i.e. invoke init)\n" " --chdir=PATH Set working directory in the container\n" @@ -424,6 +428,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_CHDIR, ARG_PRIVATE_USERS_CHOWN, ARG_NOTIFY_READY, + ARG_ROOT_HASH, }; static const struct option options[] = { @@ -473,6 +478,7 @@ static int parse_argv(int argc, char *argv[]) { { "settings", required_argument, NULL, ARG_SETTINGS }, { "chdir", required_argument, NULL, ARG_CHDIR }, { "notify-ready", required_argument, NULL, ARG_NOTIFY_READY }, + { "root-hash", required_argument, NULL, ARG_ROOT_HASH }, {} }; @@ -1016,6 +1022,25 @@ static int parse_argv(int argc, char *argv[]) { arg_settings_mask |= SETTING_NOTIFY_READY; break; + case ARG_ROOT_HASH: { + void *k; + size_t l; + + r = unhexmem(optarg, strlen(optarg), &k, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse root hash: %s", optarg); + if (l < sizeof(sd_id128_t)) { + log_error("Root hash must be at least 128bit long: %s", optarg); + free(k); + return -EINVAL; + } + + free(arg_root_hash); + arg_root_hash = k; + arg_root_hash_size = l; + break; + } + case '?': return -EINVAL; @@ -3409,6 +3434,53 @@ static int run(int master, return 1; /* loop again */ } +static int load_root_hash(const char *image) { + _cleanup_free_ char *text = NULL; + char *fn, *n, *e; + void *k; + size_t l; + int r; + + assert_se(image); + + /* Try to load the root hash from a file next to the image file if it exists. */ + + if (arg_root_hash) + return 0; + + fn = new(char, strlen(image) + strlen(".roothash") + 1); + if (!fn) + return log_oom(); + + n = stpcpy(fn, image); + e = endswith(fn, ".raw"); + if (e) + n = e; + + strcpy(n, ".roothash"); + + r = read_one_line_file(fn, &text); + if (r == -ENOENT) + return 0; + if (r < 0) { + log_warning_errno(r, "Failed to read %s, ignoring: %m", fn); + return 0; + } + + r = unhexmem(text, strlen(text), &k, &l); + if (r < 0) + return log_error_errno(r, "Invalid root hash: %s", text); + if (l < sizeof(sd_id128_t)) { + free(k); + return log_error_errno(r, "Root hash too short: %s", text); + } + + arg_root_hash = k; + arg_root_hash_size = l; + + return 0; +} + int main(int argc, char *argv[]) { _cleanup_free_ char *console = NULL; @@ -3623,6 +3695,10 @@ int main(int argc, char *argv[]) { r = log_error_errno(r, "Failed to create image lock: %m"); goto finish; } + + r = load_root_hash(arg_image); + if (r < 0) + goto finish; } if (!mkdtemp(tmprootdir)) { @@ -3644,7 +3720,7 @@ int main(int argc, char *argv[]) { goto finish; } - r = dissect_image(loop->fd, &dissected_image); + r = dissect_image(loop->fd, arg_root_hash, arg_root_hash_size, &dissected_image); if (r == -ENOPKG) { log_error_errno(r, "Could not find a suitable file system or partition table in image: %s", arg_image); @@ -3656,6 +3732,10 @@ int main(int argc, char *argv[]) { "in order to be bootable with systemd-nspawn."); goto finish; } + if (r == -EADDRNOTAVAIL) { + log_error_errno(r, "No root partition for specified root hash found."); + goto finish; + } if (r == -EOPNOTSUPP) { log_error_errno(r, "--image= is not supported, compiled without blkid support."); goto finish; @@ -3665,7 +3745,10 @@ int main(int argc, char *argv[]) { goto finish; } - r = dissected_image_decrypt_interactively(dissected_image, NULL, 0, &decrypted_image); + if (!arg_root_hash && dissected_image->can_verity) + log_notice("Note: image %s contains verity information, but no root hash specified! Proceeding without integrity checking.", arg_image); + + r = dissected_image_decrypt_interactively(dissected_image, NULL, arg_root_hash, arg_root_hash_size, 0, &decrypted_image); if (r < 0) goto finish; @@ -3792,6 +3875,7 @@ finish: strv_free(arg_parameters); custom_mount_free_all(arg_custom_mounts, arg_n_custom_mounts); expose_port_free_all(arg_expose_ports); + free(arg_root_hash); return r < 0 ? EXIT_FAILURE : ret; } diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index bc4e45be6e..257af78781 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -84,9 +84,10 @@ not_found: #endif } -int dissect_image(int fd, DissectedImage **ret) { +int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectedImage **ret) { #ifdef HAVE_BLKID + sd_id128_t root_uuid = SD_ID128_NULL, verity_uuid = SD_ID128_NULL; _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; bool is_gpt, is_mbr, generic_rw, multiple_generic = false; _cleanup_udev_device_unref_ struct udev_device *d = NULL; @@ -103,10 +104,29 @@ int dissect_image(int fd, DissectedImage **ret) { assert(fd >= 0); assert(ret); + assert(root_hash || root_hash_size == 0); /* Probes a disk image, and returns information about what it found in *ret. * - * Returns -ENOPKG if no suitable partition table or file system could be found. */ + * Returns -ENOPKG if no suitable partition table or file system could be found. + * Returns -EADDRNOTAVAIL if a root hash was specified but no matching root/verity partitions found. */ + + if (root_hash) { + /* If a root hash is supplied, then we use the root partition that has a UUID that match the first + * 128bit of the root hash. And we use the verity partition that has a UUID that match the final + * 128bit. */ + + if (root_hash_size < sizeof(sd_id128_t)) + return -EINVAL; + + memcpy(&root_uuid, root_hash, sizeof(sd_id128_t)); + memcpy(&verity_uuid, (const uint8_t*) root_hash + root_hash_size - sizeof(sd_id128_t), sizeof(sd_id128_t)); + + if (sd_id128_is_null(root_uuid)) + return -EINVAL; + if (sd_id128_is_null(verity_uuid)) + return -EINVAL; + } if (fstat(fd, &st) < 0) return -errno; @@ -313,17 +333,22 @@ int dissect_image(int fd, DissectedImage **ret) { if (is_gpt) { int designator = _PARTITION_DESIGNATOR_INVALID, architecture = _ARCHITECTURE_INVALID; - const char *stype, *fstype = NULL; - sd_id128_t type_id; + const char *stype, *sid, *fstype = NULL; + sd_id128_t type_id, id; bool rw = true; if (flags & GPT_FLAG_NO_AUTO) continue; + sid = blkid_partition_get_uuid(pp); + if (!sid) + continue; + if (sd_id128_from_string(sid, &id) < 0) + continue; + stype = blkid_partition_get_type_string(pp); if (!stype) continue; - if (sd_id128_from_string(stype, &type_id) < 0) continue; @@ -339,17 +364,57 @@ int dissect_image(int fd, DissectedImage **ret) { } #ifdef GPT_ROOT_NATIVE else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) { + + /* If a root ID is specified, ignore everything but the root id */ + if (!sd_id128_is_null(root_uuid) && !sd_id128_equal(root_uuid, id)) + continue; + designator = PARTITION_ROOT; architecture = native_architecture(); rw = !(flags & GPT_FLAG_READ_ONLY); } +#ifdef GPT_ROOT_NATIVE_VERITY + else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE_VERITY)) { + + m->can_verity = true; + + /* Ignore verity unless a root hash is specified */ + if (sd_id128_is_null(verity_uuid) || !sd_id128_equal(verity_uuid, id)) + continue; + + designator = PARTITION_ROOT_VERITY; + fstype = "DM_verity_hash"; + architecture = native_architecture(); + rw = false; + } +#endif #endif #ifdef GPT_ROOT_SECONDARY else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY)) { + + /* If a root ID is specified, ignore everything but the root id */ + if (!sd_id128_is_null(root_uuid) && !sd_id128_equal(root_uuid, id)) + continue; + designator = PARTITION_ROOT_SECONDARY; architecture = SECONDARY_ARCHITECTURE; rw = !(flags & GPT_FLAG_READ_ONLY); } +#ifdef GPT_ROOT_SECONDARY_VERITY + else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY_VERITY)) { + + m->can_verity = true; + + /* Ignore verity unless root has is specified */ + if (sd_id128_is_null(verity_uuid) || !sd_id128_equal(verity_uuid, id)) + continue; + + designator = PARTITION_ROOT_SECONDARY_VERITY; + fstype = "DM_verity_hash"; + architecture = SECONDARY_ARCHITECTURE; + rw = false; + } +#endif #endif else if (sd_id128_equal(type_id, GPT_SWAP)) { designator = PARTITION_SWAP; @@ -420,10 +485,17 @@ int dissect_image(int fd, DissectedImage **ret) { /* No root partition found? Then let's see if ther's one for the secondary architecture. And if not * either, then check if there's a single generic one, and use that. */ + if (m->partitions[PARTITION_ROOT_VERITY].found) + return -ENXIO; + if (m->partitions[PARTITION_ROOT_SECONDARY].found) { m->partitions[PARTITION_ROOT] = m->partitions[PARTITION_ROOT_SECONDARY]; zero(m->partitions[PARTITION_ROOT_SECONDARY]); - } else if (generic_node) { + + m->partitions[PARTITION_ROOT_VERITY] = m->partitions[PARTITION_ROOT_SECONDARY_VERITY]; + zero(m->partitions[PARTITION_ROOT_SECONDARY_VERITY]); + + } else if (generic_node && !root_hash) { if (multiple_generic) return -ENOTUNIQ; @@ -441,6 +513,24 @@ int dissect_image(int fd, DissectedImage **ret) { return -ENXIO; } + assert(m->partitions[PARTITION_ROOT].found); + + if (root_hash) { + if (!m->partitions[PARTITION_ROOT_VERITY].found) + return -EADDRNOTAVAIL; + + /* If we found the primary root with the hash, then we definitely want to suppress any secondary root + * (which would be weird, after all the root hash should only be assigned to one pair of + * partitions... */ + m->partitions[PARTITION_ROOT_SECONDARY].found = false; + m->partitions[PARTITION_ROOT_SECONDARY_VERITY].found = false; + + /* If we found a verity setup, then the root partition is necessarily read-only. */ + m->partitions[PARTITION_ROOT].rw = false; + + m->verity = true; + } + blkid_free_probe(b); b = NULL; @@ -637,6 +727,40 @@ DecryptedImage* decrypted_image_unref(DecryptedImage* d) { } #ifdef HAVE_LIBCRYPTSETUP + +static int make_dm_name_and_node(const void *original_node, const char *suffix, char **ret_name, char **ret_node) { + _cleanup_free_ char *name = NULL, *node = NULL; + const char *base; + + assert(original_node); + assert(suffix); + assert(ret_name); + assert(ret_node); + + base = strrchr(original_node, '/'); + if (!base) + return -EINVAL; + base++; + if (isempty(base)) + return -EINVAL; + + name = strjoin(base, suffix); + if (!name) + return -ENOMEM; + if (!filename_is_valid(name)) + return -EINVAL; + + node = strjoin(crypt_get_dir(), "/", name); + if (!node) + return -ENOMEM; + + *ret_name = name; + *ret_node = node; + + name = node = NULL; + return 0; +} + static int decrypt_partition( DissectedPartition *m, const char *passphrase, @@ -645,7 +769,6 @@ static int decrypt_partition( _cleanup_free_ char *node = NULL, *name = NULL; struct crypt_device *cd; - const char *suffix; int r; assert(m); @@ -657,22 +780,9 @@ static int decrypt_partition( if (!streq(m->fstype, "crypto_LUKS")) return 0; - suffix = strrchr(m->node, '/'); - if (!suffix) - return -EINVAL; - suffix++; - if (isempty(suffix)) - return -EINVAL; - - name = strjoin(suffix, "-decrypted"); - if (!name) - return -ENOMEM; - if (!filename_is_valid(name)) - return -EINVAL; - - node = strjoin(crypt_get_dir(), "/", name); - if (!node) - return -ENOMEM; + r = make_dm_name_and_node(m->node, "-decrypted", &name, &node); + if (r < 0) + return r; if (!GREEDY_REALLOC0(d->decrypted, d->n_allocated, d->n_decrypted + 1)) return -ENOMEM; @@ -706,6 +816,71 @@ static int decrypt_partition( return 0; +fail: + crypt_free(cd); + return r; +} + +static int verity_partition( + DissectedPartition *m, + DissectedPartition *v, + const void *root_hash, + size_t root_hash_size, + DissectImageFlags flags, + DecryptedImage *d) { + + _cleanup_free_ char *node = NULL, *name = NULL; + struct crypt_device *cd; + int r; + + assert(m); + assert(v); + + if (!root_hash) + return 0; + + if (!m->found || !m->node || !m->fstype) + return 0; + if (!v->found || !v->node || !v->fstype) + return 0; + + if (!streq(v->fstype, "DM_verity_hash")) + return 0; + + r = make_dm_name_and_node(m->node, "-verity", &name, &node); + if (r < 0) + return r; + + if (!GREEDY_REALLOC0(d->decrypted, d->n_allocated, d->n_decrypted + 1)) + return -ENOMEM; + + r = crypt_init(&cd, v->node); + if (r < 0) + return r; + + r = crypt_load(cd, CRYPT_VERITY, NULL); + if (r < 0) + goto fail; + + r = crypt_set_data_device(cd, m->node); + if (r < 0) + goto fail; + + r = crypt_activate_by_volume_key(cd, name, root_hash, root_hash_size, CRYPT_ACTIVATE_READONLY); + if (r < 0) + goto fail; + + d->decrypted[d->n_decrypted].name = name; + name = NULL; + + d->decrypted[d->n_decrypted].device = cd; + d->n_decrypted++; + + m->decrypted_node = node; + node = NULL; + + return 0; + fail: crypt_free(cd); return r; @@ -715,6 +890,8 @@ fail: int dissected_image_decrypt( DissectedImage *m, const char *passphrase, + const void *root_hash, + size_t root_hash_size, DissectImageFlags flags, DecryptedImage **ret) { @@ -725,6 +902,7 @@ int dissected_image_decrypt( #endif assert(m); + assert(root_hash || root_hash_size == 0); /* Returns: * @@ -734,13 +912,16 @@ int dissected_image_decrypt( * -EKEYREJECTED → Passed key was not correct */ - if (!m->encrypted) { + if (root_hash && root_hash_size < sizeof(sd_id128_t)) + return -EINVAL; + + if (!m->encrypted && !m->verity) { *ret = NULL; return 0; } #ifdef HAVE_LIBCRYPTSETUP - if (!passphrase) + if (m->encrypted && !passphrase) return -ENOKEY; d = new0(DecryptedImage, 1); @@ -749,6 +930,7 @@ int dissected_image_decrypt( for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) { DissectedPartition *p = m->partitions + i; + int k; if (!p->found) continue; @@ -757,6 +939,13 @@ int dissected_image_decrypt( if (r < 0) return r; + k = PARTITION_VERITY_OF(i); + if (k >= 0) { + r = verity_partition(p, m->partitions + k, root_hash, root_hash_size, flags, d); + if (r < 0) + return r; + } + if (!p->decrypted_fstype && p->decrypted_node) { r = probe_filesystem(p->decrypted_node, &p->decrypted_fstype); if (r < 0) @@ -776,6 +965,8 @@ int dissected_image_decrypt( int dissected_image_decrypt_interactively( DissectedImage *m, const char *passphrase, + const void *root_hash, + size_t root_hash_size, DissectImageFlags flags, DecryptedImage **ret) { @@ -786,7 +977,7 @@ int dissected_image_decrypt_interactively( n--; for (;;) { - r = dissected_image_decrypt(m, passphrase, flags, ret); + r = dissected_image_decrypt(m, passphrase, root_hash, root_hash_size, flags, ret); if (r >= 0) return r; if (r == -EKEYREJECTED) @@ -880,6 +1071,8 @@ static const char *const partition_designator_table[] = { [PARTITION_SRV] = "srv", [PARTITION_ESP] = "esp", [PARTITION_SWAP] = "swap", + [PARTITION_ROOT_VERITY] = "root-verity", + [PARTITION_ROOT_SECONDARY_VERITY] = "root-secondary-verity", }; DEFINE_STRING_TABLE_LOOKUP(partition_designator, int); diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 69484eb32c..902c8d4a37 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -45,10 +45,20 @@ enum { PARTITION_SRV, PARTITION_ESP, PARTITION_SWAP, + PARTITION_ROOT_VERITY, /* verity data for the PARTITION_ROOT partition */ + PARTITION_ROOT_SECONDARY_VERITY, /* verity data for the PARTITION_ROOT_SECONDARY partition */ _PARTITION_DESIGNATOR_MAX, _PARTITION_DESIGNATOR_INVALID = -1 }; +static inline int PARTITION_VERITY_OF(int p) { + if (p == PARTITION_ROOT) + return PARTITION_ROOT_VERITY; + if (p == PARTITION_ROOT_SECONDARY) + return PARTITION_ROOT_SECONDARY_VERITY; + return _PARTITION_DESIGNATOR_INVALID; +} + typedef enum DissectImageFlags { DISSECT_IMAGE_READ_ONLY = 1, DISSECT_IMAGE_DISCARD_ON_LOOP = 2, /* Turn on "discard" if on loop device and file system supports it */ @@ -57,17 +67,19 @@ typedef enum DissectImageFlags { } DissectImageFlags; struct DissectedImage { - bool encrypted; + bool encrypted:1; + bool verity:1; /* verity available and usable */ + bool can_verity:1; /* verity available, but not necessarily used */ DissectedPartition partitions[_PARTITION_DESIGNATOR_MAX]; }; -int dissect_image(int fd, DissectedImage **ret); +int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectedImage **ret); DissectedImage* dissected_image_unref(DissectedImage *m); DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref); -int dissected_image_decrypt(DissectedImage *m, const char *passphrase, DissectImageFlags flags, DecryptedImage **ret); -int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, DissectImageFlags flags, DecryptedImage **ret); +int dissected_image_decrypt(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, DissectImageFlags flags, DecryptedImage **ret); +int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, DissectImageFlags flags, DecryptedImage **ret); int dissected_image_mount(DissectedImage *m, const char *dest, DissectImageFlags flags); DecryptedImage* decrypted_image_unref(DecryptedImage *p); diff --git a/src/shared/gpt.h b/src/shared/gpt.h index 55b41bbcd8..13d80d611c 100644 --- a/src/shared/gpt.h +++ b/src/shared/gpt.h @@ -32,28 +32,43 @@ #define GPT_ROOT_ARM SD_ID128_MAKE(69,da,d7,10,2c,e4,4e,3c,b1,6c,21,a1,d4,9a,be,d3) #define GPT_ROOT_ARM_64 SD_ID128_MAKE(b9,21,b0,45,1d,f0,41,c3,af,44,4c,6f,28,0d,3f,ae) #define GPT_ROOT_IA64 SD_ID128_MAKE(99,3d,8d,3d,f8,0e,42,25,85,5a,9d,af,8e,d7,ea,97) - #define GPT_ESP SD_ID128_MAKE(c1,2a,73,28,f8,1f,11,d2,ba,4b,00,a0,c9,3e,c9,3b) #define GPT_SWAP SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f) #define GPT_HOME SD_ID128_MAKE(93,3a,c7,e1,2e,b4,4f,13,b8,44,0e,14,e2,ae,f9,15) #define GPT_SRV SD_ID128_MAKE(3b,8f,84,25,20,e0,4f,3b,90,7f,1a,25,a7,6f,98,e8) +/* Verity partitions for the root partitions above (we only define them for the root partitions, because only they are + * are commonly read-only and hence suitable for verity). */ +#define GPT_ROOT_X86_VERITY SD_ID128_MAKE(d1,3c,5d,3b,b5,d1,42,2a,b2,9f,94,54,fd,c8,9d,76) +#define GPT_ROOT_X86_64_VERITY SD_ID128_MAKE(2c,73,57,ed,eb,d2,46,d9,ae,c1,23,d4,37,ec,2b,f5) +#define GPT_ROOT_ARM_VERITY SD_ID128_MAKE(73,86,cd,f2,20,3c,47,a9,a4,98,f2,ec,ce,45,a2,d6) +#define GPT_ROOT_ARM_64_VERITY SD_ID128_MAKE(df,33,00,ce,d6,9f,4c,92,97,8c,9b,fb,0f,38,d8,20) +#define GPT_ROOT_IA64_VERITY SD_ID128_MAKE(86,ed,10,d5,b6,07,45,bb,89,57,d3,50,f2,3d,05,71) + + #if defined(__x86_64__) # define GPT_ROOT_NATIVE GPT_ROOT_X86_64 # define GPT_ROOT_SECONDARY GPT_ROOT_X86 +# define GPT_ROOT_NATIVE_VERITY GPT_ROOT_X86_64_VERITY +# define GPT_ROOT_SECONDARY_VERITY GPT_ROOT_X86_VERITY #elif defined(__i386__) # define GPT_ROOT_NATIVE GPT_ROOT_X86 +# define GPT_ROOT_NATIVE_VERITY GPT_ROOT_X86_VERITY #endif #if defined(__ia64__) # define GPT_ROOT_NATIVE GPT_ROOT_IA64 +# define GPT_ROOT_NATIVE_VERITY GPT_ROOT_IA64_VERITY #endif #if defined(__aarch64__) && (__BYTE_ORDER != __BIG_ENDIAN) # define GPT_ROOT_NATIVE GPT_ROOT_ARM_64 # define GPT_ROOT_SECONDARY GPT_ROOT_ARM +# define GPT_ROOT_NATIVE_VERITY GPT_ROOT_ARM_64_VERITY +# define GPT_ROOT_SECONDARY_VERITY GPT_ROOT_ARM_VERITY #elif defined(__arm__) && (__BYTE_ORDER != __BIG_ENDIAN) # define GPT_ROOT_NATIVE GPT_ROOT_ARM +# define GPT_ROOT_NATIVE_VERITY GPT_ROOT_ARM_VERITY #endif /* Flags we recognize on the root, swap, home and srv partitions when diff --git a/src/test/test-dissect-image.c b/src/test/test-dissect-image.c index 0363ef8eb6..0512a15e88 100644 --- a/src/test/test-dissect-image.c +++ b/src/test/test-dissect-image.c @@ -43,7 +43,7 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } - r = dissect_image(d->fd, &m); + r = dissect_image(d->fd, NULL, 0, &m); if (r < 0) { log_error_errno(r, "Failed to dissect image: %m"); return EXIT_FAILURE; -- cgit v1.2.3-54-g00ecf