From c69bd1dab56b0a744e8d424553264f37b0db8aab Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 15 Sep 2025 10:04:59 +0200 Subject: [PATCH 1/2] prepare-root: add allow_noent argument to load_variant This is a minor preparation for a later change. Instead of hand-rolling the G_FILE_ERROR_NOENT error check we add a new allow_noent option. Additionally, we move the handling of a no commitmeta being an error to the caller of load_commit_for_deploy(), because this check will be slightly more complex in the future. --- src/libotcore/otcore-prepare-root.c | 50 +++++++++++++++++------------ 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/src/libotcore/otcore-prepare-root.c b/src/libotcore/otcore-prepare-root.c index e29f1150a5..6ca42afc28 100644 --- a/src/libotcore/otcore-prepare-root.c +++ b/src/libotcore/otcore-prepare-root.c @@ -278,19 +278,31 @@ otcore_load_rootfs_config (const char *cmdline, GKeyFile *config, gboolean load_ } #ifdef HAVE_COMPOSEFS -static GVariant * +static gboolean load_variant (const char *root_mountpoint, const char *digest, const char *extension, - const GVariantType *type, GError **error) + const GVariantType *type, gboolean allow_noent, GVariant **out, GError **error) { g_autofree char *path = g_strdup_printf ("%s/ostree/repo/objects/%.2s/%s.%s", root_mountpoint, digest, digest + 2, extension); char *data = NULL; gsize data_size; - if (!g_file_get_contents (path, &data, &data_size, error)) - return NULL; + g_autoptr (GError) local_error = NULL; + + if (!g_file_get_contents (path, &data, &data_size, &local_error)) + { + if (allow_noent && g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) + { + *out = NULL; + return TRUE; + } + + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } - return g_variant_ref_sink (g_variant_new_from_data (type, data, data_size, FALSE, g_free, data)); + *out = g_variant_ref_sink (g_variant_new_from_data (type, data, data_size, FALSE, g_free, data)); + return TRUE; } // Given a mount point, directly load the .commit object. At the current time this tool @@ -299,27 +311,21 @@ static gboolean load_commit_for_deploy (const char *root_mountpoint, const char *deploy_path, GVariant **commit_out, GVariant **commitmeta_out, GError **error) { - g_autoptr (GError) local_error = NULL; g_autofree char *digest = g_path_get_basename (deploy_path); char *dot = strchr (digest, '.'); if (dot != NULL) *dot = 0; - g_autoptr (GVariant) commit_v - = load_variant (root_mountpoint, digest, "commit", OSTREE_COMMIT_GVARIANT_FORMAT, error); - if (commit_v == NULL) + g_autoptr (GVariant) commit_v = NULL; + g_autoptr (GVariant) commitmeta_v = NULL; + + if (!load_variant (root_mountpoint, digest, "commit", OSTREE_COMMIT_GVARIANT_FORMAT, FALSE, + &commit_v, error)) return FALSE; - g_autoptr (GVariant) commitmeta_v = load_variant (root_mountpoint, digest, "commitmeta", - G_VARIANT_TYPE ("a{sv}"), &local_error); - if (commitmeta_v == NULL) - { - if (g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) - glnx_throw (error, "No commitmeta for commit %s", digest); - else - g_propagate_error (error, g_steal_pointer (&local_error)); - return FALSE; - } + if (!load_variant (root_mountpoint, digest, "commitmeta", G_VARIANT_TYPE ("a{sv}"), TRUE, + &commitmeta_v, error)) + return FALSE; *commit_out = g_steal_pointer (&commit_v); *commitmeta_out = g_steal_pointer (&commitmeta_v); @@ -553,9 +559,13 @@ otcore_mount_rootfs (RootConfig *rootfs_config, GVariantBuilder *metadata_builde g_autoptr (GVariant) commit = NULL; g_autoptr (GVariant) commitmeta = NULL; - if (!load_commit_for_deploy (root_mountpoint, deploy_path, &commit, &commitmeta, error)) + if (!load_commit_for_deploy (root_mountpoint, deploy_path, &commit, &commitmeta, + error)) return glnx_prefix_error (error, "Error loading signatures from repo"); + if (commitmeta == NULL) + return glnx_throw (error, "No commitmeta for deploy %s", deploy_path); + g_autoptr (GVariant) signatures = g_variant_lookup_value ( commitmeta, OSTREE_SIGN_METADATA_ED25519_KEY, G_VARIANT_TYPE ("aay")); if (signatures == NULL) From 92f6d8ecc87c4965dab6f0f52024a360b498b3db Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 15 Sep 2025 11:33:16 +0200 Subject: [PATCH 2/2] Support using composefs signatures also with bootc commits When using bootc, if you convert a signed ostree commit into an OCI image `rpm-ostree compose container-encapsulate` you end up with a new commit that isn't signed. However, the base commit object, and its commitmeta are still in the image and will end up the repo, and since https://github.com/bootc-dev/bootc/pull/1600 the base commit id is available as the parent commit. So, we change ostree-prepare-root to fall back to using the base commit+commitmeta to find the expected composefs digest if the main commit is not signed. Note: This will only work with ostree-only commits. If you have any layered data, then the content will change, and the composefs digest in the base commit will not match the deployed one. This is expected with such sealed commits though. If you want to layer, either disable sealing, or create a new sealed ostree commit for the new image. --- src/libotcore/otcore-prepare-root.c | 82 ++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/src/libotcore/otcore-prepare-root.c b/src/libotcore/otcore-prepare-root.c index 6ca42afc28..a628b88030 100644 --- a/src/libotcore/otcore-prepare-root.c +++ b/src/libotcore/otcore-prepare-root.c @@ -305,11 +305,41 @@ load_variant (const char *root_mountpoint, const char *digest, const char *exten return TRUE; } +/* For local bootc commit, return the base ostree commit that was used to generate the commit. + */ +static char * +get_base_digest_for_bootc_commit (GVariant *commit) +{ + g_autoptr (GVariant) metadata = g_variant_get_child_value (commit, 0); + + /* Check for ostree.container.image-config to determine if this is a bootc commit */ + const char *image_config = NULL; + if (!g_variant_lookup (metadata, "ostree.container.image-config", "s", &image_config)) + return NULL; + + /* If so, since https://github.com/bootc-dev/bootc/pull/1600, the + * parent commit will be the base ostree commit. */ + + g_autoptr (GVariant) parent_commit_v = g_variant_get_child_value (commit, 1); + if (g_variant_n_children (parent_commit_v) != OSTREE_SHA256_DIGEST_LEN) + return NULL; + + const guint8 *parent_commit_bin = ot_variant_get_data (parent_commit_v, NULL); + if (parent_commit_bin == NULL) + return NULL; + + char *basecommit_digest = g_malloc (OSTREE_SHA256_STRING_LEN + 1); + ot_bin2hex (basecommit_digest, parent_commit_bin, OSTREE_SHA256_DIGEST_LEN); + + return basecommit_digest; +} + // Given a mount point, directly load the .commit object. At the current time this tool // doesn't link to libostree. static gboolean load_commit_for_deploy (const char *root_mountpoint, const char *deploy_path, GVariant **commit_out, - GVariant **commitmeta_out, GError **error) + GVariant **commitmeta_out, GVariant **basecommit_out, + GVariant **basecommitmeta_out, GError **error) { g_autofree char *digest = g_path_get_basename (deploy_path); char *dot = strchr (digest, '.'); @@ -318,6 +348,8 @@ load_commit_for_deploy (const char *root_mountpoint, const char *deploy_path, GV g_autoptr (GVariant) commit_v = NULL; g_autoptr (GVariant) commitmeta_v = NULL; + g_autoptr (GVariant) basecommit_v = NULL; + g_autoptr (GVariant) basecommitmeta_v = NULL; if (!load_variant (root_mountpoint, digest, "commit", OSTREE_COMMIT_GVARIANT_FORMAT, FALSE, &commit_v, error)) @@ -327,8 +359,28 @@ load_commit_for_deploy (const char *root_mountpoint, const char *deploy_path, GV &commitmeta_v, error)) return FALSE; + /* In case the commit is one created by bootc when importing a container, it will not + * be signed. However, we can still look at the base commit which may be signed. + */ + g_autofree char *basecommit_digest = get_base_digest_for_bootc_commit (commit_v); + if (basecommit_digest) + { + if (!load_variant (root_mountpoint, basecommit_digest, "commit", + OSTREE_COMMIT_GVARIANT_FORMAT, TRUE, &basecommit_v, error)) + return FALSE; + + if (basecommit_v != NULL) + { + if (!load_variant (root_mountpoint, basecommit_digest, "commitmeta", + G_VARIANT_TYPE ("a{sv}"), TRUE, &basecommitmeta_v, error)) + return FALSE; + } + } + *commit_out = g_steal_pointer (&commit_v); *commitmeta_out = g_steal_pointer (&commitmeta_v); + *basecommit_out = g_steal_pointer (&basecommit_v); + *basecommitmeta_out = g_steal_pointer (&basecommitmeta_v); return TRUE; } @@ -556,14 +608,30 @@ otcore_mount_rootfs (RootConfig *rootfs_config, GVariantBuilder *metadata_builde if (rootfs_config->is_signed) { const char *composefs_pubkey = rootfs_config->signature_pubkey; - g_autoptr (GVariant) commit = NULL; - g_autoptr (GVariant) commitmeta = NULL; - - if (!load_commit_for_deploy (root_mountpoint, deploy_path, &commit, &commitmeta, - error)) + g_autoptr (GVariant) main_commit = NULL; + g_autoptr (GVariant) main_commitmeta = NULL; + g_autoptr (GVariant) base_commit = NULL; + g_autoptr (GVariant) base_commitmeta = NULL; + GVariant *commit = NULL; + GVariant *commitmeta = NULL; + + if (!load_commit_for_deploy (root_mountpoint, deploy_path, &main_commit, &main_commitmeta, + &base_commit, &base_commitmeta, error)) return glnx_prefix_error (error, "Error loading signatures from repo"); - if (commitmeta == NULL) + if (main_commitmeta != NULL) + { + commit = main_commit; + commitmeta = main_commitmeta; + } + else if (base_commitmeta != NULL) + { + ot_journal_print (LOG_INFO, + "composefs+ostree: Validating composefs using bootc base commit"); + commit = base_commit; + commitmeta = base_commitmeta; + } + else return glnx_throw (error, "No commitmeta for deploy %s", deploy_path); g_autoptr (GVariant) signatures = g_variant_lookup_value (