diff --git a/.gitignore b/.gitignore index b28dd21..420397a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/libostree-2025.1.tar.xz +SOURCES/libostree-2025.2.tar.xz diff --git a/.ostree.metadata b/.ostree.metadata index 4e1db6c..e12465b 100644 --- a/.ostree.metadata +++ b/.ostree.metadata @@ -1 +1 @@ -d1ad76514f2dcc673d1d6c38707b2f9cbd062632 SOURCES/libostree-2025.1.tar.xz +b39d0847461ccd41cd293b773f5975d295cae93d SOURCES/libostree-2025.2.tar.xz diff --git a/SOURCES/0001-Add-root-transient-ro.patch b/SOURCES/0001-Add-root-transient-ro.patch new file mode 100644 index 0000000..87985f0 --- /dev/null +++ b/SOURCES/0001-Add-root-transient-ro.patch @@ -0,0 +1,712 @@ +From 589e0ef3d72cb31d4e6905b464281c8a6139029e Mon Sep 17 00:00:00 2001 +From: Colin Walters +Date: Wed, 16 Jul 2025 12:56:49 -0400 +Subject: [PATCH 1/5] prepare-root: Rename rootfs loading functions + +Prep for moving more functionality there, it's really about +the rootfs, not just composefs. +--- + src/libostree/ostree-sysroot-deploy.c | 4 ++-- + src/libotcore/otcore-prepare-root.c | 12 ++++++------ + src/libotcore/otcore.h | 8 ++++---- + src/switchroot/ostree-prepare-root.c | 4 ++-- + 4 files changed, 14 insertions(+), 14 deletions(-) + +diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c +index 5d356d4d..fa2fd8cc 100644 +--- a/src/libostree/ostree-sysroot-deploy.c ++++ b/src/libostree/ostree-sysroot-deploy.c +@@ -653,8 +653,8 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy + // out if it's enabled, but not supported at compile time. + // However, we don't load the keys here, because they may not exist, such + // as in the initial deploy +- g_autoptr (ComposefsConfig) composefs_config +- = otcore_load_composefs_config ("", prepare_root_config, FALSE, error); ++ g_autoptr (RootConfig) composefs_config ++ = otcore_load_rootfs_config ("", prepare_root_config, FALSE, error); + if (!composefs_config) + return glnx_prefix_error (error, "Reading composefs config"); + +diff --git a/src/libotcore/otcore-prepare-root.c b/src/libotcore/otcore-prepare-root.c +index 18bdf43e..b778ec8d 100644 +--- a/src/libotcore/otcore-prepare-root.c ++++ b/src/libotcore/otcore-prepare-root.c +@@ -154,24 +154,24 @@ otcore_load_config (int rootfs_fd, const char *filename, GError **error) + } + + void +-otcore_free_composefs_config (ComposefsConfig *config) ++otcore_free_rootfs_config (RootConfig *config) + { + g_clear_pointer (&config->pubkeys, g_ptr_array_unref); + g_free (config->signature_pubkey); + g_free (config); + } + +-// Parse the [composefs] section of the prepare-root.conf. +-ComposefsConfig * +-otcore_load_composefs_config (const char *cmdline, GKeyFile *config, gboolean load_keys, ++// Parse key bits of prepare-root.conf into a data structure. ++RootConfig * ++otcore_load_rootfs_config (const char *cmdline, GKeyFile *config, gboolean load_keys, + GError **error) + { + g_assert (cmdline); + g_assert (config); + +- GLNX_AUTO_PREFIX_ERROR ("Loading composefs config", error); ++ GLNX_AUTO_PREFIX_ERROR ("Parsing rootfs config", error); + +- g_autoptr (ComposefsConfig) ret = g_new0 (ComposefsConfig, 1); ++ g_autoptr (RootConfig) ret = g_new0 (RootConfig, 1); + + g_autofree char *enabled = g_key_file_get_value (config, OTCORE_PREPARE_ROOT_COMPOSEFS_KEY, + OTCORE_PREPARE_ROOT_ENABLED_KEY, NULL); +diff --git a/src/libotcore/otcore.h b/src/libotcore/otcore.h +index ceeb1a92..3f1b5192 100644 +--- a/src/libotcore/otcore.h ++++ b/src/libotcore/otcore.h +@@ -69,11 +69,11 @@ typedef struct + gboolean is_signed; + char *signature_pubkey; + GPtrArray *pubkeys; +-} ComposefsConfig; +-void otcore_free_composefs_config (ComposefsConfig *config); +-G_DEFINE_AUTOPTR_CLEANUP_FUNC (ComposefsConfig, otcore_free_composefs_config) ++} RootConfig; ++void otcore_free_rootfs_config (RootConfig *config); ++G_DEFINE_AUTOPTR_CLEANUP_FUNC (RootConfig, otcore_free_rootfs_config) + +-ComposefsConfig *otcore_load_composefs_config (const char *cmdline, GKeyFile *config, ++RootConfig *otcore_load_rootfs_config (const char *cmdline, GKeyFile *config, + gboolean load_keys, GError **error); + + // Our directory with transient state (eventually /run/ostree-booted should be a link to +diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c +index 4bf180c3..5d83a8ca 100644 +--- a/src/switchroot/ostree-prepare-root.c ++++ b/src/switchroot/ostree-prepare-root.c +@@ -289,8 +289,8 @@ main (int argc, char *argv[]) + + // 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 +- = otcore_load_composefs_config (kernel_cmdline, config, TRUE, &error); ++ g_autoptr (RootConfig) composefs_config ++ = otcore_load_rootfs_config (kernel_cmdline, config, TRUE, &error); + if (!composefs_config) + errx (EXIT_FAILURE, "%s", error->message); + +-- +2.50.1 + + +From 2e7bca26eb69b1376526ec707cd6a4aa177c5fa3 Mon Sep 17 00:00:00 2001 +From: Colin Walters +Date: Wed, 16 Jul 2025 12:56:49 -0400 +Subject: [PATCH 2/5] prepare-root: Rename rootfs variables + +Prep for moving more functionality there, it's really about +the rootfs, not just composefs. + +Signed-off-by: Colin Walters +--- + src/libostree/ostree-sysroot-deploy.c | 10 +++++----- + src/switchroot/ostree-prepare-root.c | 28 +++++++++++++-------------- + 2 files changed, 19 insertions(+), 19 deletions(-) + +diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c +index fa2fd8cc..6a3f1712 100644 +--- a/src/libostree/ostree-sysroot-deploy.c ++++ b/src/libostree/ostree-sysroot-deploy.c +@@ -653,12 +653,12 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy + // out if it's enabled, but not supported at compile time. + // However, we don't load the keys here, because they may not exist, such + // as in the initial deploy +- g_autoptr (RootConfig) composefs_config ++ g_autoptr (RootConfig) rootfs_config + = otcore_load_rootfs_config ("", prepare_root_config, FALSE, error); +- if (!composefs_config) +- return glnx_prefix_error (error, "Reading composefs config"); ++ if (!rootfs_config) ++ return glnx_prefix_error (error, "Reading rootfs config"); + +- OtTristate composefs_enabled = composefs_config->enabled; ++ OtTristate composefs_enabled = rootfs_config->enabled; + g_debug ("composefs enabled by config: %d repo: %d", composefs_enabled, repo->composefs_wanted); + if (repo->composefs_wanted == OT_TRISTATE_YES) + composefs_enabled = repo->composefs_wanted; +@@ -677,7 +677,7 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy + g_auto (GVariantBuilder) cfs_checkout_opts_builder + = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + guint32 composefs_requested = 1; +- if (composefs_config->require_verity) ++ if (rootfs_config->require_verity) + composefs_requested = 2; + g_variant_builder_add (&cfs_checkout_opts_builder, "{sv}", "verity", + g_variant_new_uint32 (composefs_requested)); +diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c +index 5d83a8ca..745bd902 100644 +--- a/src/switchroot/ostree-prepare-root.c ++++ b/src/switchroot/ostree-prepare-root.c +@@ -289,14 +289,14 @@ main (int argc, char *argv[]) + + // 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 (RootConfig) composefs_config ++ g_autoptr (RootConfig) rootfs_config + = otcore_load_rootfs_config (kernel_cmdline, config, TRUE, &error); +- if (!composefs_config) ++ if (!rootfs_config) + errx (EXIT_FAILURE, "%s", error->message); + + // If composefs is enabled, that also implies sysroot.readonly=true because it's + // the new default we want to use (not because it's actually required) +- const bool sysroot_readonly_default = composefs_config->enabled == OT_TRISTATE_YES; ++ const bool sysroot_readonly_default = rootfs_config->enabled == OT_TRISTATE_YES; + if (!ot_keyfile_get_boolean_with_default (config, SYSROOT_KEY, READONLY_KEY, + sysroot_readonly_default, &sysroot_readonly, &error)) + errx (EXIT_FAILURE, "Failed to parse sysroot.readonly value: %s", error->message); +@@ -328,7 +328,7 @@ main (int argc, char *argv[]) + * However, we only do this if composefs is not enabled, because we don't + * want to parse the target root filesystem before verifying its integrity. + */ +- if (!sysroot_readonly && composefs_config->enabled != OT_TRISTATE_YES) ++ if (!sysroot_readonly && rootfs_config->enabled != OT_TRISTATE_YES) + { + sysroot_readonly = sysroot_is_configured_ro (root_arg); + // Encourage porting to the new config file +@@ -373,7 +373,7 @@ main (int argc, char *argv[]) + #ifdef HAVE_COMPOSEFS + /* We construct the new sysroot in /sysroot.tmp, which is either the composefs + mount or a bind mount of the deploy-dir */ +- if (composefs_config->enabled != OT_TRISTATE_NO) ++ if (rootfs_config->enabled != OT_TRISTATE_NO) + { + const char *objdirs[] = { "/sysroot/ostree/repo/objects" }; + g_autofree char *cfs_digest = NULL; +@@ -415,9 +415,9 @@ main (int argc, char *argv[]) + cfs_options.flags = LCFS_MOUNT_FLAGS_READONLY; + } + +- if (composefs_config->is_signed) ++ if (rootfs_config->is_signed) + { +- const char *composefs_pubkey = composefs_config->signature_pubkey; ++ const char *composefs_pubkey = rootfs_config->signature_pubkey; + g_autoptr (GError) local_error = NULL; + g_autoptr (GVariant) commit = NULL; + g_autoptr (GVariant) commitmeta = NULL; +@@ -432,7 +432,7 @@ main (int argc, char *argv[]) + errx (EXIT_FAILURE, "Signature validation requested, but no signatures in commit"); + + g_autoptr (GBytes) commit_data = g_variant_get_data_as_bytes (commit); +- if (!validate_signature (commit_data, signatures, composefs_config->pubkeys)) ++ if (!validate_signature (commit_data, signatures, rootfs_config->pubkeys)) + errx (EXIT_FAILURE, "No valid signatures found for public key"); + + g_print ("composefs+ostree: Validated commit signature using '%s'\n", composefs_pubkey); +@@ -452,12 +452,12 @@ main (int argc, char *argv[]) + expected_digest = g_malloc (OSTREE_SHA256_STRING_LEN + 1); + ot_bin2hex (expected_digest, cfs_digest_buf, g_variant_get_size (cfs_digest_v)); + +- g_assert (composefs_config->require_verity); ++ g_assert (rootfs_config->require_verity); + cfs_options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_VERITY; + g_print ("composefs: Verifying digest: %s\n", expected_digest); + cfs_options.expected_fsverity_digest = expected_digest; + } +- else if (composefs_config->require_verity) ++ else if (rootfs_config->require_verity) + { + cfs_options.flags |= LCFS_MOUNT_FLAGS_REQUIRE_VERITY; + } +@@ -476,8 +476,8 @@ main (int argc, char *argv[]) + else + { + int errsv = errno; +- g_assert (composefs_config->enabled != OT_TRISTATE_NO); +- if (composefs_config->enabled == OT_TRISTATE_MAYBE && errsv == ENOENT) ++ g_assert (rootfs_config->enabled != OT_TRISTATE_NO); ++ if (rootfs_config->enabled == OT_TRISTATE_MAYBE && errsv == ENOENT) + { + g_print ("composefs: No image present\n"); + } +@@ -490,7 +490,7 @@ main (int argc, char *argv[]) + } + #else + /* if composefs is configured as "maybe", we should continue */ +- if (composefs_config->enabled == OT_TRISTATE_YES) ++ if (rootfs_config->enabled == OT_TRISTATE_YES) + errx (EXIT_FAILURE, "composefs: enabled at runtime, but support is not compiled in"); + #endif + +@@ -590,7 +590,7 @@ main (int argc, char *argv[]) + * Also, hotfixes are incompatible with signed composefs use for security reasons. + */ + if (lstat (OTCORE_HOTFIX_USR_OVL_WORK, &stbuf) == 0 +- && !(using_composefs && composefs_config->is_signed)) ++ && !(using_composefs && rootfs_config->is_signed)) + { + /* Do we have a persistent overlayfs for /usr? If so, mount it now. */ + const char usr_ovl_options[] +-- +2.50.1 + + +From b329c9ea48a6a5633d95309979b9dcae5561d963 Mon Sep 17 00:00:00 2001 +From: Colin Walters +Date: Wed, 16 Jul 2025 12:56:49 -0400 +Subject: [PATCH 3/5] prepare-root: Rename `enabled` -> `composefs_enabled` + +Prep for moving more functionality there, it's really about +the rootfs, not just composefs. + +Signed-off-by: Colin Walters +--- + src/libostree/ostree-sysroot-deploy.c | 2 +- + src/libotcore/otcore-prepare-root.c | 10 +++++----- + src/libotcore/otcore.h | 2 +- + src/switchroot/ostree-prepare-root.c | 12 ++++++------ + 4 files changed, 13 insertions(+), 13 deletions(-) + +diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c +index 6a3f1712..7ac25b4d 100644 +--- a/src/libostree/ostree-sysroot-deploy.c ++++ b/src/libostree/ostree-sysroot-deploy.c +@@ -658,7 +658,7 @@ checkout_deployment_tree (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeploy + if (!rootfs_config) + return glnx_prefix_error (error, "Reading rootfs config"); + +- OtTristate composefs_enabled = rootfs_config->enabled; ++ OtTristate composefs_enabled = rootfs_config->composefs_enabled; + g_debug ("composefs enabled by config: %d repo: %d", composefs_enabled, repo->composefs_wanted); + if (repo->composefs_wanted == OT_TRISTATE_YES) + composefs_enabled = repo->composefs_wanted; +diff --git a/src/libotcore/otcore-prepare-root.c b/src/libotcore/otcore-prepare-root.c +index b778ec8d..4ffd63d2 100644 +--- a/src/libotcore/otcore-prepare-root.c ++++ b/src/libotcore/otcore-prepare-root.c +@@ -177,19 +177,19 @@ otcore_load_rootfs_config (const char *cmdline, GKeyFile *config, gboolean load_ + OTCORE_PREPARE_ROOT_ENABLED_KEY, NULL); + if (g_strcmp0 (enabled, "signed") == 0) + { +- ret->enabled = OT_TRISTATE_YES; ++ ret->composefs_enabled = OT_TRISTATE_YES; + ret->require_verity = true; + ret->is_signed = true; + } + else if (g_strcmp0 (enabled, "verity") == 0) + { +- ret->enabled = OT_TRISTATE_YES; ++ ret->composefs_enabled = OT_TRISTATE_YES; + ret->require_verity = true; + ret->is_signed = false; + } + else if (!ot_keyfile_get_tristate_with_default (config, OTCORE_PREPARE_ROOT_COMPOSEFS_KEY, + OTCORE_PREPARE_ROOT_ENABLED_KEY, OT_TRISTATE_NO, +- &ret->enabled, error)) ++ &ret->composefs_enabled, error)) + return NULL; + + // Look for a key - we default to the initramfs binding path. +@@ -232,7 +232,7 @@ otcore_load_rootfs_config (const char *cmdline, GKeyFile *config, gboolean load_ + { + if (g_strcmp0 (ostree_composefs, "signed") == 0) + { +- ret->enabled = OT_TRISTATE_YES; ++ ret->composefs_enabled = OT_TRISTATE_YES; + ret->is_signed = true; + ret->require_verity = true; + } +@@ -240,7 +240,7 @@ otcore_load_rootfs_config (const char *cmdline, GKeyFile *config, gboolean load_ + { + // The other states force off signatures + ret->is_signed = false; +- if (!_ostree_parse_tristate (ostree_composefs, &ret->enabled, error)) ++ if (!_ostree_parse_tristate (ostree_composefs, &ret->composefs_enabled, error)) + return glnx_prefix_error (error, "handling karg " CMDLINE_KEY_COMPOSEFS), NULL; + } + } +diff --git a/src/libotcore/otcore.h b/src/libotcore/otcore.h +index 3f1b5192..ff4baa94 100644 +--- a/src/libotcore/otcore.h ++++ b/src/libotcore/otcore.h +@@ -64,7 +64,7 @@ GKeyFile *otcore_load_config (int rootfs, const char *filename, GError **error); + + typedef struct + { +- OtTristate enabled; ++ OtTristate composefs_enabled; + gboolean require_verity; + gboolean is_signed; + char *signature_pubkey; +diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c +index 745bd902..d946bd8a 100644 +--- a/src/switchroot/ostree-prepare-root.c ++++ b/src/switchroot/ostree-prepare-root.c +@@ -296,7 +296,7 @@ main (int argc, char *argv[]) + + // If composefs is enabled, that also implies sysroot.readonly=true because it's + // the new default we want to use (not because it's actually required) +- const bool sysroot_readonly_default = rootfs_config->enabled == OT_TRISTATE_YES; ++ const bool sysroot_readonly_default = rootfs_config->composefs_enabled == OT_TRISTATE_YES; + if (!ot_keyfile_get_boolean_with_default (config, SYSROOT_KEY, READONLY_KEY, + sysroot_readonly_default, &sysroot_readonly, &error)) + errx (EXIT_FAILURE, "Failed to parse sysroot.readonly value: %s", error->message); +@@ -328,7 +328,7 @@ main (int argc, char *argv[]) + * However, we only do this if composefs is not enabled, because we don't + * want to parse the target root filesystem before verifying its integrity. + */ +- if (!sysroot_readonly && rootfs_config->enabled != OT_TRISTATE_YES) ++ if (!sysroot_readonly && rootfs_config->composefs_enabled != OT_TRISTATE_YES) + { + sysroot_readonly = sysroot_is_configured_ro (root_arg); + // Encourage porting to the new config file +@@ -373,7 +373,7 @@ main (int argc, char *argv[]) + #ifdef HAVE_COMPOSEFS + /* We construct the new sysroot in /sysroot.tmp, which is either the composefs + mount or a bind mount of the deploy-dir */ +- if (rootfs_config->enabled != OT_TRISTATE_NO) ++ if (rootfs_config->composefs_enabled != OT_TRISTATE_NO) + { + const char *objdirs[] = { "/sysroot/ostree/repo/objects" }; + g_autofree char *cfs_digest = NULL; +@@ -476,8 +476,8 @@ main (int argc, char *argv[]) + else + { + int errsv = errno; +- g_assert (rootfs_config->enabled != OT_TRISTATE_NO); +- if (rootfs_config->enabled == OT_TRISTATE_MAYBE && errsv == ENOENT) ++ g_assert (rootfs_config->composefs_enabled != OT_TRISTATE_NO); ++ if (rootfs_config->composefs_enabled == OT_TRISTATE_MAYBE && errsv == ENOENT) + { + g_print ("composefs: No image present\n"); + } +@@ -490,7 +490,7 @@ main (int argc, char *argv[]) + } + #else + /* if composefs is configured as "maybe", we should continue */ +- if (rootfs_config->enabled == OT_TRISTATE_YES) ++ if (rootfs_config->composefs_enabled == OT_TRISTATE_YES) + errx (EXIT_FAILURE, "composefs: enabled at runtime, but support is not compiled in"); + #endif + +-- +2.50.1 + + +From 8e52cc3f67f62225055ab294adb87728063744df Mon Sep 17 00:00:00 2001 +From: Colin Walters +Date: Wed, 16 Jul 2025 13:18:58 -0400 +Subject: [PATCH 4/5] prepare-root: Move root.transient parsing rootfs parsing + +This deduplicates more code between main boot and soft reboot, +and is prep for supporting `rootfs.transient-ro = true`. +--- + src/libotcore/otcore-prepare-root.c | 6 ++++++ + src/libotcore/otcore.h | 2 ++ + src/switchroot/ostree-prepare-root.c | 19 ++++++------------- + 3 files changed, 14 insertions(+), 13 deletions(-) + +diff --git a/src/libotcore/otcore-prepare-root.c b/src/libotcore/otcore-prepare-root.c +index 4ffd63d2..8a78ce28 100644 +--- a/src/libotcore/otcore-prepare-root.c ++++ b/src/libotcore/otcore-prepare-root.c +@@ -26,6 +26,8 @@ + #define BINDING_KEYPATH "/etc/ostree/initramfs-root-binding.key" + // The kernel argument to configure composefs + #define CMDLINE_KEY_COMPOSEFS "ostree.prepare-root.composefs" ++/* This key configures the / mount in the deployment root */ ++#define ROOT_KEY "root" + + static bool + proc_cmdline_has_key_starting_with (const char *cmdline, const char *key) +@@ -173,6 +175,10 @@ otcore_load_rootfs_config (const char *cmdline, GKeyFile *config, gboolean load_ + + g_autoptr (RootConfig) ret = g_new0 (RootConfig, 1); + ++ if (!ot_keyfile_get_boolean_with_default (config, ROOT_KEY, OTCORE_PREPARE_ROOT_TRANSIENT_KEY, ++ FALSE, &ret->root_transient, error)) ++ return NULL; ++ + g_autofree char *enabled = g_key_file_get_value (config, OTCORE_PREPARE_ROOT_COMPOSEFS_KEY, + OTCORE_PREPARE_ROOT_ENABLED_KEY, NULL); + if (g_strcmp0 (enabled, "signed") == 0) +diff --git a/src/libotcore/otcore.h b/src/libotcore/otcore.h +index ff4baa94..e59651b0 100644 +--- a/src/libotcore/otcore.h ++++ b/src/libotcore/otcore.h +@@ -65,6 +65,7 @@ GKeyFile *otcore_load_config (int rootfs, const char *filename, GError **error); + typedef struct + { + OtTristate composefs_enabled; ++ gboolean root_transient; + gboolean require_verity; + gboolean is_signed; + char *signature_pubkey; +@@ -103,6 +104,7 @@ RootConfig *otcore_load_rootfs_config (const char *cmdline, GKeyFile *config, + #define OTCORE_PREPARE_ROOT_COMPOSEFS_KEY "composefs" + #define OTCORE_PREPARE_ROOT_ENABLED_KEY "enabled" + #define OTCORE_PREPARE_ROOT_KEYPATH_KEY "keypath" ++#define OTCORE_PREPARE_ROOT_TRANSIENT_KEY "transient" + + // The file written in the initramfs which contains an a{sv} of metadata + // from ostree-prepare-root. +diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c +index d946bd8a..c6cf33c4 100644 +--- a/src/switchroot/ostree-prepare-root.c ++++ b/src/switchroot/ostree-prepare-root.c +@@ -79,8 +79,6 @@ + #define SYSROOT_KEY "sysroot" + #define READONLY_KEY "readonly" + +-/* This key configures the / mount in the deployment root */ +-#define ROOT_KEY "root" + #define ETC_KEY "etc" + #define TRANSIENT_KEY "transient" + +@@ -281,11 +279,6 @@ main (int argc, char *argv[]) + errx (EXIT_FAILURE, "Failed to parse config: %s", error->message); + + gboolean sysroot_readonly = FALSE; +- gboolean root_transient = FALSE; +- +- if (!ot_keyfile_get_boolean_with_default (config, ROOT_KEY, TRANSIENT_KEY, FALSE, &root_transient, +- &error)) +- return FALSE; + + // We always parse the composefs config, because we want to detect and error + // out if it's enabled, but not supported at compile time. +@@ -394,13 +387,13 @@ main (int argc, char *argv[]) + // https://github.com/systemd/systemd/blob/604b2001081adcbd64ee1fbe7de7a6d77c5209fe/src/basic/mountpoint-util.h#L36 + // which bumps up these defaults for the rootfs a bit. + g_autofree char *root_upperdir +- = root_transient ? g_build_filename (OTCORE_RUN_OSTREE_PRIVATE, "root/upper", NULL) ++ = rootfs_config->root_transient ? g_build_filename (OTCORE_RUN_OSTREE_PRIVATE, "root/upper", NULL) + : NULL; + g_autofree char *root_workdir +- = root_transient ? g_build_filename (OTCORE_RUN_OSTREE_PRIVATE, "root/work", NULL) : NULL; ++ = rootfs_config->root_transient ? g_build_filename (OTCORE_RUN_OSTREE_PRIVATE, "root/work", NULL) : NULL; + + // Propagate these options for transient root, if provided +- if (root_transient) ++ if (rootfs_config->root_transient) + { + if (!glnx_shutil_mkdir_p_at (AT_FDCWD, root_upperdir, 0755, NULL, &error)) + errx (EXIT_FAILURE, "Failed to create %s: %s", root_upperdir, error->message); +@@ -496,7 +489,7 @@ main (int argc, char *argv[]) + + if (!using_composefs) + { +- if (root_transient) ++ if (rootfs_config->root_transient) + { + errx (EXIT_FAILURE, "Must enable composefs with root.transient"); + } +@@ -508,7 +501,7 @@ main (int argc, char *argv[]) + + /* Pass on the state */ + g_variant_builder_add (&metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_ROOT_TRANSIENT, +- g_variant_new_boolean (root_transient)); ++ g_variant_new_boolean (rootfs_config->root_transient)); + + /* Pass on the state for use by ostree-prepare-root */ + g_variant_builder_add (&metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_SYSROOT_RO, +@@ -533,7 +526,7 @@ main (int argc, char *argv[]) + /* Prepare /etc. + * No action required if sysroot is writable. Otherwise, a bind-mount for + * the deployment needs to be created and remounted as read/write. */ +- if (sysroot_readonly || using_composefs || root_transient) ++ if (sysroot_readonly || using_composefs || rootfs_config->root_transient) + { + gboolean etc_transient = FALSE; + if (!ot_keyfile_get_boolean_with_default (config, ETC_KEY, TRANSIENT_KEY, FALSE, +-- +2.50.1 + + +From c6d138cdf2756753566a26907bffbdb07ce8319b Mon Sep 17 00:00:00 2001 +From: Colin Walters +Date: Wed, 16 Jul 2025 11:08:00 -0400 +Subject: [PATCH 5/5] Add root.transient-ro + +An example use case for this is having privileged code +add dynamic new toplevel mountpoints (that don't persist across +reboots/upgrades), while still keeping the rootfs readonly +for processes by default. + +Closes: https://github.com/ostreedev/ostree/issues/3471 + +Signed-off-by: Colin Walters +--- + man/ostree-prepare-root.xml | 9 ++++ + src/libotcore/otcore-prepare-root.c | 14 ++++++ + src/libotcore/otcore.h | 4 ++ + src/switchroot/ostree-prepare-root.c | 10 +++++ + .../kolainst/destructive/root-transient-ro.sh | 44 +++++++++++++++++++ + 5 files changed, 81 insertions(+) + create mode 100755 tests/kolainst/destructive/root-transient-ro.sh + +diff --git a/man/ostree-prepare-root.xml b/man/ostree-prepare-root.xml +index c1f39a8a..fd46a357 100644 +--- a/man/ostree-prepare-root.xml ++++ b/man/ostree-prepare-root.xml +@@ -140,6 +140,15 @@ License along with this library. If not, see . + + + ++ ++ root.transient-ro ++ A boolean value; the default is false. ++ This is like root.transient, but the overlayfs upper will be mounted ++ read-only by default. Use this when you want specific privileged components to be able to ++ write to the upper by temporarily mounting it writable in a new mount namespace. ++ ++ ++ + + composefs.enabled + This can be yes, no, maybe, +diff --git a/src/libotcore/otcore-prepare-root.c b/src/libotcore/otcore-prepare-root.c +index 8a78ce28..42611105 100644 +--- a/src/libotcore/otcore-prepare-root.c ++++ b/src/libotcore/otcore-prepare-root.c +@@ -179,6 +179,20 @@ otcore_load_rootfs_config (const char *cmdline, GKeyFile *config, gboolean load_ + FALSE, &ret->root_transient, error)) + return NULL; + ++ ++ if (!ot_keyfile_get_boolean_with_default (config, ROOT_KEY, OTCORE_PREPARE_ROOT_TRANSIENT_RO_KEY, ++ FALSE, &ret->root_transient_ro, error)) ++ return NULL; ++ if (ret->root_transient && ret->root_transient_ro) ++ { ++ return glnx_null_throw (error, "Cannot set both root.transient and root.transient-ro"); ++ } ++ // This way callers can test for just root_transient ++ else if (ret->root_transient_ro) ++ { ++ ret->root_transient = TRUE; ++ } ++ + g_autofree char *enabled = g_key_file_get_value (config, OTCORE_PREPARE_ROOT_COMPOSEFS_KEY, + OTCORE_PREPARE_ROOT_ENABLED_KEY, NULL); + if (g_strcmp0 (enabled, "signed") == 0) +diff --git a/src/libotcore/otcore.h b/src/libotcore/otcore.h +index e59651b0..33ccd7bb 100644 +--- a/src/libotcore/otcore.h ++++ b/src/libotcore/otcore.h +@@ -66,6 +66,7 @@ typedef struct + { + OtTristate composefs_enabled; + gboolean root_transient; ++ gboolean root_transient_ro; + gboolean require_verity; + gboolean is_signed; + char *signature_pubkey; +@@ -105,6 +106,7 @@ RootConfig *otcore_load_rootfs_config (const char *cmdline, GKeyFile *config, + #define OTCORE_PREPARE_ROOT_ENABLED_KEY "enabled" + #define OTCORE_PREPARE_ROOT_KEYPATH_KEY "keypath" + #define OTCORE_PREPARE_ROOT_TRANSIENT_KEY "transient" ++#define OTCORE_PREPARE_ROOT_TRANSIENT_RO_KEY "transient-ro" + + // The file written in the initramfs which contains an a{sv} of metadata + // from ostree-prepare-root. +@@ -118,6 +120,8 @@ RootConfig *otcore_load_rootfs_config (const char *cmdline, GKeyFile *config, + #define OTCORE_RUN_BOOTED_KEY_COMPOSEFS_SIGNATURE "composefs.signed" + // This key will be present if the root is transient + #define OTCORE_RUN_BOOTED_KEY_ROOT_TRANSIENT "root.transient" ++// This key will be present if the root is transient readonly ++#define OTCORE_RUN_BOOTED_KEY_ROOT_TRANSIENT_RO "root.transient-ro" + // This key will be present if the sysroot-ro flag was found + #define OTCORE_RUN_BOOTED_KEY_SYSROOT_RO "sysroot-ro" + // Always holds the (device, inode) pair of the booted deployment +diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c +index c6cf33c4..84c14703 100644 +--- a/src/switchroot/ostree-prepare-root.c ++++ b/src/switchroot/ostree-prepare-root.c +@@ -332,6 +332,11 @@ main (int argc, char *argv[]) + const bool sysroot_currently_writable = !path_is_on_readonly_fs (root_arg); + g_print ("sysroot.readonly configuration value: %d (fs writable: %d)\n", (int)sysroot_readonly, + (int)sysroot_currently_writable); ++ if (rootfs_config->root_transient) ++ { ++ g_print ("root.transient: %d (ro: %d)\n", (int)rootfs_config->root_transient, ++ (int)rootfs_config->root_transient_ro); ++ } + + /* Remount root MS_PRIVATE here to avoid errors due to the kernel-enforced + * constraint that disallows MS_SHARED mounts to be moved. +@@ -402,6 +407,8 @@ main (int argc, char *argv[]) + + cfs_options.workdir = root_workdir; + cfs_options.upperdir = root_upperdir; ++ if (rootfs_config->root_transient_ro) ++ cfs_options.flags = LCFS_MOUNT_FLAGS_READONLY; + } + else + { +@@ -503,6 +510,9 @@ main (int argc, char *argv[]) + g_variant_builder_add (&metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_ROOT_TRANSIENT, + g_variant_new_boolean (rootfs_config->root_transient)); + ++ g_variant_builder_add (&metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_ROOT_TRANSIENT_RO, ++ g_variant_new_boolean (rootfs_config->root_transient_ro)); ++ + /* Pass on the state for use by ostree-prepare-root */ + g_variant_builder_add (&metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_SYSROOT_RO, + g_variant_new_boolean (sysroot_readonly)); +diff --git a/tests/kolainst/destructive/root-transient-ro.sh b/tests/kolainst/destructive/root-transient-ro.sh +new file mode 100755 +index 00000000..7a8ab54a +--- /dev/null ++++ b/tests/kolainst/destructive/root-transient-ro.sh +@@ -0,0 +1,44 @@ ++#!/bin/bash ++set -xeuo pipefail ++ ++. ${KOLA_EXT_DATA}/libinsttest.sh ++ ++prepare_tmpdir ++ ++echo "testing boot=${AUTOPKGTEST_REBOOT_MARK:-}" ++ ++# Print this by default on each boot ++ostree admin status ++ ++case "${AUTOPKGTEST_REBOOT_MARK:-}" in ++ "") ++ # xref https://github.com/coreos/coreos-assembler/pull/2814 ++ systemctl mask --now zincati ++ ++ test '!' -w / ++ ++ cp /usr/lib/ostree/prepare-root.conf /etc/ostree/ ++ cat >> /etc/ostree/prepare-root.conf <<'EOF' ++[root] ++transient-ro = true ++EOF ++ ++ rpm-ostree initramfs-etc --track /etc/ostree/prepare-root.conf ++ ++ /tmp/autopkgtest-reboot "2" ++ ;; ++ "2") ++ ++ test '!' -w '/' ++ ++ unshare -m /bin/sh -c 'env LIBMOUNT_FORCE_MOUNT2=always mount -o remount,rw / && mkdir /new-dir-in-root' ++ test -d /new-dir-in-root ++ ++ test '!' -w '/' ++ ++ echo "ok root transient-ro" ++ ;; ++ *) ++ fatal "Unexpected AUTOPKGTEST_REBOOT_MARK=${AUTOPKGTEST_REBOOT_MARK}" ++ ;; ++esac +-- +2.50.1 + diff --git a/SPECS/ostree.spec b/SPECS/ostree.spec index 9d404e5..66f8a68 100644 --- a/SPECS/ostree.spec +++ b/SPECS/ostree.spec @@ -7,12 +7,14 @@ Summary: Tool for managing bootable, immutable filesystem trees Name: ostree -Version: 2025.1 -Release: 1%{?dist} +Version: 2025.2 +Release: 2%{?dist} Source0: https://github.com/ostreedev/%{name}/releases/download/v%{version}/libostree-%{version}.tar.xz Source1: ostree-readonly-sysroot-migration Source2: ostree-readonly-sysroot-migration.service +Patch0: 0001-Add-root-transient-ro.patch + License: LGPLv2+ URL: https://ostree.readthedocs.io/en/latest/ @@ -179,6 +181,15 @@ find %{buildroot} -name '*.la' -delete %endif %changelog +* Thu Aug 14 2025 Joseph Marrero - 2025.2-2 +- Backport https://github.com/ostreedev/ostree/pull/3472 + Backport https://github.com/ostreedev/ostree/pull/3473 + Resolves: #RHEL-108923 + +* Fri Jul 25 2025 Michael Armijo - 2025.2-1 +- Rebase to https://github.com/ostreedev/ostree/releases/tag/v2025.2 + Resolves: RHEL-93000 + * Mon Jan 20 2025 Joseph Marrero - 2025.1-1 - Rebase to https://github.com/ostreedev/ostree/releases/tag/v2025.1 Resolves: #RHEL-73827