From: Colin Walters Date: Thu, 13 Jul 2023 20:37:41 +0000 (-0400) Subject: prepare-root: Refactor composefs config handling X-Git-Tag: archive/raspbian/2023.7-3+rpi1~1^2~9^2^2~54^2 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=d4ca834b0904166fb7691ddbb1e1e2ea8f76c55f;p=ostree.git prepare-root: Refactor composefs config handling - Convert the current enum into a struct, using `OtTristate` and two member variables (expected signature and digest) - Factor out a helper function to parse this config - Clean up the logging by consistently using `composefs:` as a prefix - Add more assertions to more strictly verify our runtime state since this is security relevant --- diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c index 69ebd237..2ce4af7c 100644 --- a/src/switchroot/ostree-prepare-root.c +++ b/src/switchroot/ostree-prepare-root.c @@ -75,6 +75,9 @@ #include "otcore.h" +// The kernel argument we support to configure composefs. +#define OT_COMPOSEFS_KARG "ot-composefs" + #define OSTREE_PREPARE_ROOT_DEPLOYMENT_MSG \ SD_ID128_MAKE (71, 70, 33, 6a, 73, ba, 46, 01, ba, d3, 1a, f8, 88, aa, 0d, f7) @@ -88,15 +91,6 @@ #include "ostree-mount-util.h" -typedef enum -{ - OSTREE_COMPOSEFS_MODE_OFF, /* Never use composefs */ - OSTREE_COMPOSEFS_MODE_MAYBE, /* Use if supported and image exists in deploy */ - OSTREE_COMPOSEFS_MODE_ON, /* Always use (and fail if not working) */ - OSTREE_COMPOSEFS_MODE_SIGNED, /* Always use and require it to be signed */ - OSTREE_COMPOSEFS_MODE_DIGEST, /* Always use and require specific digest */ -} OstreeComposefsMode; - static bool sysroot_is_configured_ro (const char *sysroot) { @@ -223,6 +217,61 @@ validate_signature (GBytes *data, GVariant *signatures, const guchar *pubkey, si } #endif +typedef struct +{ + OtTristate enabled; + char *signature_pubkey; + char *expected_digest; +} ComposefsConfig; + +static void +free_composefs_config (ComposefsConfig *config) +{ + free (config->signature_pubkey); + free (config->expected_digest); + free (config); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (ComposefsConfig, free_composefs_config) + +static ComposefsConfig * +load_composefs_config (GError **error) +{ + GLNX_AUTO_PREFIX_ERROR ("Loading composefs config", error); + g_autoptr (ComposefsConfig) ret = g_new0 (ComposefsConfig, 1); + ret->enabled = OT_TRISTATE_MAYBE; + + // TODO: Drop this kernel argument in favor of just the config file in the initramfs + autofree char *ot_composefs = read_proc_cmdline_key (OT_COMPOSEFS_KARG); + if (ot_composefs) + { + if (strcmp (ot_composefs, "off") == 0) + ret->enabled = OT_TRISTATE_NO; + else if (strcmp (ot_composefs, "maybe") == 0) + ret->enabled = OT_TRISTATE_MAYBE; + else if (strcmp (ot_composefs, "on") == 0) + ret->enabled = OT_TRISTATE_YES; + else if (g_str_has_prefix (ot_composefs, "signed=")) + { + ret->enabled = OT_TRISTATE_YES; + ret->signature_pubkey = g_strdup (ot_composefs + strlen ("signed=")); + } + else if (g_str_has_prefix (ot_composefs, "digest=")) + { + ret->enabled = OT_TRISTATE_YES; + ret->expected_digest = g_strdup (ot_composefs + strlen ("digest=")); + } + else + return glnx_null_throw (error, "Unsupported %s option: '%s'", OT_COMPOSEFS_KARG, + ot_composefs); + // In theory it's OK to have both a signature and an expected digest, + // but since there's no valid reason to do both, let's not support it. + g_assert (!(ret->signature_pubkey && ret->expected_digest)); + } + + return g_steal_pointer (&ret); +} + int main (int argc, char *argv[]) { @@ -265,39 +314,6 @@ main (int argc, char *argv[]) err (EXIT_FAILURE, "failed to umount proc from /proc"); } - OstreeComposefsMode composefs_mode = OSTREE_COMPOSEFS_MODE_MAYBE; - autofree char *ot_composefs = read_proc_cmdline_key ("ot-composefs"); - char *composefs_digest = NULL; - char *composefs_pubkey = NULL; - if (ot_composefs) - { - if (strcmp (ot_composefs, "off") == 0) - composefs_mode = OSTREE_COMPOSEFS_MODE_OFF; - else if (strcmp (ot_composefs, "maybe") == 0) - composefs_mode = OSTREE_COMPOSEFS_MODE_MAYBE; - else if (strcmp (ot_composefs, "on") == 0) - composefs_mode = OSTREE_COMPOSEFS_MODE_ON; - else if (strncmp (ot_composefs, "signed=", strlen ("signed=")) == 0) - { - composefs_mode = OSTREE_COMPOSEFS_MODE_SIGNED; - composefs_pubkey = ot_composefs + strlen ("signed="); - } - else if (strncmp (ot_composefs, "digest=", strlen ("digest=")) == 0) - { - composefs_mode = OSTREE_COMPOSEFS_MODE_DIGEST; - composefs_digest = ot_composefs + strlen ("digest="); - } - else - err (EXIT_FAILURE, "Unsupported ot-composefs option: '%s'", ot_composefs); - } - -#ifndef HAVE_COMPOSEFS - if (composefs_mode == OSTREE_COMPOSEFS_MODE_MAYBE) - composefs_mode = OSTREE_COMPOSEFS_MODE_OFF; - (void)composefs_digest; - (void)composefs_pubkey; -#endif - /* Query the repository configuration - this is an operating system builder * choice. More info: https://github.com/ostreedev/ostree/pull/1767 */ @@ -324,11 +340,18 @@ main (int argc, char *argv[]) GVariantBuilder metadata_builder; g_variant_builder_init (&metadata_builder, G_VARIANT_TYPE ("a{sv}")); + + // We always parse the composefs config, because we want to detect and error + // out if it's enabled, but not supported at compile time. + g_autoptr (ComposefsConfig) composefs_config = load_composefs_config (&error); + if (!composefs_config) + errx (EXIT_FAILURE, "%s", error->message); + // Tracks if we did successfully enable it at runtime bool using_composefs = false; /* We construct the new sysroot in /sysroot.tmp, which is either the composfs mount or a bind mount of the deploy-dir */ - if (composefs_mode != OSTREE_COMPOSEFS_MODE_OFF) + if (composefs_config->enabled != OT_TRISTATE_NO) { #ifdef HAVE_COMPOSEFS const char *objdirs[] = { "/sysroot/ostree/repo/objects" }; @@ -338,8 +361,12 @@ main (int argc, char *argv[]) 1, }; - if (composefs_mode == OSTREE_COMPOSEFS_MODE_SIGNED) + g_autofree char *expected_digest_owned = NULL; + const char *expected_digest = expected_digest_owned; + if (composefs_config->signature_pubkey) { + g_assert (expected_digest == NULL); + const char *composefs_pubkey = composefs_config->signature_pubkey; g_autoptr (GError) local_error = NULL; g_autofree char *pubkey = NULL; gsize pubkey_size; @@ -363,7 +390,7 @@ main (int argc, char *argv[]) if (!validate_signature (commit_data, signatures, (guchar *)pubkey, pubkey_size)) errx (EXIT_FAILURE, "No valid signatures found for public key"); - g_print ("Validated commit signature using '%s'\n", composefs_pubkey); + g_print ("composefs+ostree: Validated commit signature using '%s'\n", composefs_pubkey); g_variant_builder_add (&metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_COMPOSEFS_SIGNATURE, g_variant_new_string (composefs_pubkey)); @@ -374,9 +401,10 @@ main (int argc, char *argv[]) if (cfs_digest_v == NULL || g_variant_get_size (cfs_digest_v) != OSTREE_SHA256_DIGEST_LEN) errx (EXIT_FAILURE, "Signature validation requested, but no valid digest in commit"); - composefs_digest = g_malloc (OSTREE_SHA256_STRING_LEN + 1); - ot_bin2hex (composefs_digest, g_variant_get_data (cfs_digest_v), + expected_digest_owned = g_malloc (OSTREE_SHA256_STRING_LEN + 1); + ot_bin2hex (expected_digest_owned, g_variant_get_data (cfs_digest_v), g_variant_get_size (cfs_digest_v)); + expected_digest = expected_digest_owned; } cfs_options.flags = LCFS_MOUNT_FLAGS_READONLY; @@ -385,54 +413,68 @@ main (int argc, char *argv[]) err (EXIT_FAILURE, "failed to assemble /boot/loader path"); cfs_options.image_mountdir = srcpath; - if (composefs_digest != NULL) + if (expected_digest != NULL) { cfs_options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_VERITY; - cfs_options.expected_fsverity_digest = composefs_digest; + g_print ("composefs: Verifying digest: %s\n", expected_digest); + cfs_options.expected_fsverity_digest = expected_digest; } - - if (composefs_mode == OSTREE_COMPOSEFS_MODE_MAYBE) - g_print ("Trying to mount composefs rootfs\n"); - else if (composefs_digest != NULL) - g_print ("Mounting composefs rootfs with expected digest '%s'\n", composefs_digest); else - g_print ("Mounting composefs rootfs\n"); + { + // If we're not verifying a digest, then we *must* also have signatures disabled. + // Or stated in reverse: if signature verification is enabled, then digest verification + // must also be. + g_assert (!composefs_config->signature_pubkey); + g_print ("composefs: Mounting with no digest or signature check\n"); + } if (lcfs_mount_image (OSTREE_COMPOSEFS_NAME, TMP_SYSROOT, &cfs_options) == 0) { - using_composefs = 1; + using_composefs = true; g_variant_builder_add (&metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_COMPOSEFS, g_variant_new_boolean (true)); + g_print ("composefs: mounted successfully"); } else { - if (errno == ENOVERITY) - g_print ("No verity in composefs image\n"); - else if (errno == EWRONGVERITY) - g_print ("Wrong verity digest in composefs image\n"); - else if (errno == ENOSIGNATURE) - g_print ("Missing signature in composefs image\n"); + int errsv = errno; + const char *errmsg; + switch (errsv) + { + case ENOVERITY: + errmsg = "fsverity not enabled on composefs image"; + break; + case EWRONGVERITY: + errmsg = "Wrong fsverity digest in composefs image"; + break; + case ENOSIGNATURE: + errmsg = "Missing signature for fsverity in composefs image"; + break; + default: + errmsg = strerror (errno); + break; + } + if (composefs_config->enabled == OT_TRISTATE_MAYBE) + { + g_print ("composefs: optional support failed: %s\n", errmsg); + } else - g_print ("Mounting composefs image failed: %s\n", strerror (errno)); + { + g_assert (composefs_config->enabled == OT_TRISTATE_YES); + errx (EXIT_FAILURE, "composefs: failed to mount: %s", errmsg); + } } #else - err (EXIT_FAILURE, "Composefs not supported"); + errx (EXIT_FAILURE, "composefs: enabled at runtime, but support is not compiled in"); #endif } if (!using_composefs) { - if (composefs_mode > OSTREE_COMPOSEFS_MODE_MAYBE) - err (EXIT_FAILURE, "Failed to mount composefs"); - /* The deploy root starts out bind mounted to sysroot.tmp */ if (mount (deploy_path, TMP_SYSROOT, NULL, MS_BIND | MS_SILENT, NULL) < 0) err (EXIT_FAILURE, "failed to make initial bind mount %s", deploy_path); } - else - { - g_print ("Mounted composefs\n"); - } /* This will result in a system with /sysroot read-only. Thus, two additional * writable bind-mounts (for /etc and /var) are required later on. */