deploy: Write a .ostree.cfs composefs image in the deploy dir
authorAlexander Larsson <alexl@redhat.com>
Thu, 30 Jun 2022 12:47:44 +0000 (14:47 +0200)
committerAlexander Larsson <alexl@redhat.com>
Wed, 31 May 2023 08:55:14 +0000 (10:55 +0200)
This can be used as a composefs source for the root fs instead of
the checkout by pointing the basedir to /ostree/repo/objects.

We only write the file is `composefs` is enabled.

We enable ensure_rootfs_dirs when building the image which adds the
required root dirs to the image. In particular, this includes /etc
which often isn't in ostree commits in use.

We also create an (empty) .ostree.mnt directory, where composefs
will mount the erofs image that will be used as overlayfs lowerdir
for the root overlayfs mount. This way we can find the deploy
dir from the root overlayfs mount options.

If the commit has composefs digests recorded we verify those with the
created file. It also applies the fs-verity signature if it is
recorded, unless this is disabled with the
ex-integrity.composefs-apply-sign=false option.

src/libostree/ostree-sysroot-deploy.c

index 32bbd3358e8648879ddf49ee0f23719a27b3f9ef..a4853bb010e0e062ab274caa99b9c790a71ad3ec 100644 (file)
@@ -582,13 +582,45 @@ merge_configuration_from (OstreeSysroot *sysroot, OstreeDeployment *merge_deploy
   return TRUE;
 }
 
+#ifdef HAVE_COMPOSEFS
+static gboolean
+compare_verity_digests (GVariant *metadata_composefs, const guchar *fsverity_digest, GError **error)
+{
+  const guchar *expected_digest;
+
+  if (metadata_composefs == NULL)
+    return TRUE;
+
+  if (g_variant_n_children (metadata_composefs) != OSTREE_SHA256_DIGEST_LEN)
+    return glnx_throw (error, "Expected composefs fs-verity in metadata has the wrong size");
+
+  expected_digest = g_variant_get_data (metadata_composefs);
+  if (memcmp (fsverity_digest, expected_digest, OSTREE_SHA256_DIGEST_LEN) != 0)
+    {
+      char actual_checksum[OSTREE_SHA256_STRING_LEN + 1];
+      char expected_checksum[OSTREE_SHA256_STRING_LEN + 1];
+
+      ostree_checksum_inplace_from_bytes (fsverity_digest, actual_checksum);
+      ostree_checksum_inplace_from_bytes (expected_digest, expected_checksum);
+
+      return glnx_throw (error,
+                         "Generated composefs image digest (%s) doesn't match expected digest (%s)",
+                         actual_checksum, expected_checksum);
+    }
+
+  return TRUE;
+}
+
+#endif
+
 /* Look up @revision in the repository, and check it out in
  * /ostree/deploy/OS/deploy/${treecsum}.${deployserial}.
  * A dfd for the result is returned in @out_deployment_dfd.
  */
 static gboolean
 checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeployment *deployment,
-                          int *out_deployment_dfd, GCancellable *cancellable, GError **error)
+                          const char *revision, int *out_deployment_dfd, GCancellable *cancellable,
+                          GError **error)
 {
   GLNX_AUTO_PREFIX_ERROR ("Checking out deployment tree", error);
   /* Find the directory with deployments for this stateroot */
@@ -614,6 +646,78 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy
                                 cancellable, error))
     return FALSE;
 
+#ifdef HAVE_COMPOSEFS
+  if (repo->composefs_wanted != OT_TRISTATE_NO)
+    {
+      g_autoptr (GBytes) sig = NULL;
+      gboolean apply_composefs_signature;
+      g_autofree guchar *fsverity_digest = NULL;
+      g_auto (GLnxTmpfile) tmpf = {
+        0,
+      };
+      g_autoptr (GVariant) commit_variant = NULL;
+
+      if (!ostree_repo_load_commit (repo, revision, &commit_variant, NULL, error))
+        return FALSE;
+
+      if (!ot_keyfile_get_boolean_with_default (repo->config, _OSTREE_INTEGRITY_SECTION,
+                                                "composefs-apply-sig", TRUE,
+                                                &apply_composefs_signature, error))
+        return FALSE;
+
+      g_autoptr (GVariant) metadata = g_variant_get_child_value (commit_variant, 0);
+      g_autoptr (GVariant) metadata_composefs
+          = g_variant_lookup_value (metadata, "ostree.composefs", G_VARIANT_TYPE_BYTESTRING);
+      g_autoptr (GVariant) metadata_composefs_sig
+          = g_variant_lookup_value (metadata, "ostree.composefs-sig", G_VARIANT_TYPE_BYTESTRING);
+
+      /* Create a composefs image and put in deploy dir as .ostree.cfs */
+      g_autoptr (OstreeComposefsTarget) target = ostree_composefs_target_new ();
+
+      g_autoptr (GFile) commit_root = NULL;
+      if (!ostree_repo_read_commit (repo, csum, &commit_root, NULL, cancellable, error))
+        return FALSE;
+
+      if (!ostree_repo_checkout_composefs (repo, target, (OstreeRepoFile *)commit_root, cancellable,
+                                           error))
+        return FALSE;
+
+      g_autofree char *composefs_cfs_path
+          = g_strdup_printf ("%s/.ostree.cfs", checkout_target_name);
+
+      if (!glnx_open_tmpfile_linkable_at (osdeploy_dfd, checkout_target_name, O_WRONLY | O_CLOEXEC,
+                                          &tmpf, error))
+        return FALSE;
+
+      if (!ostree_composefs_target_write (target, tmpf.fd, &fsverity_digest, cancellable, error))
+        return FALSE;
+
+      /* If the commit specified a composefs digest, verify it */
+      if (!compare_verity_digests (metadata_composefs, fsverity_digest, error))
+        return FALSE;
+
+      if (!glnx_fchmod (tmpf.fd, 0644, error))
+        return FALSE;
+
+      if (apply_composefs_signature && metadata_composefs_sig)
+        sig = g_variant_get_data_as_bytes (metadata_composefs_sig);
+
+      if (!_ostree_tmpf_fsverity (repo, &tmpf, sig, error))
+        return FALSE;
+
+      if (!glnx_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_REPLACE, osdeploy_dfd, composefs_cfs_path,
+                                 error))
+        return FALSE;
+
+      /* This is where the erofs image will be temporarily mounted */
+      g_autofree char *composefs_mnt_path
+          = g_strdup_printf ("%s/.ostree.mnt", checkout_target_name);
+
+      if (!glnx_shutil_mkdir_p_at (osdeploy_dfd, composefs_mnt_path, 0775, cancellable, error))
+        return FALSE;
+    }
+#endif
+
   return glnx_opendirat (osdeploy_dfd, checkout_target_name, TRUE, out_deployment_dfd, error);
 }
 
@@ -2995,7 +3099,8 @@ sysroot_initialize_deployment (OstreeSysroot *self, const char *osname, const ch
 
   /* Check out the userspace tree onto the filesystem */
   glnx_autofd int deployment_dfd = -1;
-  if (!checkout_deployment_tree (self, repo, new_deployment, &deployment_dfd, cancellable, error))
+  if (!checkout_deployment_tree (self, repo, new_deployment, revision, &deployment_dfd, cancellable,
+                                 error))
     return FALSE;
 
   g_autoptr (OstreeKernelLayout) kernel_layout = NULL;