From: Lennart Poettering Date: Fri, 19 Sep 2025 16:12:55 +0000 (+0200) Subject: dissect-image: take policy into consideration when unlocking verity, too X-Git-Tag: v259-rc1~339^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d20dff2814c26fad2e568758aa7e5e437908fb0f;p=thirdparty%2Fsystemd.git dissect-image: take policy into consideration when unlocking verity, too Previously, we'd take the image policy only into consideration when dissecting the mage, but for the unlock/verity step we'd go via best effort. Change that. This means we can now enforce policies such as activating by root hash only even if a signature exists and similar. Also, introduce a separate error code if we try to unlock a Verity volume but have no root hash. Previously we'd return ENOKEY for that, exactly like we do for encrypted volumes where we have no passparse. The interctive unlock loop dissected_image_decrypt_interactively() is otherwise very confused and will ask for a root hash, which makes no sense. Hence use two distinct errors for this. --- diff --git a/src/core/namespace.c b/src/core/namespace.c index 725f954679c..16159fdf84d 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -2589,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 ff9eabb600f..7d921959cb1 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 4f37bbd0815..74b97d7371a 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 afe7ebd500c..12e0787aaa6 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 60e10b5747a..e44c307bbf1 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."); + + /* 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."); - return sym_crypt_activate_by_volume_key( + /* 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,6 +3098,7 @@ int dissected_image_decrypt( DissectedImage *m, const char *passphrase, const VeritySettings *verity, + const ImagePolicy *policy, DissectImageFlags flags) { #if HAVE_LIBCRYPTSETUP @@ -3072,11 +3111,13 @@ int dissected_image_decrypt( /* 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)) @@ -3101,13 +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) { - 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; } @@ -3120,7 +3163,6 @@ int dissected_image_decrypt( } m->decrypted_image = TAKE_PTR(d); - return 1; #else return -EOPNOTSUPP; @@ -3131,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; @@ -3140,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), @@ -4277,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; @@ -4429,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 2be4c342953..cd87ff769ac 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 88278fc688d..ef291489261 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.mountfsd.sh b/test/units/TEST-50-DISSECT.mountfsd.sh index bdd9a773261..067ed238e8c 100755 --- a/test/units/TEST-50-DISSECT.mountfsd.sh +++ b/test/units/TEST-50-DISSECT.mountfsd.sh @@ -60,9 +60,17 @@ if (SYSTEMD_LOG_TARGET=console varlinkctl call \ exit 0 fi +# 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 +# This should work now +systemd-dissect --image-policy='root=signed:=absent+unused' --mtree /var/tmp/unpriv.raw >/dev/null +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 runas testuser systemd-dissect --mtree /var/tmp/unpriv.raw >/tmp/unpriv2.raw.mtree