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