dissect image policy tweaks (#39168)
This commit is contained in:
+6
-2
@@ -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 `<ROOTHASH>-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 `<ROOTHASH>-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`:
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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;
|
||||
|
||||
+81
-30
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user