diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index eeb7be0ac1..3a35c1b9aa 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -123,8 +123,12 @@ All tools: * `$SYSTEMD_NETLINK_DEFAULT_TIMEOUT` — specifies the default timeout of waiting replies for netlink messages from the kernel. Defaults to 25 seconds. -* `$SYSTEMD_VERITY_SHARING=0` — if set, sharing dm-verity devices by - using a stable `-verity` device mapper name will be disabled. +* `$SYSTEMD_VERITY_SHARING=` — takes a boolean. If set, overrides whether + dm-verity devices shall be shared between multiple components by using a + stable `-verity` device mapper name. The default for this depends + on the subsystem in question. Usually, + RootImage=/ExtensionImages=/MountImages= in unit files default to enabled, + while other uses default to disabled for this. `systemctl`: diff --git a/src/core/namespace.c b/src/core/namespace.c index a90711f5ce..16159fdf84 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -2523,7 +2523,8 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) { DISSECT_IMAGE_GROWFS | DISSECT_IMAGE_ADD_PARTITION_DEVICES | DISSECT_IMAGE_PIN_PARTITION_DEVICES | - DISSECT_IMAGE_ALLOW_USERSPACE_VERITY; + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY | + DISSECT_IMAGE_VERITY_SHARE; int r; assert(p); @@ -2588,6 +2589,7 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) { dissected_image, NULL, p->verity, + p->root_image_policy, dissect_image_flags); 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 ff9eabb600..7d921959cb 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -2281,6 +2281,7 @@ static int run(int argc, char *argv[]) { r = dissected_image_decrypt_interactively( m, NULL, &arg_verity_settings, + arg_image_policy, arg_flags); if (r < 0) return r; diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c index 4f37bbd081..74b97d7371 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -497,6 +497,7 @@ static int vl_method_mount_image( di, p.password, &verity, + use_policy, dissect_flags); if (r == -ENOKEY) /* new dm-verity userspace returns ENOKEY if the dm-verity signature key is not in * key chain. That's great. */ diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index afe7ebd500..12e0787aaa 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -6301,6 +6301,7 @@ static int run(int argc, char *argv[]) { dissected_image, NULL, &arg_verity_settings, + arg_image_policy ?: &image_policy_container, dissect_image_flags); if (r < 0) goto finish; diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 44c4408079..e44c307bbf 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -2547,6 +2547,7 @@ static int decrypt_partition( DissectedPartition *m, const char *passphrase, DissectImageFlags flags, + PartitionPolicyFlags policy_flags, DecryptedImage *d) { _cleanup_free_ char *node = NULL, *name = NULL; @@ -2566,6 +2567,9 @@ static int decrypt_partition( if (!passphrase) return -ENOKEY; + if (!FLAGS_SET(policy_flags, PARTITION_POLICY_ENCRYPTED)) + return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), "Attempted to unlock partition via LUKS, but it's prohibited."); + r = dlopen_cryptsetup(); if (r < 0) return r; @@ -2672,6 +2676,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(char *, dm_deferred_remove_clean); static int validate_signature_userspace(const VeritySettings *verity, DissectImageFlags flags) { int r; + /* Returns > 0 if signature checks out, == 0 if not, < 0 on unexpected errors */ + if (!FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_USERSPACE_VERITY)) { log_debug("Userspace dm-verity signature authentication disabled via flag."); return 0; @@ -2778,7 +2784,8 @@ static int do_crypt_activate_verity( struct crypt_device *cd, const char *name, const VeritySettings *verity, - DissectImageFlags flags) { + DissectImageFlags flags, + PartitionPolicyFlags policy_flags) { bool check_signature; int r, k; @@ -2787,7 +2794,7 @@ static int do_crypt_activate_verity( assert(name); assert(verity); - if (verity->root_hash_sig) { + if (verity->root_hash_sig && FLAGS_SET(policy_flags, PARTITION_POLICY_SIGNED)) { r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_SIGNATURE"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_SIGNATURE"); @@ -2797,7 +2804,6 @@ static int do_crypt_activate_verity( check_signature = false; if (check_signature) { - #if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY /* First, if we have support for signed keys in the kernel, then try that first. */ r = sym_crypt_activate_by_signed_key( @@ -2808,10 +2814,18 @@ static int do_crypt_activate_verity( verity->root_hash_sig, verity->root_hash_sig_size, CRYPT_ACTIVATE_READONLY); - if (r >= 0) - return r; + if (r >= 0) { + log_debug("Verity activation via kernel signature logic worked."); + return 0; + } log_debug_errno(r, "Validation of dm-verity signature failed via the kernel, trying userspace validation instead: %m"); + + /* Let's mangle ENOKEY → EDESTADDRREQ, so that we return a clear, recognizable error if + * there's a signature we don't recognize, that is distinct from the LUKS/encryption + * -ENOKEY, which means "password required, but I have none". */ + if (r == -ENOKEY) + r = -EDESTADDRREQ; #else log_debug("Activation of verity device with signature requested, but not supported via the kernel by %s due to missing crypt_activate_by_signed_key(), trying userspace validation instead.", program_invocation_short_name); @@ -2825,18 +2839,36 @@ static int do_crypt_activate_verity( * as the device-mapper is finicky around concurrent activations of the same volume */ k = validate_signature_userspace(verity, flags); if (k < 0) - return r < 0 ? r : k; - if (k == 0) - return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(ENOKEY), - "Activation of signed Verity volume worked neither via the kernel nor in userspace, can't activate."); - } + return k; + if (k == 0) { + log_debug("Activation of signed Verity volume worked neither via the kernel nor in userspace, can't activate."); - return sym_crypt_activate_by_volume_key( + /* So if we had a signature and we're supposed to exclusively allow + * signature-based activation, then return the error now */ + if (!FLAGS_SET(policy_flags, PARTITION_POLICY_VERITY)) + return r < 0 ? r : -EDESTADDRREQ; + + log_debug("Activation of signed Verity volume without validating signature is permitted by policy. Continuing."); + } else + log_debug("Verity activation via userspace signature logic worked, activating by root hash."); + + /* Otherwise let's see what signature-less activation results in. */ + + } else if (!FLAGS_SET(policy_flags, PARTITION_POLICY_VERITY)) + return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), + "No-signature activation of Verity volume not allowed by policy, refusing."); + + r = sym_crypt_activate_by_volume_key( cd, name, verity->root_hash, verity->root_hash_size, CRYPT_ACTIVATE_READONLY); + if (r < 0) + return log_debug_errno(r, "Activation of Verity via root hash failed: %m"); + + log_debug("Activation of Verity via root hash succeeded."); + return 0; } static usec_t verity_timeout(void) { @@ -2863,10 +2895,11 @@ static usec_t verity_timeout(void) { static int verity_partition( PartitionDesignator designator, - DissectedPartition *m, - DissectedPartition *v, + DissectedPartition *m, /* data partition */ + DissectedPartition *v, /* verity partition */ const VeritySettings *verity, DissectImageFlags flags, + PartitionPolicyFlags policy_flags, DecryptedImage *d) { _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; @@ -2893,6 +2926,11 @@ static int verity_partition( return 0; } + if (!(policy_flags & (PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED))) { + log_debug("Attempted to unlock partition via Verity, but it's prohibited, skipping."); + return 0; + } + r = dlopen_cryptsetup(); if (r < 0) return r; @@ -2944,7 +2982,7 @@ static int verity_partition( goto check; /* The device already exists. Let's check it. */ /* The symlink to the device node does not exist yet. Assume not activated, and let's activate it. */ - r = do_crypt_activate_verity(cd, name, verity, flags); + r = do_crypt_activate_verity(cd, name, verity, flags, policy_flags); if (r >= 0) goto try_open; /* The device is activated. Let's open it. */ /* libdevmapper can return EINVAL when the device is already in the activation stage. @@ -3038,7 +3076,7 @@ static int verity_partition( */ sym_crypt_free(cd); cd = NULL; - return verity_partition(designator, m, v, verity, flags & ~DISSECT_IMAGE_VERITY_SHARE, d); + return verity_partition(designator, m, v, verity, flags & ~DISSECT_IMAGE_VERITY_SHARE, policy_flags, d); } return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "All attempts to activate verity device %s failed.", name); @@ -3060,23 +3098,26 @@ int dissected_image_decrypt( DissectedImage *m, const char *passphrase, const VeritySettings *verity, + const ImagePolicy *policy, DissectImageFlags flags) { #if HAVE_LIBCRYPTSETUP _cleanup_(decrypted_image_unrefp) DecryptedImage *d = NULL; - int r; #endif + int r; assert(m); assert(!verity || verity->root_hash || verity->root_hash_size == 0); /* Returns: * - * = 0 → There was nothing to decrypt - * > 0 → Decrypted successfully - * -ENOKEY → There's something to decrypt but no key was supplied - * -EKEYREJECTED → Passed key was not correct - * -EBUSY → Generic Verity error (kernel is not very explanatory) + * = 0 → There was nothing to decrypt/setup + * > 0 → Decrypted/setup successfully + * -ENOKEY → dm-crypt: there's something to decrypt but no decryption key was supplied + * -EKEYREJECTED → dm-crypt: Passed key was not correct + * -EDESTADDRREQ → dm-verity: there's something to setup but no signature was supplied + * -EBUSY → dm-verity: Generic Verity error (kernel is not very explanatory) + * -ERFKILL → image policy not compatible with request */ if (verity && verity->root_hash && verity->root_hash_size < sizeof(sd_id128_t)) @@ -3085,6 +3126,10 @@ int dissected_image_decrypt( if (!m->encrypted && !m->verity_ready) return 0; + r = secure_getenv_bool("SYSTEMD_VERITY_SHARING"); + if (r >= 0) + SET_FLAG(flags, DISSECT_IMAGE_VERITY_SHARE, r); + #if HAVE_LIBCRYPTSETUP r = decrypted_image_new(&d); if (r < 0) @@ -3097,15 +3142,15 @@ int dissected_image_decrypt( if (!p->found) continue; - r = decrypt_partition(p, passphrase, flags, d); + PartitionPolicyFlags fl = image_policy_get_exhaustively(policy, i); + + r = decrypt_partition(p, passphrase, flags, fl, d); if (r < 0) return r; k = partition_verity_hash_of(i); if (k >= 0) { - flags |= getenv_bool("SYSTEMD_VERITY_SHARING") != 0 ? DISSECT_IMAGE_VERITY_SHARE : 0; - - r = verity_partition(i, p, m->partitions + k, verity, flags, d); + r = verity_partition(i, p, m->partitions + k, verity, flags, fl, d); if (r < 0) return r; } @@ -3118,7 +3163,6 @@ int dissected_image_decrypt( } m->decrypted_image = TAKE_PTR(d); - return 1; #else return -EOPNOTSUPP; @@ -3129,6 +3173,7 @@ int dissected_image_decrypt_interactively( DissectedImage *m, const char *passphrase, const VeritySettings *verity, + const ImagePolicy *image_policy, DissectImageFlags flags) { _cleanup_strv_free_erase_ char **z = NULL; @@ -3138,13 +3183,17 @@ int dissected_image_decrypt_interactively( n--; for (;;) { - r = dissected_image_decrypt(m, passphrase, verity, flags); + r = dissected_image_decrypt(m, passphrase, verity, image_policy, flags); if (r >= 0) return r; if (r == -EKEYREJECTED) log_error_errno(r, "Incorrect passphrase, try again!"); + else if (r == -EDESTADDRREQ) + return log_error_errno(r, "Image lacks recognized signature."); + else if (r == -ERFKILL) + return log_error_errno(r, "Unlocking of Verity/LUKS volumes not permitted by policy."); else if (r != -ENOKEY) - return log_error_errno(r, "Failed to decrypt image: %m"); + return log_error_errno(r, "Failed to decrypt/set up image: %m"); if (--n < 0) return log_error_errno(SYNTHETIC_ERRNO(EKEYREJECTED), @@ -4275,7 +4324,7 @@ int mount_image_privately_interactively( if (r < 0) return r; - r = dissected_image_decrypt_interactively(dissected_image, NULL, &verity, flags); + r = dissected_image_decrypt_interactively(dissected_image, NULL, &verity, image_policy, flags); if (r < 0) return r; @@ -4379,7 +4428,8 @@ int verity_dissect_and_mount( (relax_extension_release_check ? DISSECT_IMAGE_RELAX_EXTENSION_CHECK : 0) | DISSECT_IMAGE_ADD_PARTITION_DEVICES | DISSECT_IMAGE_PIN_PARTITION_DEVICES | - DISSECT_IMAGE_ALLOW_USERSPACE_VERITY; + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY | + DISSECT_IMAGE_VERITY_SHARE; /* Note that we don't use loop_device_make here, as the FD is most likely O_PATH which would not be * accepted by LOOP_CONFIGURE, so just let loop_device_make_by_path reopen it as a regular FD. */ @@ -4426,6 +4476,7 @@ int verity_dissect_and_mount( dissected_image, NULL, verity, + image_policy, dissect_image_flags); if (r < 0) return log_debug_errno(r, "Failed to decrypt dissected image: %m"); diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 2be4c34295..cd87ff769a 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -171,8 +171,8 @@ void dissected_image_close(DissectedImage *m); DissectedImage* dissected_image_unref(DissectedImage *m); DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref); -int dissected_image_decrypt(DissectedImage *m, const char *passphrase, const VeritySettings *verity, DissectImageFlags flags); -int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const VeritySettings *verity, DissectImageFlags flags); +int dissected_image_decrypt(DissectedImage *m, const char *passphrase, const VeritySettings *verity, const ImagePolicy *image_policy, DissectImageFlags flags); +int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const VeritySettings *verity, const ImagePolicy *image_policy, DissectImageFlags flags); int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags); int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags); diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 88278fc688..ef29148926 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -1862,7 +1862,7 @@ static int merge_subprocess( if (r < 0) return r; - r = dissected_image_decrypt(m, /* passphrase= */ NULL, &verity_settings, flags); + r = dissected_image_decrypt(m, /* passphrase= */ NULL, &verity_settings, pick_image_policy(img), flags); if (r < 0) return r; diff --git a/test/units/TEST-50-DISSECT.dissect.sh b/test/units/TEST-50-DISSECT.dissect.sh index bd0ce438a0..d25a4cb02d 100755 --- a/test/units/TEST-50-DISSECT.dissect.sh +++ b/test/units/TEST-50-DISSECT.dissect.sh @@ -69,12 +69,12 @@ mv "$MINIMAL_IMAGE.fooverity" "$MINIMAL_IMAGE.verity" mv "$MINIMAL_IMAGE.foohash" "$MINIMAL_IMAGE.roothash" mkdir -p "$IMAGE_DIR/mount" "$IMAGE_DIR/mount2" -systemd-dissect --mount "$MINIMAL_IMAGE.raw" "$IMAGE_DIR/mount" +SYSTEMD_VERITY_SHARING=1 systemd-dissect --mount "$MINIMAL_IMAGE.raw" "$IMAGE_DIR/mount" grep -q -F -f "$OS_RELEASE" "$IMAGE_DIR/mount/usr/lib/os-release" grep -q -F -f "$OS_RELEASE" "$IMAGE_DIR/mount/etc/os-release" grep -q -F "MARKER=1" "$IMAGE_DIR/mount/usr/lib/os-release" # Verity volume should be shared (opened only once) -systemd-dissect --mount "$MINIMAL_IMAGE.raw" "$IMAGE_DIR/mount2" +SYSTEMD_VERITY_SHARING=1 systemd-dissect --mount "$MINIMAL_IMAGE.raw" "$IMAGE_DIR/mount2" verity_count=$(find /dev/mapper/ -name "*verity*" | wc -l) # In theory we should check that count is exactly one. In practice, libdevmapper # randomly and unpredictably fails with an unhelpful EINVAL when a device is open diff --git a/test/units/TEST-50-DISSECT.mountfsd.sh b/test/units/TEST-50-DISSECT.mountfsd.sh index bdd9a77326..d2ce67714a 100755 --- a/test/units/TEST-50-DISSECT.mountfsd.sh +++ b/test/units/TEST-50-DISSECT.mountfsd.sh @@ -60,8 +60,23 @@ if (SYSTEMD_LOG_TARGET=console varlinkctl call \ exit 0 fi +# This should work without the key +systemd-dissect --image-policy='root=verity:=absent+unused' --mtree /var/tmp/unpriv.raw >/dev/null +systemd-dissect --image-policy='root=verity+signed:=absent+unused' --mtree /var/tmp/unpriv.raw >/dev/null + +# This should fail before we install the key +(! systemd-dissect --image-policy='root=signed:=absent+unused' --mtree /var/tmp/unpriv.raw >/dev/null) + # Install key in keychain -cp /tmp/test-50-unpriv-cert.crt /run/verity.d +mkdir -p /run/verity.d +cp /tmp/test-50-unpriv-cert.crt /run/verity.d/ + +# This should work now +systemd-dissect --image-policy='root=signed:=absent+unused' --mtree /var/tmp/unpriv.raw >/dev/null + +# This should still work +systemd-dissect --image-policy='root=verity:=absent+unused' --mtree /var/tmp/unpriv.raw >/dev/null +systemd-dissect --image-policy='root=verity+signed:=absent+unused' --mtree /var/tmp/unpriv.raw >/dev/null # Now run unpriv again, should be OK now. runas testuser systemd-dissect /var/tmp/unpriv.raw