diff options
author | Lennart Poettering <lennart@poettering.net> | 2020-06-09 19:52:21 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-09 19:52:21 +0200 |
commit | 4f4f37b20d0598d424ff9187112cd791d325ac50 (patch) | |
tree | 419f4639148f7c1ff8e6597af8ff26624dcd6580 | |
parent | 2233c2703c91325f8c742daf2d37c1343f35998e (diff) | |
parent | e7cbe5cb9e7d246474dcee1d8e759ed3c8786913 (diff) | |
download | systemd-4f4f37b20d0598d424ff9187112cd791d325ac50.tar.gz |
Merge pull request #16046 from bluca/dissect_squashfs_verity
dissect: single-filesystem verity images with external hashdevice
-rw-r--r-- | man/systemd-nspawn.xml | 20 | ||||
-rw-r--r-- | src/core/namespace.c | 8 | ||||
-rw-r--r-- | src/dissect/dissect.c | 61 | ||||
-rw-r--r-- | src/gpt-auto-generator/gpt-auto-generator.c | 2 | ||||
-rw-r--r-- | src/nspawn/nspawn.c | 29 | ||||
-rw-r--r-- | src/portable/portable.c | 2 | ||||
-rw-r--r-- | src/shared/dissect-image.c | 161 | ||||
-rw-r--r-- | src/shared/dissect-image.h | 14 | ||||
-rw-r--r-- | src/shared/machine-image.c | 2 | ||||
-rw-r--r-- | src/test/test-dissect-image.c | 2 | ||||
l--------- | test/TEST-50-DISSECT/Makefile | 1 | ||||
-rwxr-xr-x | test/TEST-50-DISSECT/test.sh | 34 | ||||
-rw-r--r-- | test/units/testsuite-50.service | 7 | ||||
-rwxr-xr-x | test/units/testsuite-50.sh | 31 |
14 files changed, 276 insertions, 98 deletions
diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index f9cc5a8828..72d2f1e4ba 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -302,6 +302,10 @@ hash partitions are set up if the root hash for them is specified using the <option>--root-hash=</option> option.</para> + <para>Single file system images (i.e. file systems without a surrounding partition table) can be opened using + dm-verity if the integrity data is passed using the <option>--root-hash=</option> and + <option>--verity-data=</option> options.</para> + <para>Any other partitions, such as foreign partitions or swap partitions are not mounted. May not be specified together with <option>--directory=</option>, <option>--template=</option>.</para></listitem> </varlistentry> @@ -390,8 +394,20 @@ project='man-pages'><refentrytitle>xattr</refentrytitle><manvolnum>7</manvolnum></citerefentry>), then the root hash is read from it, also as formatted hexadecimal characters. If the extended file attribute is not found (or is not supported by the underlying file system), but a file with the <filename>.roothash</filename> suffix is - found next to the image file, bearing otherwise the same name, the root hash is read from it and automatically - used, also as formatted hexadecimal characters.</para></listitem> + found next to the image file, bearing otherwise the same name (except if the image has the + <filename>.raw</filename> suffix, in which case the root hash file must not have it in its name), the root hash + is read from it and automatically used, also as formatted hexadecimal characters.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--verity-data=</option></term> + + <listitem><para>Takes the path to a data integrity (dm-verity) file. This option enables data integrity checks + using dm-verity, if a root-hash is passed and if the used image itself does not contains the integrity data. + The integrity data must be matched by the root hash. If this option is not specified, but a file with the + <filename>.verity</filename> suffix is found next to the image file, bearing otherwise the same name (except if + the image has the <filename>.raw</filename> suffix, in which case the verity data file must not have it in its name), + the verity data is read from it and automatically used.</para></listitem> </varlistentry> <varlistentry> diff --git a/src/core/namespace.c b/src/core/namespace.c index 34eb469fb8..b02274b9a6 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -1264,6 +1264,7 @@ int setup_namespace( _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL; _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL; _cleanup_free_ void *root_hash = NULL; + _cleanup_free_ char *verity_data = NULL; MountEntry *m = NULL, *mounts = NULL; size_t n_mounts, root_hash_size = 0; bool require_prefix = false; @@ -1294,15 +1295,16 @@ int setup_namespace( if (r < 0) return log_debug_errno(r, "Failed to create loop device for root image: %m"); - r = root_hash_load(root_image, &root_hash, &root_hash_size); + r = verity_metadata_load(root_image, &root_hash, &root_hash_size, &verity_data); if (r < 0) return log_debug_errno(r, "Failed to load root hash: %m"); + dissect_image_flags |= verity_data ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0; - r = dissect_image(loop_device->fd, root_hash, root_hash_size, dissect_image_flags, &dissected_image); + r = dissect_image(loop_device->fd, root_hash, root_hash_size, verity_data, dissect_image_flags, &dissected_image); if (r < 0) return log_debug_errno(r, "Failed to dissect image: %m"); - r = dissected_image_decrypt(dissected_image, NULL, root_hash, root_hash_size, dissect_image_flags, &decrypted_image); + r = dissected_image_decrypt(dissected_image, NULL, root_hash, root_hash_size, verity_data, dissect_image_flags, &decrypted_image); if (r < 0) return log_debug_errno(r, "Failed to decrypt dissected image: %m"); } diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index e1418419f7..9ae632f226 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -12,6 +12,7 @@ #include "loop-util.h" #include "main-func.h" #include "parse-util.h" +#include "path-util.h" #include "string-util.h" #include "strv.h" #include "user-util.h" @@ -25,21 +26,25 @@ static const char *arg_image = NULL; static const char *arg_path = NULL; static DissectImageFlags arg_flags = DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK|DISSECT_IMAGE_FSCK; static void *arg_root_hash = NULL; +static char *arg_verity_data = NULL; static size_t arg_root_hash_size = 0; STATIC_DESTRUCTOR_REGISTER(arg_root_hash, freep); +STATIC_DESTRUCTOR_REGISTER(arg_verity_data, freep); static void help(void) { printf("%s [OPTIONS...] IMAGE\n" "%s [OPTIONS...] --mount IMAGE PATH\n" "Dissect a file system OS image.\n\n" - " -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" - " --fsck=BOOL Run fsck before mounting\n" - " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n" - " --root-hash=HASH Specify root hash for verity\n", + " -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" + " --fsck=BOOL Run fsck before mounting\n" + " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n" + " --root-hash=HASH Specify root hash for verity\n" + " --verity-data=PATH Specify data file with hash tree for verity if it is\n" + " not embedded in IMAGE\n", program_invocation_short_name, program_invocation_short_name); } @@ -51,16 +56,18 @@ static int parse_argv(int argc, char *argv[]) { ARG_DISCARD, ARG_ROOT_HASH, ARG_FSCK, + ARG_VERITY_DATA, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "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 }, - { "fsck", required_argument, NULL, ARG_FSCK }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "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 }, + { "fsck", required_argument, NULL, ARG_FSCK }, + { "verity-data", required_argument, NULL, ARG_VERITY_DATA }, {} }; @@ -127,6 +134,12 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_VERITY_DATA: + r = parse_path_argument_and_warn(optarg, false, &arg_verity_data); + if (r < 0) + return r; + break; + case ARG_FSCK: r = parse_boolean(optarg); if (r < 0) @@ -188,13 +201,13 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to set up loopback device: %m"); - if (!arg_root_hash) { - r = root_hash_load(arg_image, &arg_root_hash, &arg_root_hash_size); - if (r < 0) - return log_error_errno(r, "Failed to read root hash file for %s: %m", arg_image); - } + r = verity_metadata_load(arg_image, arg_root_hash ? NULL : &arg_root_hash, &arg_root_hash_size, + arg_verity_data ? NULL : &arg_verity_data); + if (r < 0) + return log_error_errno(r, "Failed to read verity artefacts for %s: %m", arg_image); + arg_flags |= arg_verity_data ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0; - r = dissect_image_and_warn(d->fd, arg_image, arg_root_hash, arg_root_hash_size, arg_flags, &m); + r = dissect_image_and_warn(d->fd, arg_image, arg_root_hash, arg_root_hash_size, arg_verity_data, arg_flags, &m); if (r < 0) return r; @@ -205,7 +218,6 @@ static int run(int argc, char *argv[]) { for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) { DissectedPartition *p = m->partitions + i; - int k; if (!p->found) continue; @@ -223,9 +235,8 @@ static int run(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 (dissected_image_can_do_verity(m, i)) + printf(" %s verity", dissected_image_has_verity(m, i) ? "with" : "without"); if (p->partno >= 0) printf(" on partition #%i", p->partno); @@ -268,7 +279,7 @@ static int run(int argc, char *argv[]) { } case ACTION_MOUNT: - r = dissected_image_decrypt_interactively(m, NULL, arg_root_hash, arg_root_hash_size, arg_flags, &di); + r = dissected_image_decrypt_interactively(m, NULL, arg_root_hash, arg_root_hash_size, arg_verity_data, arg_flags, &di); if (r < 0) return r; diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 2067eeaf89..a9478b9dbd 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -665,7 +665,7 @@ static int enumerate_partitions(dev_t devnum) { if (r <= 0) return r; - r = dissect_image(fd, NULL, 0, DISSECT_IMAGE_GPT_ONLY|DISSECT_IMAGE_NO_UDEV, &m); + r = dissect_image(fd, NULL, 0, NULL, DISSECT_IMAGE_GPT_ONLY|DISSECT_IMAGE_NO_UDEV, &m); if (r == -ENOPKG) { log_debug_errno(r, "No suitable partition table found, ignoring."); return 0; diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 8e8881749a..9a9ddecabb 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -199,6 +199,7 @@ 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|MOUNT_APPLY_TMPFS_TMP; static void *arg_root_hash = NULL; +static char *arg_verity_data = NULL; static size_t arg_root_hash_size = 0; static char **arg_syscall_whitelist = NULL; static char **arg_syscall_blacklist = NULL; @@ -242,6 +243,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_property, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_property_message, sd_bus_message_unrefp); STATIC_DESTRUCTOR_REGISTER(arg_parameters, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_root_hash, freep); +STATIC_DESTRUCTOR_REGISTER(arg_verity_data, freep); STATIC_DESTRUCTOR_REGISTER(arg_syscall_whitelist, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_syscall_blacklist, strv_freep); #if HAVE_SECCOMP @@ -303,6 +305,7 @@ static int help(void) { " --read-only Mount the root directory read-only\n" " --volatile[=MODE] Run the system in volatile mode\n" " --root-hash=HASH Specify verity root hash for root disk image\n" + " --verity-data=PATH Specify hash device for verity\n" " --pivot-root=PATH[:PATH]\n" " Pivot root to given directory in the container\n\n" "%3$sExecution:%4$s\n" @@ -663,6 +666,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_PIPE, ARG_OCI_BUNDLE, ARG_NO_PAGER, + ARG_VERITY_DATA, }; static const struct option options[] = { @@ -728,6 +732,7 @@ static int parse_argv(int argc, char *argv[]) { { "pipe", no_argument, NULL, ARG_PIPE }, { "oci-bundle", required_argument, NULL, ARG_OCI_BUNDLE }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "verity-data", required_argument, NULL, ARG_VERITY_DATA }, {} }; @@ -1316,6 +1321,12 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_VERITY_DATA: + r = parse_path_argument_and_warn(optarg, false, &arg_verity_data); + if (r < 0) + return r; + break; + case ARG_SYSTEM_CALL_FILTER: { bool negative; const char *items; @@ -5080,6 +5091,7 @@ static int run(int argc, char *argv[]) { } } else { + DissectImageFlags dissect_image_flags = DISSECT_IMAGE_REQUIRE_ROOT | DISSECT_IMAGE_RELAX_VAR_CHECK; assert(arg_image); assert(!arg_template); @@ -5129,13 +5141,13 @@ static int run(int argc, char *argv[]) { goto finish; } - if (!arg_root_hash) { - r = root_hash_load(arg_image, &arg_root_hash, &arg_root_hash_size); - if (r < 0) { - log_error_errno(r, "Failed to load root hash file for %s: %m", arg_image); - goto finish; - } + r = verity_metadata_load(arg_image, arg_root_hash ? NULL : &arg_root_hash, &arg_root_hash_size, + arg_verity_data ? NULL : &arg_verity_data); + if (r < 0) { + log_error_errno(r, "Failed to read verity artefacts for %s: %m", arg_image); + goto finish; } + dissect_image_flags |= arg_verity_data ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0; } if (!mkdtemp(tmprootdir)) { @@ -5161,7 +5173,8 @@ static int run(int argc, char *argv[]) { loop->fd, arg_image, arg_root_hash, arg_root_hash_size, - DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_RELAX_VAR_CHECK, + arg_verity_data, + dissect_image_flags, &dissected_image); if (r == -ENOPKG) { /* dissected_image_and_warn() already printed a brief error message. Extend on that with more details */ @@ -5179,7 +5192,7 @@ static int run(int argc, char *argv[]) { 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); + r = dissected_image_decrypt_interactively(dissected_image, NULL, arg_root_hash, arg_root_hash_size, arg_verity_data, 0, &decrypted_image); if (r < 0) goto finish; diff --git a/src/portable/portable.c b/src/portable/portable.c index a6aaa14ab6..7917ea3902 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -380,7 +380,7 @@ static int portable_extract_by_path( if (r < 0) return log_debug_errno(r, "Failed to create temporary directory: %m"); - r = dissect_image(d->fd, NULL, 0, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK, &m); + r = dissect_image(d->fd, NULL, 0, NULL, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK, &m); if (r == -ENOPKG) sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Couldn't identify a suitable partition table or file system in '%s'.", path); else if (r == -EADDRNOTAVAIL) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 8decac94b2..2c8a5d85bf 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -307,6 +307,7 @@ int dissect_image( int fd, const void *root_hash, size_t root_hash_size, + const char *verity_data, DissectImageFlags flags, DissectedImage **ret) { @@ -329,6 +330,7 @@ int dissect_image( assert(fd >= 0); assert(ret); assert(root_hash || root_hash_size == 0); + assert(!((flags & DISSECT_IMAGE_GPT_ONLY) && (flags & DISSECT_IMAGE_NO_PARTITION_TABLE))); /* Probes a disk image, and returns information about what it found in *ret. * @@ -391,8 +393,9 @@ int dissect_image( if (r < 0) return r; - if (!(flags & DISSECT_IMAGE_GPT_ONLY) && - (flags & DISSECT_IMAGE_REQUIRE_ROOT)) { + if ((!(flags & DISSECT_IMAGE_GPT_ONLY) && + (flags & DISSECT_IMAGE_REQUIRE_ROOT)) || + (flags & DISSECT_IMAGE_NO_PARTITION_TABLE)) { const char *usage = NULL; (void) blkid_probe_lookup_value(b, "USAGE", &usage, NULL); @@ -413,9 +416,13 @@ int dissect_image( if (r < 0) return r; + m->single_file_system = true; + m->verity = root_hash && verity_data; + m->can_verity = !!verity_data; + m->partitions[PARTITION_ROOT] = (DissectedPartition) { .found = true, - .rw = true, + .rw = !m->verity, .partno = -1, .architecture = _ARCHITECTURE_INVALID, .fstype = TAKE_PTR(t), @@ -424,11 +431,11 @@ int dissect_image( m->encrypted = streq_ptr(fstype, "crypto_LUKS"); - if (!streq(usage, "filesystem")) { - r = loop_wait_for_partitions_to_appear(fd, d, 0, flags, &e); - if (r < 0) - return r; - } + /* Even on a single partition we need to wait for udev to create the + * /dev/block/X:Y symlink to /dev/loopZ */ + r = loop_wait_for_partitions_to_appear(fd, d, 0, flags, &e); + if (r < 0) + return r; *ret = TAKE_PTR(m); return 0; @@ -1215,6 +1222,7 @@ static int verity_partition( DissectedPartition *v, const void *root_hash, size_t root_hash_size, + const char *verity_data, DissectImageFlags flags, DecryptedImage *d) { @@ -1223,18 +1231,20 @@ static int verity_partition( int r; assert(m); - assert(v); + assert(v || verity_data); if (!root_hash) return 0; if (!m->found || !m->node || !m->fstype) return 0; - if (!v->found || !v->node || !v->fstype) - return 0; + if (!verity_data) { + if (!v->found || !v->node || !v->fstype) + return 0; - if (!streq(v->fstype, "DM_verity_hash")) - 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) @@ -1243,7 +1253,7 @@ static int verity_partition( if (!GREEDY_REALLOC0(d->decrypted, d->n_allocated, d->n_decrypted + 1)) return -ENOMEM; - r = crypt_init(&cd, v->node); + r = crypt_init(&cd, verity_data ?: v->node); if (r < 0) return r; @@ -1276,6 +1286,7 @@ int dissected_image_decrypt( const char *passphrase, const void *root_hash, size_t root_hash_size, + const char *verity_data, DissectImageFlags flags, DecryptedImage **ret) { @@ -1322,7 +1333,7 @@ int dissected_image_decrypt( k = PARTITION_VERITY_OF(i); if (k >= 0) { - r = verity_partition(p, m->partitions + k, root_hash, root_hash_size, flags, d); + r = verity_partition(p, m->partitions + k, root_hash, root_hash_size, verity_data, flags, d); if (r < 0) return r; } @@ -1347,6 +1358,7 @@ int dissected_image_decrypt_interactively( const char *passphrase, const void *root_hash, size_t root_hash_size, + const char *verity_data, DissectImageFlags flags, DecryptedImage **ret) { @@ -1357,7 +1369,7 @@ int dissected_image_decrypt_interactively( n--; for (;;) { - r = dissected_image_decrypt(m, passphrase, root_hash, root_hash_size, flags, ret); + r = dissected_image_decrypt(m, passphrase, root_hash, root_hash_size, verity_data, flags, ret); if (r >= 0) return r; if (r == -EKEYREJECTED) @@ -1409,56 +1421,85 @@ int decrypted_image_relinquish(DecryptedImage *d) { return 0; } -int root_hash_load(const char *image, void **ret, size_t *ret_size) { - _cleanup_free_ char *text = NULL; - _cleanup_free_ void *k = NULL; - size_t l; +int verity_metadata_load(const char *image, void **ret_roothash, size_t *ret_roothash_size, char **ret_verity_data) { + _cleanup_free_ char *verity_filename = NULL; + _cleanup_free_ void *roothash_decoded = NULL; + size_t roothash_decoded_size = 0; int r; assert(image); - assert(ret); - assert(ret_size); if (is_device_path(image)) { /* If we are asked to load the root hash for a device node, exit early */ - *ret = NULL; - *ret_size = 0; + if (ret_roothash) + *ret_roothash = NULL; + if (ret_roothash_size) + *ret_roothash_size = 0; + if (ret_verity_data) + *ret_verity_data = NULL; return 0; } - r = getxattr_malloc(image, "user.verity.roothash", &text, true); - if (r < 0) { - char *fn, *e, *n; - - if (!IN_SET(r, -ENODATA, -EOPNOTSUPP, -ENOENT)) - return r; + if (ret_verity_data) { + char *e; - fn = newa(char, strlen(image) + STRLEN(".roothash") + 1); - n = stpcpy(fn, image); - e = endswith(fn, ".raw"); + verity_filename = new(char, strlen(image) + STRLEN(".verity") + 1); + if (!verity_filename) + return -ENOMEM; + strcpy(verity_filename, image); + e = endswith(verity_filename, ".raw"); if (e) - n = e; - - strcpy(n, ".roothash"); + strcpy(e, ".verity"); + else + strcat(verity_filename, ".verity"); - r = read_one_line_file(fn, &text); - if (r == -ENOENT) { - *ret = NULL; - *ret_size = 0; - return 0; + r = access(verity_filename, F_OK); + if (r < 0) { + if (errno != ENOENT) + return -errno; + verity_filename = mfree(verity_filename); } - if (r < 0) - return r; } - r = unhexmem(text, strlen(text), &k, &l); - if (r < 0) - return r; - if (l < sizeof(sd_id128_t)) - return -EINVAL; + if (ret_roothash) { + _cleanup_free_ char *text = NULL; + assert(ret_roothash_size); + + r = getxattr_malloc(image, "user.verity.roothash", &text, true); + if (r < 0) { + char *fn, *e, *n; + + if (!IN_SET(r, -ENODATA, -EOPNOTSUPP, -ENOENT)) + return r; + + fn = newa(char, strlen(image) + STRLEN(".roothash") + 1); + n = stpcpy(fn, image); + e = endswith(fn, ".raw"); + if (e) + n = e; - *ret = TAKE_PTR(k); - *ret_size = l; + strcpy(n, ".roothash"); + + r = read_one_line_file(fn, &text); + if (r < 0 && r != -ENOENT) + return r; + } + + if (text) { + r = unhexmem(text, strlen(text), &roothash_decoded, &roothash_decoded_size); + if (r < 0) + return r; + if (roothash_decoded_size < sizeof(sd_id128_t)) + return -EINVAL; + } + } + + if (ret_roothash) { + *ret_roothash = TAKE_PTR(roothash_decoded); + *ret_roothash_size = roothash_decoded_size; + } + if (ret_verity_data) + *ret_verity_data = TAKE_PTR(verity_filename); return 1; } @@ -1617,6 +1658,7 @@ int dissect_image_and_warn( const char *name, const void *root_hash, size_t root_hash_size, + const char *verity_data, DissectImageFlags flags, DissectedImage **ret) { @@ -1631,7 +1673,7 @@ int dissect_image_and_warn( name = buffer; } - r = dissect_image(fd, root_hash, root_hash_size, flags, ret); + r = dissect_image(fd, root_hash, root_hash_size, verity_data, flags, ret); switch (r) { @@ -1661,6 +1703,23 @@ int dissect_image_and_warn( } } +bool dissected_image_can_do_verity(const DissectedImage *image, unsigned partition_designator) { + if (image->single_file_system) + return partition_designator == PARTITION_ROOT && image->can_verity; + + return PARTITION_VERITY_OF(partition_designator) >= 0; +} + +bool dissected_image_has_verity(const DissectedImage *image, unsigned partition_designator) { + int k; + + if (image->single_file_system) + return partition_designator == PARTITION_ROOT && image->verity; + + k = PARTITION_VERITY_OF(partition_designator); + return k >= 0 && image->partitions[k].found; +} + 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 6a666ca7c7..92d223cfec 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -63,12 +63,14 @@ typedef enum DissectImageFlags { DISSECT_IMAGE_NO_UDEV = 1 << 9, /* Don't wait for udev initializing things */ DISSECT_IMAGE_RELAX_VAR_CHECK = 1 << 10, /* Don't insist that the UUID of /var is hashed from /etc/machine-id */ DISSECT_IMAGE_FSCK = 1 << 11, /* File system check the partition before mounting (no effect when combined with DISSECT_IMAGE_READ_ONLY) */ + DISSECT_IMAGE_NO_PARTITION_TABLE = 1 << 12, /* Only recognize single file system images */ } DissectImageFlags; struct DissectedImage { bool encrypted:1; bool verity:1; /* verity available and usable */ bool can_verity:1; /* verity available, but not necessarily used */ + bool single_file_system:1; /* MBR/GPT or single file system */ DissectedPartition partitions[_PARTITION_DESIGNATOR_MAX]; @@ -79,14 +81,14 @@ struct DissectedImage { }; int probe_filesystem(const char *node, char **ret_fstype); -int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectImageFlags flags, DissectedImage **ret); -int dissect_image_and_warn(int fd, const char *name, const void *root_hash, size_t root_hash_size, DissectImageFlags flags, DissectedImage **ret); +int dissect_image(int fd, const void *root_hash, size_t root_hash_size, const char *verity_data, DissectImageFlags flags, DissectedImage **ret); +int dissect_image_and_warn(int fd, const char *name, const void *root_hash, size_t root_hash_size, const char *verity_data, DissectImageFlags flags, 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, 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_decrypt(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, const char *verity_data, DissectImageFlags flags, DecryptedImage **ret); +int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, const char *verity_data, DissectImageFlags flags, DecryptedImage **ret); int dissected_image_mount(DissectedImage *m, const char *dest, uid_t uid_shift, DissectImageFlags flags); int dissected_image_acquire_metadata(DissectedImage *m); @@ -98,4 +100,6 @@ int decrypted_image_relinquish(DecryptedImage *d); const char* partition_designator_to_string(int i) _const_; int partition_designator_from_string(const char *name) _pure_; -int root_hash_load(const char *image, void **ret, size_t *ret_size); +int verity_metadata_load(const char *image, void **ret_roothash, size_t *ret_roothash_size, char **ret_verity_data); +bool dissected_image_can_do_verity(const DissectedImage *image, unsigned partition_designator); +bool dissected_image_has_verity(const DissectedImage *image, unsigned partition_designator); diff --git a/src/shared/machine-image.c b/src/shared/machine-image.c index b45efcd1e6..1b7cfb5028 100644 --- a/src/shared/machine-image.c +++ b/src/shared/machine-image.c @@ -1171,7 +1171,7 @@ int image_read_metadata(Image *i) { if (r < 0) return r; - r = dissect_image(d->fd, NULL, 0, DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_RELAX_VAR_CHECK, &m); + r = dissect_image(d->fd, NULL, 0, NULL, DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_RELAX_VAR_CHECK, &m); if (r < 0) return r; diff --git a/src/test/test-dissect-image.c b/src/test/test-dissect-image.c index a1ccf605b1..13ff8add5d 100644 --- a/src/test/test-dissect-image.c +++ b/src/test/test-dissect-image.c @@ -28,7 +28,7 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } - r = dissect_image(d->fd, NULL, 0, DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_RELAX_VAR_CHECK, &m); + r = dissect_image(d->fd, NULL, 0, NULL, DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_RELAX_VAR_CHECK, &m); if (r < 0) { log_error_errno(r, "Failed to dissect image: %m"); return EXIT_FAILURE; diff --git a/test/TEST-50-DISSECT/Makefile b/test/TEST-50-DISSECT/Makefile new file mode 120000 index 0000000000..e9f93b1104 --- /dev/null +++ b/test/TEST-50-DISSECT/Makefile @@ -0,0 +1 @@ +../TEST-01-BASIC/Makefile
\ No newline at end of file diff --git a/test/TEST-50-DISSECT/test.sh b/test/TEST-50-DISSECT/test.sh new file mode 100755 index 0000000000..cfe97aaa2d --- /dev/null +++ b/test/TEST-50-DISSECT/test.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +set -e +TEST_DESCRIPTION="test systemd-dissect" +IMAGE_NAME="dissect" +TEST_NO_NSPAWN=1 + +. $TEST_BASE_DIR/test-functions + +command -v mksquashfs >/dev/null 2>&1 || exit 0 +command -v veritysetup >/dev/null 2>&1 || exit 0 + +# Need loop devices for systemd-dissect +test_create_image() { + create_empty_image_rootdir + + # Create what will eventually be our root filesystem onto an overlay + # If some pieces are missing from the host, skip rather than fail + ( + LOG_LEVEL=5 + setup_basic_environment + mask_supporting_services + + instmods loop =block + instmods squashfs =squashfs + instmods dm_verity =md + generate_module_dependencies + inst_binary mksquashfs + inst_binary veritysetup + ) +} + +do_test "$@" 50 diff --git a/test/units/testsuite-50.service b/test/units/testsuite-50.service new file mode 100644 index 0000000000..5a10a6418b --- /dev/null +++ b/test/units/testsuite-50.service @@ -0,0 +1,7 @@ +[Unit] +Description=TEST-50-DISSECT + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh new file mode 100755 index 0000000000..9f35ffd653 --- /dev/null +++ b/test/units/testsuite-50.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +set -ex +set -o pipefail + +cd /tmp + +image=$(mktemp -d -t -p /tmp tmp.XXXXXX) +if [ -z "${image}" ] || [ ! -d "${image}" ]; then + echo "Could not create temporary directory with mktemp under /tmp" + exit 1 +fi + +mkdir -p ${image}/usr/lib ${image}/etc +cp /usr/lib/os-release ${image}/usr/lib/ +cp /etc/machine-id /etc/os-release ${image}/etc/ +mksquashfs ${image} ${image}.raw +veritysetup format ${image}.raw ${image}.verity | grep '^Root hash:' | cut -f2 | tr -d '\n' > ${image}.roothash + +/usr/lib/systemd/systemd-dissect ${image}.raw | grep -q -F "Found read-only 'root' partition of type squashfs with verity" +/usr/lib/systemd/systemd-dissect ${image}.raw | grep -q -F -f /usr/lib/os-release + +mv ${image}.verity ${image}.fooverity +mv ${image}.roothash ${image}.foohash +/usr/lib/systemd/systemd-dissect ${image}.raw --root-hash=`cat ${image}.foohash` --verity-data=${image}.fooverity | grep -q -F "Found read-only 'root' partition of type squashfs with verity" +/usr/lib/systemd/systemd-dissect ${image}.raw --root-hash=`cat ${image}.foohash` --verity-data=${image}.fooverity | grep -q -F -f /usr/lib/os-release + +echo OK > /testok + +exit 0 |