#include <sys/wait.h>
#include <sysexits.h>
+#if HAVE_OPENSSL
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+#endif
+
#include "sd-device.h"
#include "sd-id128.h"
#include "ask-password-api.h"
#include "blkid-util.h"
#include "blockdev-util.h"
+#include "chase-symlinks.h"
+#include "conf-files.h"
#include "copy.h"
#include "cryptsetup-util.h"
#include "def.h"
#include "hostname-setup.h"
#include "id128-util.h"
#include "import-util.h"
-#include "mkdir.h"
+#include "io-util.h"
+#include "mkdir-label.h"
#include "mount-util.h"
#include "mountpoint-util.h"
#include "namespace-util.h"
#include "nulstr-util.h"
+#include "openssl-util.h"
#include "os-util.h"
#include "path-util.h"
#include "process-util.h"
(void) sd_device_get_sysname(device, &sn);
log_device_debug(device,
- "Waiting for device '%s' to initialize for %s.", strna(sn), FORMAT_TIMESPAN(left, 0));
+ "Will wait up to %s for '%s' to initialize…", FORMAT_TIMESPAN(left, 0), strna(sn));
}
if (left != USEC_INFINITY)
DissectedImage **ret) {
#if HAVE_BLKID
-#ifdef GPT_ROOT_NATIVE
sd_id128_t root_uuid = SD_ID128_NULL, root_verity_uuid = SD_ID128_NULL;
-#endif
-#ifdef GPT_USR_NATIVE
sd_id128_t usr_uuid = SD_ID128_NULL, usr_verity_uuid = SD_ID128_NULL;
-#endif
bool is_gpt, is_mbr, multiple_generic = false,
generic_rw = false, /* initialize to appease gcc */
generic_growfs = false;
/* If the verity data declares it's for the /usr partition, then search for that, in all
* other cases assume it's for the root partition. */
-#ifdef GPT_USR_NATIVE
if (verity->designator == PARTITION_USR) {
usr_uuid = fsuuid;
usr_verity_uuid = vuuid;
} else {
-#endif
-#ifdef GPT_ROOT_NATIVE
root_uuid = fsuuid;
root_verity_uuid = vuuid;
-#endif
-#ifdef GPT_USR_NATIVE
}
-#endif
}
if (fstat(fd, &st) < 0)
if (r != 0)
return errno_or_else(EIO);
- m = new0(DissectedImage, 1);
+ m = new(DissectedImage, 1);
if (!m)
return -ENOMEM;
+ *m = (DissectedImage) {
+ .has_init_system = -1,
+ };
+
r = sd_device_get_sysname(d, &sysname);
if (r < 0)
return log_debug_errno(r, "Failed to get device sysname: %m");
designator = PARTITION_XBOOTLDR;
rw = !(pflags & GPT_FLAG_READ_ONLY);
growfs = FLAGS_SET(pflags, GPT_FLAG_GROWFS);
- }
-#ifdef GPT_ROOT_NATIVE
- else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) {
+
+ } else if (gpt_partition_type_is_root(type_id)) {
check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY|GPT_FLAG_GROWFS);
if (!sd_id128_is_null(root_uuid) && !sd_id128_equal(root_uuid, id))
continue;
- designator = PARTITION_ROOT;
- architecture = native_architecture();
+ assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
+ designator = PARTITION_ROOT_OF_ARCH(architecture);
rw = !(pflags & GPT_FLAG_READ_ONLY);
growfs = FLAGS_SET(pflags, GPT_FLAG_GROWFS);
- } else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE_VERITY)) {
+ } else if (gpt_partition_type_is_root_verity(type_id)) {
check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
if (!sd_id128_is_null(root_verity_uuid) && !sd_id128_equal(root_verity_uuid, id))
continue;
- designator = PARTITION_ROOT_VERITY;
+ assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
+ designator = PARTITION_VERITY_OF(PARTITION_ROOT_OF_ARCH(architecture));
fstype = "DM_verity_hash";
- architecture = native_architecture();
rw = false;
- } else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE_VERITY_SIG)) {
+ } else if (gpt_partition_type_is_root_verity_sig(type_id)) {
check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
if (verity->root_hash)
continue;
- designator = PARTITION_ROOT_VERITY_SIG;
+ assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
+ designator = PARTITION_VERITY_SIG_OF(PARTITION_ROOT_OF_ARCH(architecture));
fstype = "verity_hash_signature";
- architecture = native_architecture();
rw = false;
- }
-#endif
-#ifdef GPT_ROOT_SECONDARY
- else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY)) {
-
- check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY|GPT_FLAG_GROWFS);
-
- if (pflags & GPT_FLAG_NO_AUTO)
- continue;
-
- /* 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 = !(pflags & GPT_FLAG_READ_ONLY);
- growfs = FLAGS_SET(pflags, GPT_FLAG_GROWFS);
-
- } else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY_VERITY)) {
-
- check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
-
- if (pflags & GPT_FLAG_NO_AUTO)
- continue;
-
- m->has_verity = true;
-
- /* Don't do verity if no verity config is passed in */
- if (!verity)
- continue;
- if (verity->designator >= 0 && verity->designator != PARTITION_ROOT)
- continue;
-
- /* If root hash is specified, then ignore everything but the root id */
- if (!sd_id128_is_null(root_verity_uuid) && !sd_id128_equal(root_verity_uuid, id))
- continue;
-
- designator = PARTITION_ROOT_SECONDARY_VERITY;
- fstype = "DM_verity_hash";
- architecture = SECONDARY_ARCHITECTURE;
- rw = false;
-
- } else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY_VERITY_SIG)) {
-
- check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
-
- if (pflags & GPT_FLAG_NO_AUTO)
- continue;
-
- m->has_verity_sig = true;
-
- /* If root hash is specified explicitly, then ignore any embedded signature */
- if (!verity)
- continue;
- if (verity->designator >= 0 && verity->designator != PARTITION_ROOT)
- continue;
- if (verity->root_hash)
- continue;
- designator = PARTITION_ROOT_SECONDARY_VERITY_SIG;
- fstype = "verity_hash_signature";
- architecture = native_architecture();
- rw = false;
- }
-#endif
-#ifdef GPT_USR_NATIVE
- else if (sd_id128_equal(type_id, GPT_USR_NATIVE)) {
+ } else if (gpt_partition_type_is_usr(type_id)) {
check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY|GPT_FLAG_GROWFS);
if (!sd_id128_is_null(usr_uuid) && !sd_id128_equal(usr_uuid, id))
continue;
- designator = PARTITION_USR;
- architecture = native_architecture();
+ assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
+ designator = PARTITION_USR_OF_ARCH(architecture);
rw = !(pflags & GPT_FLAG_READ_ONLY);
growfs = FLAGS_SET(pflags, GPT_FLAG_GROWFS);
- } else if (sd_id128_equal(type_id, GPT_USR_NATIVE_VERITY)) {
+ } else if (gpt_partition_type_is_usr_verity(type_id)) {
check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
if (!sd_id128_is_null(usr_verity_uuid) && !sd_id128_equal(usr_verity_uuid, id))
continue;
- designator = PARTITION_USR_VERITY;
+ assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
+ designator = PARTITION_VERITY_OF(PARTITION_USR_OF_ARCH(architecture));
fstype = "DM_verity_hash";
- architecture = native_architecture();
rw = false;
- } else if (sd_id128_equal(type_id, GPT_USR_NATIVE_VERITY_SIG)) {
+ } else if (gpt_partition_type_is_usr_verity_sig(type_id)) {
check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
if (verity->root_hash)
continue;
- designator = PARTITION_USR_VERITY_SIG;
+ assert_se((architecture = gpt_partition_type_uuid_to_arch(type_id)) >= 0);
+ designator = PARTITION_VERITY_SIG_OF(PARTITION_USR_OF_ARCH(architecture));
fstype = "verity_hash_signature";
- architecture = native_architecture();
rw = false;
- }
-#endif
-#ifdef GPT_USR_SECONDARY
- else if (sd_id128_equal(type_id, GPT_USR_SECONDARY)) {
-
- check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY|GPT_FLAG_GROWFS);
-
- if (pflags & GPT_FLAG_NO_AUTO)
- continue;
-
- /* If a usr ID is specified, ignore everything but the usr id */
- if (!sd_id128_is_null(usr_uuid) && !sd_id128_equal(usr_uuid, id))
- continue;
-
- designator = PARTITION_USR_SECONDARY;
- architecture = SECONDARY_ARCHITECTURE;
- rw = !(pflags & GPT_FLAG_READ_ONLY);
- growfs = FLAGS_SET(pflags, GPT_FLAG_GROWFS);
-
- } else if (sd_id128_equal(type_id, GPT_USR_SECONDARY_VERITY)) {
-
- check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
-
- if (pflags & GPT_FLAG_NO_AUTO)
- continue;
- m->has_verity = true;
-
- if (!verity)
- continue;
- if (verity->designator >= 0 && verity->designator != PARTITION_USR)
- continue;
-
- /* If usr hash is specified, then ignore everything but the root id */
- if (!sd_id128_is_null(usr_verity_uuid) && !sd_id128_equal(usr_verity_uuid, id))
- continue;
-
- designator = PARTITION_USR_SECONDARY_VERITY;
- fstype = "DM_verity_hash";
- architecture = SECONDARY_ARCHITECTURE;
- rw = false;
-
- } else if (sd_id128_equal(type_id, GPT_USR_SECONDARY_VERITY_SIG)) {
-
- check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
-
- if (pflags & GPT_FLAG_NO_AUTO)
- continue;
-
- m->has_verity_sig = true;
-
- /* If usr hash is specified explicitly, then ignore any embedded signature */
- if (!verity)
- continue;
- if (verity->designator >= 0 && verity->designator != PARTITION_USR)
- continue;
- if (verity->root_hash)
- continue;
-
- designator = PARTITION_USR_SECONDARY_VERITY_SIG;
- fstype = "verity_hash_signature";
- architecture = native_architecture();
- rw = false;
- }
-#endif
- else if (sd_id128_equal(type_id, GPT_SWAP)) {
+ } else if (sd_id128_equal(type_id, GPT_SWAP)) {
check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO);
}
if (m->partitions[PARTITION_ROOT].found) {
- /* If we found the primary arch, then invalidate the secondary arch to avoid any ambiguities,
- * since we never want to mount the secondary arch in this case. */
+ /* If we found the primary arch, then invalidate the secondary and other arch to avoid any
+ * ambiguities, since we never want to mount the secondary or other arch in this case. */
m->partitions[PARTITION_ROOT_SECONDARY].found = false;
m->partitions[PARTITION_ROOT_SECONDARY_VERITY].found = false;
m->partitions[PARTITION_ROOT_SECONDARY_VERITY_SIG].found = false;
m->partitions[PARTITION_USR_SECONDARY_VERITY].found = false;
m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG].found = false;
+ m->partitions[PARTITION_ROOT_OTHER].found = false;
+ m->partitions[PARTITION_ROOT_OTHER_VERITY].found = false;
+ m->partitions[PARTITION_ROOT_OTHER_VERITY_SIG].found = false;
+ m->partitions[PARTITION_USR_OTHER].found = false;
+ m->partitions[PARTITION_USR_OTHER_VERITY].found = false;
+ m->partitions[PARTITION_USR_OTHER_VERITY_SIG].found = false;
+
} else if (m->partitions[PARTITION_ROOT_VERITY].found ||
m->partitions[PARTITION_ROOT_VERITY_SIG].found)
return -EADDRNOTAVAIL; /* Verity found but no matching rootfs? Something is off, refuse. */
else if (m->partitions[PARTITION_ROOT_SECONDARY].found) {
/* No root partition found but there's one for the secondary architecture? Then upgrade
- * secondary arch to first */
+ * secondary arch to first and invalidate the other arch. */
+
+ log_debug("No root partition found of the native architecture, falling back to a root "
+ "partition of the secondary architecture.");
m->partitions[PARTITION_ROOT] = m->partitions[PARTITION_ROOT_SECONDARY];
zero(m->partitions[PARTITION_ROOT_SECONDARY]);
m->partitions[PARTITION_USR_VERITY_SIG] = m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG];
zero(m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG]);
+ m->partitions[PARTITION_ROOT_OTHER].found = false;
+ m->partitions[PARTITION_ROOT_OTHER_VERITY].found = false;
+ m->partitions[PARTITION_ROOT_OTHER_VERITY_SIG].found = false;
+ m->partitions[PARTITION_USR_OTHER].found = false;
+ m->partitions[PARTITION_USR_OTHER_VERITY].found = false;
+ m->partitions[PARTITION_USR_OTHER_VERITY_SIG].found = false;
+
} else if (m->partitions[PARTITION_ROOT_SECONDARY_VERITY].found ||
m->partitions[PARTITION_ROOT_SECONDARY_VERITY_SIG].found)
return -EADDRNOTAVAIL; /* as above */
+ else if (m->partitions[PARTITION_ROOT_OTHER].found) {
+
+ /* No root or secondary partition found but there's one for another architecture? Then
+ * upgrade the other architecture to first. */
+
+ log_debug("No root partition found of the native architecture or the secondary architecture, "
+ "falling back to a root partition of a non-native architecture (%s).",
+ architecture_to_string(m->partitions[PARTITION_ROOT_OTHER].architecture));
+
+ m->partitions[PARTITION_ROOT] = m->partitions[PARTITION_ROOT_OTHER];
+ zero(m->partitions[PARTITION_ROOT_OTHER]);
+ m->partitions[PARTITION_ROOT_VERITY] = m->partitions[PARTITION_ROOT_OTHER_VERITY];
+ zero(m->partitions[PARTITION_ROOT_OTHER_VERITY]);
+ m->partitions[PARTITION_ROOT_VERITY_SIG] = m->partitions[PARTITION_ROOT_OTHER_VERITY_SIG];
+ zero(m->partitions[PARTITION_ROOT_OTHER_VERITY_SIG]);
+
+ m->partitions[PARTITION_USR] = m->partitions[PARTITION_USR_OTHER];
+ zero(m->partitions[PARTITION_USR_OTHER]);
+ m->partitions[PARTITION_USR_VERITY] = m->partitions[PARTITION_USR_OTHER_VERITY];
+ zero(m->partitions[PARTITION_USR_OTHER_VERITY]);
+ m->partitions[PARTITION_USR_VERITY_SIG] = m->partitions[PARTITION_USR_OTHER_VERITY_SIG];
+ zero(m->partitions[PARTITION_USR_OTHER_VERITY_SIG]);
+ }
+
/* Hmm, we found a signature partition but no Verity data? Something is off. */
if (m->partitions[PARTITION_ROOT_VERITY_SIG].found && !m->partitions[PARTITION_ROOT_VERITY].found)
return -EADDRNOTAVAIL;
if (m->partitions[PARTITION_USR].found) {
- /* Invalidate secondary arch /usr/ if we found the primary arch */
+ /* Invalidate secondary and other arch /usr/ if we found the primary arch */
m->partitions[PARTITION_USR_SECONDARY].found = false;
m->partitions[PARTITION_USR_SECONDARY_VERITY].found = false;
m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG].found = false;
+ m->partitions[PARTITION_USR_OTHER].found = false;
+ m->partitions[PARTITION_USR_OTHER_VERITY].found = false;
+ m->partitions[PARTITION_USR_OTHER_VERITY_SIG].found = false;
+
} else if (m->partitions[PARTITION_USR_VERITY].found ||
m->partitions[PARTITION_USR_VERITY_SIG].found)
return -EADDRNOTAVAIL; /* as above */
else if (m->partitions[PARTITION_USR_SECONDARY].found) {
+ log_debug("No usr partition found of the native architecture, falling back to a usr "
+ "partition of the secondary architecture.");
+
/* Upgrade secondary arch to primary */
m->partitions[PARTITION_USR] = m->partitions[PARTITION_USR_SECONDARY];
zero(m->partitions[PARTITION_USR_SECONDARY]);
m->partitions[PARTITION_USR_VERITY_SIG] = m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG];
zero(m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG]);
+ m->partitions[PARTITION_USR_OTHER].found = false;
+ m->partitions[PARTITION_USR_OTHER_VERITY].found = false;
+ m->partitions[PARTITION_USR_OTHER_VERITY_SIG].found = false;
+
} else if (m->partitions[PARTITION_USR_SECONDARY_VERITY].found ||
m->partitions[PARTITION_USR_SECONDARY_VERITY_SIG].found)
return -EADDRNOTAVAIL; /* as above */
+ else if (m->partitions[PARTITION_USR_OTHER].found) {
+
+ log_debug("No usr partition found of the native architecture or the secondary architecture, "
+ "falling back to a usr partition of a non-native architecture (%s).",
+ architecture_to_string(m->partitions[PARTITION_ROOT_OTHER].architecture));
+
+ /* Upgrade other arch to primary */
+ m->partitions[PARTITION_USR] = m->partitions[PARTITION_USR_OTHER];
+ zero(m->partitions[PARTITION_USR_OTHER]);
+ m->partitions[PARTITION_USR_VERITY] = m->partitions[PARTITION_USR_OTHER_VERITY];
+ zero(m->partitions[PARTITION_USR_OTHER_VERITY]);
+ m->partitions[PARTITION_USR_VERITY_SIG] = m->partitions[PARTITION_USR_OTHER_VERITY_SIG];
+ zero(m->partitions[PARTITION_USR_OTHER_VERITY_SIG]);
+ }
+
/* Hmm, we found a signature partition but no Verity data? Something is off. */
if (m->partitions[PARTITION_USR_VERITY_SIG].found && !m->partitions[PARTITION_USR_VERITY].found)
return -EADDRNOTAVAIL;
}
if (m->verity_ready)
- m->verity_sig_ready = !!verity->root_hash_sig;
+ m->verity_sig_ready = verity->root_hash_sig;
} else if (m->partitions[verity->designator == PARTITION_USR ? PARTITION_USR_VERITY_SIG : PARTITION_ROOT_VERITY_SIG].found) {
(void) fs_grow(node, p);
if (remap_uid_gid) {
- r = remount_idmap(p, uid_shift, uid_range);
+ r = remount_idmap(p, uid_shift, uid_range, REMOUNT_IDMAP_HOST_ROOT);
if (r < 0)
return r;
}
}
DEFINE_TRIVIAL_CLEANUP_FUNC(char *, dm_deferred_remove_clean);
+static int validate_signature_userspace(const VeritySettings *verity) {
+#if HAVE_OPENSSL
+ _cleanup_(sk_X509_free_allp) STACK_OF(X509) *sk = NULL;
+ _cleanup_strv_free_ char **certs = NULL;
+ _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
+ _cleanup_free_ char *s = NULL;
+ _cleanup_(BIO_freep) BIO *bio = NULL; /* 'bio' must be freed first, 's' second, hence keep this order
+ * of declaration in place, please */
+ const unsigned char *d;
+ int r;
+
+ assert(verity);
+ assert(verity->root_hash);
+ assert(verity->root_hash_sig);
+
+ /* Because installing a signature certificate into the kernel chain is so messy, let's optionally do
+ * userspace validation. */
+
+ r = conf_files_list_nulstr(&certs, ".crt", NULL, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, CONF_PATHS_NULSTR("verity.d"));
+ if (r < 0)
+ return log_debug_errno(r, "Failed to enumerate certificates: %m");
+ if (strv_isempty(certs)) {
+ log_debug("No userspace dm-verity certificates found.");
+ return 0;
+ }
+
+ d = verity->root_hash_sig;
+ p7 = d2i_PKCS7(NULL, &d, (long) verity->root_hash_sig_size);
+ if (!p7)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse PKCS7 DER signature data.");
+
+ s = hexmem(verity->root_hash, verity->root_hash_size);
+ if (!s)
+ return log_oom_debug();
+
+ bio = BIO_new_mem_buf(s, strlen(s));
+ if (!bio)
+ return log_oom_debug();
+
+ sk = sk_X509_new_null();
+ if (!sk)
+ return log_oom_debug();
+
+ STRV_FOREACH(i, certs) {
+ _cleanup_(X509_freep) X509 *c = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+
+ f = fopen(*i, "re");
+ if (!f) {
+ log_debug_errno(errno, "Failed to open '%s', ignoring: %m", *i);
+ continue;
+ }
+
+ c = PEM_read_X509(f, NULL, NULL, NULL);
+ if (!c) {
+ log_debug("Failed to load X509 certificate '%s', ignoring.", *i);
+ continue;
+ }
+
+ if (sk_X509_push(sk, c) == 0)
+ return log_oom_debug();
+
+ TAKE_PTR(c);
+ }
+
+ r = PKCS7_verify(p7, sk, NULL, bio, NULL, PKCS7_NOINTERN|PKCS7_NOVERIFY);
+ if (r)
+ log_debug("Userspace PKCS#7 validation succeeded.");
+ else
+ log_debug("Userspace PKCS#7 validation failed: %s", ERR_error_string(ERR_get_error(), NULL));
+
+ return r;
+#else
+ log_debug("Not doing client-side validation of dm-verity root hash signatures, OpenSSL support disabled.");
+ return 0;
+#endif
+}
+
+static int do_crypt_activate_verity(
+ struct crypt_device *cd,
+ const char *name,
+ const VeritySettings *verity) {
+
+ bool check_signature;
+ int r;
+
+ assert(cd);
+ assert(name);
+ assert(verity);
+
+ if (verity->root_hash_sig) {
+ r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_SIGNATURE");
+ if (r < 0 && r != -ENXIO)
+ log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_SIGNATURE");
+
+ check_signature = r != 0;
+ } else
+ 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(
+ cd,
+ name,
+ verity->root_hash,
+ verity->root_hash_size,
+ verity->root_hash_sig,
+ verity->root_hash_sig_size,
+ CRYPT_ACTIVATE_READONLY);
+ if (r >= 0)
+ return r;
+
+ log_debug("Validation of dm-verity signature failed via the kernel, trying userspace validation instead.");
+#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);
+#endif
+
+ /* So this didn't work via the kernel, then let's try userspace validation instead. If that
+ * works we'll try to activate without telling the kernel the signature. */
+
+ r = validate_signature_userspace(verity);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOKEY),
+ "Activation of signed Verity volume worked neither via the kernel nor in userspace, can't activate.");
+ }
+
+ return sym_crypt_activate_by_volume_key(
+ cd,
+ name,
+ verity->root_hash,
+ verity->root_hash_size,
+ CRYPT_ACTIVATE_READONLY);
+}
+
static int verity_partition(
PartitionDesignator designator,
DissectedPartition *m,
* In case of ENODEV/ENOENT, which can happen if another process is activating at the exact same time,
* retry a few times before giving up. */
for (unsigned i = 0; i < N_DEVICE_NODE_LIST_ATTEMPTS; i++) {
- if (verity->root_hash_sig) {
-#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY
- r = sym_crypt_activate_by_signed_key(
- cd,
- name,
- verity->root_hash,
- verity->root_hash_size,
- verity->root_hash_sig,
- verity->root_hash_sig_size,
- CRYPT_ACTIVATE_READONLY);
-#else
- r = log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
- "Activation of verity device with signature requested, but not supported by %s due to missing crypt_activate_by_signed_key().", program_invocation_short_name);
-#endif
- } else
- r = sym_crypt_activate_by_volume_key(
- cd,
- name,
- verity->root_hash,
- verity->root_hash_size,
- CRYPT_ACTIVATE_READONLY);
+
+ r = do_crypt_activate_verity(cd, name, verity);
/* libdevmapper can return EINVAL when the device is already in the activation stage.
* There's no way to distinguish this situation from a genuine error due to invalid
* parameters, so immediately fall back to activating the device with a unique name.
* that doesn't exist for /usr */
if (designator < 0 || designator == PARTITION_ROOT) {
- r = getxattr_malloc(image, "user.verity.roothash", &text, true);
+ r = getxattr_malloc(image, "user.verity.roothash", &text);
if (r < 0) {
_cleanup_free_ char *p = NULL;
* `usrhash`, because `usrroothash` or `rootusrhash` would just be too
* confusing. We thus drop the reference to the root of the Merkle tree, and
* just indicate which file system it's about. */
- r = getxattr_malloc(image, "user.verity.usrhash", &text, true);
+ r = getxattr_malloc(image, "user.verity.usrhash", &text);
if (r < 0) {
_cleanup_free_ char *p = NULL;
return 1;
}
-int dissected_image_acquire_metadata(DissectedImage *m) {
+int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_flags) {
enum {
META_HOSTNAME,
META_MACHINE_INFO,
META_OS_RELEASE,
META_EXTENSION_RELEASE,
+ META_HAS_INIT_SYSTEM,
_META_MAX,
};
[META_MACHINE_ID] = "/etc/machine-id\0",
[META_MACHINE_INFO] = "/etc/machine-info\0",
[META_OS_RELEASE] = ("/etc/os-release\0"
- "/usr/lib/os-release\0"),
- [META_EXTENSION_RELEASE] = "extension-release\0", /* Used only for logging. */
+ "/usr/lib/os-release\0"),
+ [META_EXTENSION_RELEASE] = "extension-release\0", /* Used only for logging. */
+ [META_HAS_INIT_SYSTEM] = "has-init-system\0", /* ditto */
};
_cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **extension_release = NULL;
_cleanup_free_ char *hostname = NULL;
unsigned n_meta_initialized = 0;
int fds[2 * _META_MAX], r, v;
+ int has_init_system = -1;
ssize_t n;
BLOCK_SIGNALS(SIGCHLD);
if (r < 0)
goto finish;
if (r == 0) {
+ /* Child in a new mount namespace */
error_pipe[0] = safe_close(error_pipe[0]);
r = dissected_image_mount(
t,
UID_INVALID,
UID_INVALID,
- DISSECT_IMAGE_READ_ONLY|
- DISSECT_IMAGE_MOUNT_ROOT_ONLY|
- DISSECT_IMAGE_VALIDATE_OS|
- DISSECT_IMAGE_VALIDATE_OS_EXT|
+ extra_flags |
+ DISSECT_IMAGE_READ_ONLY |
+ DISSECT_IMAGE_MOUNT_ROOT_ONLY |
DISSECT_IMAGE_USR_NO_ROOT);
if (r < 0) {
- /* Let parent know the error */
- (void) write(error_pipe[1], &r, sizeof(r));
-
log_debug_errno(r, "Failed to mount dissected image: %m");
- _exit(EXIT_FAILURE);
+ goto inner_fail;
}
for (unsigned k = 0; k < _META_MAX; k++) {
fds[2*k] = safe_close(fds[2*k]);
- if (k == META_EXTENSION_RELEASE) {
+ switch (k) {
+
+ case META_EXTENSION_RELEASE:
/* As per the os-release spec, if the image is an extension it will have a file
* named after the image name in extension-release.d/ - we use the image name
* and try to resolve it with the extension-release helpers, as sometimes
r = open_extension_release(t, m->image_name, NULL, &fd);
if (r < 0)
fd = r; /* Propagate the error. */
- } else
+ break;
+
+ case META_HAS_INIT_SYSTEM: {
+ bool found = false;
+ const char *init;
+
+ FOREACH_STRING(init,
+ "/usr/lib/systemd/systemd", /* systemd on /usr merged system */
+ "/lib/systemd/systemd", /* systemd on /usr non-merged systems */
+ "/sbin/init") { /* traditional path the Linux kernel invokes */
+
+ r = chase_symlinks(init, t, CHASE_PREFIX_ROOT, NULL, NULL);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_debug_errno(r, "Failed to resolve %s, ignoring: %m", init);
+ } else {
+ found = true;
+ break;
+ }
+ }
+
+ r = loop_write(fds[2*k+1], &found, sizeof(found), false);
+ if (r < 0)
+ goto inner_fail;
+
+ continue;
+ }
+
+ default:
NULSTR_FOREACH(p, paths[k]) {
fd = chase_symlinks_and_open(p, t, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
if (fd >= 0)
break;
}
+ }
+
if (fd < 0) {
log_debug_errno(fd, "Failed to read %s file of image, ignoring: %m", paths[k]);
fds[2*k+1] = safe_close(fds[2*k+1]);
}
r = copy_bytes(fd, fds[2*k+1], UINT64_MAX, 0);
- if (r < 0) {
- (void) write(error_pipe[1], &r, sizeof(r));
- _exit(EXIT_FAILURE);
- }
+ if (r < 0)
+ goto inner_fail;
fds[2*k+1] = safe_close(fds[2*k+1]);
}
_exit(EXIT_SUCCESS);
+
+ inner_fail:
+ /* Let parent know the error */
+ (void) write(error_pipe[1], &r, sizeof(r));
+ _exit(EXIT_FAILURE);
}
error_pipe[1] = safe_close(error_pipe[1]);
case META_HOSTNAME:
r = read_etc_hostname_stream(f, &hostname);
if (r < 0)
- log_debug_errno(r, "Failed to read /etc/hostname: %m");
+ log_debug_errno(r, "Failed to read /etc/hostname of image: %m");
break;
r = read_line(f, LONG_LINE_MAX, &line);
if (r < 0)
- log_debug_errno(r, "Failed to read /etc/machine-id: %m");
+ log_debug_errno(r, "Failed to read /etc/machine-id of image: %m");
else if (r == 33) {
r = sd_id128_from_string(line, &machine_id);
if (r < 0)
log_debug_errno(r, "Image contains invalid /etc/machine-id: %s", line);
} else if (r == 0)
- log_debug("/etc/machine-id file is empty.");
+ log_debug("/etc/machine-id file of image is empty.");
else if (streq(line, "uninitialized"))
- log_debug("/etc/machine-id file is uninitialized (likely aborted first boot).");
+ log_debug("/etc/machine-id file of image is uninitialized (likely aborted first boot).");
else
- log_debug("/etc/machine-id has unexpected length %i.", r);
+ log_debug("/etc/machine-id file of image has unexpected length %i.", r);
break;
}
case META_MACHINE_INFO:
r = load_env_file_pairs(f, "machine-info", &machine_info);
if (r < 0)
- log_debug_errno(r, "Failed to read /etc/machine-info: %m");
+ log_debug_errno(r, "Failed to read /etc/machine-info of image: %m");
break;
case META_OS_RELEASE:
r = load_env_file_pairs(f, "os-release", &os_release);
if (r < 0)
- log_debug_errno(r, "Failed to read OS release file: %m");
+ log_debug_errno(r, "Failed to read OS release file of image: %m");
break;
case META_EXTENSION_RELEASE:
r = load_env_file_pairs(f, "extension-release", &extension_release);
if (r < 0)
- log_debug_errno(r, "Failed to read extension release file: %m");
+ log_debug_errno(r, "Failed to read extension release file of image: %m");
break;
- }
+
+ case META_HAS_INIT_SYSTEM: {
+ bool b = false;
+ size_t nr;
+
+ errno = 0;
+ nr = fread(&b, 1, sizeof(b), f);
+ if (nr != sizeof(b))
+ log_debug_errno(errno_or_else(EIO), "Failed to read has-init-system boolean: %m");
+ else
+ has_init_system = b;
+
+ break;
+ }}
}
r = wait_for_terminate_and_check("(sd-dissect)", child, 0);
strv_free_and_replace(m->machine_info, machine_info);
strv_free_and_replace(m->os_release, os_release);
strv_free_and_replace(m->extension_release, extension_release);
+ m->has_init_system = has_init_system;
finish:
for (unsigned k = 0; k < n_meta_initialized; k++)
return log_error_errno(r, "Dissecting images is not supported, compiled without blkid support.");
case -ENOPKG:
- return log_error_errno(r, "Couldn't identify a suitable partition table or file system in '%s'.", name);
+ return log_error_errno(r, "%s: Couldn't identify a suitable partition table or file system.", name);
+
+ case -ENOMEDIUM:
+ return log_error_errno(r, "%s: The image does not pass validation.", name);
case -EADDRNOTAVAIL:
- return log_error_errno(r, "No root partition for specified root hash found in '%s'.", name);
+ return log_error_errno(r, "%s: No root partition for specified root hash found.", name);
case -ENOTUNIQ:
- return log_error_errno(r, "Multiple suitable root partitions found in image '%s'.", name);
+ return log_error_errno(r, "%s: Multiple suitable root partitions found in image.", name);
case -ENXIO:
- return log_error_errno(r, "No suitable root partition found in image '%s'.", name);
+ return log_error_errno(r, "%s: No suitable root partition found in image.", name);
case -EPROTONOSUPPORT:
return log_error_errno(r, "Device '%s' is loopback block device with partition scanning turned off, please turn it on.", name);
+ case -ENOTBLK:
+ return log_error_errno(r, "%s: Image is not a block device.", name);
+
+ case -EBADR:
+ return log_error_errno(r,
+ "Combining partitioned images (such as '%s') with external Verity data (such as '%s') not supported. "
+ "(Consider setting $SYSTEMD_DISSECT_VERITY_SIDECAR=0 to disable automatic discovery of external Verity data.)",
+ name, strna(verity ? verity->data_path : NULL));
+
default:
if (r < 0)
return log_error_errno(r, "Failed to dissect image '%s': %m", name);
}
const char* mount_options_from_designator(const MountOptions *options, PartitionDesignator designator) {
- const MountOptions *m;
-
LIST_FOREACH(mount_options, m, options)
if (designator == m->partition_designator && !isempty(m->options))
return m->options;
static const char *const partition_designator_table[] = {
[PARTITION_ROOT] = "root",
[PARTITION_ROOT_SECONDARY] = "root-secondary",
+ [PARTITION_ROOT_OTHER] = "root-other",
[PARTITION_USR] = "usr",
[PARTITION_USR_SECONDARY] = "usr-secondary",
+ [PARTITION_USR_OTHER] = "usr-other",
[PARTITION_HOME] = "home",
[PARTITION_SRV] = "srv",
[PARTITION_ESP] = "esp",
[PARTITION_SWAP] = "swap",
[PARTITION_ROOT_VERITY] = "root-verity",
[PARTITION_ROOT_SECONDARY_VERITY] = "root-secondary-verity",
+ [PARTITION_ROOT_OTHER_VERITY] = "root-other-verity",
[PARTITION_USR_VERITY] = "usr-verity",
[PARTITION_USR_SECONDARY_VERITY] = "usr-secondary-verity",
+ [PARTITION_USR_OTHER_VERITY] = "usr-other-verity",
[PARTITION_ROOT_VERITY_SIG] = "root-verity-sig",
[PARTITION_ROOT_SECONDARY_VERITY_SIG] = "root-secondary-verity-sig",
+ [PARTITION_ROOT_OTHER_VERITY_SIG] = "root-other-verity-sig",
[PARTITION_USR_VERITY_SIG] = "usr-verity-sig",
[PARTITION_USR_SECONDARY_VERITY_SIG] = "usr-secondary-verity-sig",
+ [PARTITION_USR_OTHER_VERITY_SIG] = "usr-other-verity-sig",
[PARTITION_TMP] = "tmp",
[PARTITION_VAR] = "var",
};
const MountOptions *options,
const char *required_host_os_release_id,
const char *required_host_os_release_version_id,
- const char *required_host_os_release_sysext_level) {
+ const char *required_host_os_release_sysext_level,
+ const char *required_sysext_scope) {
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
_cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
/* If we got os-release values from the caller, then we need to match them with the image's
* extension-release.d/ content. Return -EINVAL if there's any mismatch.
* First, check the distro ID. If that matches, then check the new SYSEXT_LEVEL value if
- * available, or else fallback to VERSION_ID. */
- if (required_host_os_release_id &&
- (required_host_os_release_version_id || required_host_os_release_sysext_level)) {
+ * available, or else fallback to VERSION_ID. If neither is present (eg: rolling release),
+ * then a simple match on the ID will be performed. */
+ if (required_host_os_release_id) {
_cleanup_strv_free_ char **extension_release = NULL;
r = load_extension_release_pairs(dest, dissected_image->image_name, &extension_release);
return log_debug_errno(r, "Failed to parse image %s extension-release metadata: %m", dissected_image->image_name);
r = extension_release_validate(
- dissected_image->image_name,
- required_host_os_release_id,
- required_host_os_release_version_id,
- required_host_os_release_sysext_level,
- extension_release);
+ dissected_image->image_name,
+ required_host_os_release_id,
+ required_host_os_release_version_id,
+ required_host_os_release_sysext_level,
+ required_sysext_scope,
+ extension_release);
if (r == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", dissected_image->image_name);
if (r < 0)