ostree/SOURCES/0001-Add-root-transient-ro.patch

713 lines
31 KiB
Diff

From 589e0ef3d72cb31d4e6905b464281c8a6139029e Mon Sep 17 00:00:00 2001
From: Colin Walters <walters@verbum.org>
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 <walters@verbum.org>
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 <walters@verbum.org>
---
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 <walters@verbum.org>
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 <walters@verbum.org>
---
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 <walters@verbum.org>
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 <walters@verbum.org>
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 <walters@verbum.org>
---
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 <https://www.gnu.org/licenses/>.
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>root.transient-ro</varname></term>
+ <listitem><para>A boolean value; the default is <literal>false</literal>.
+ This is like <literal>root.transient</literal>, 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.
+ </para>
+ </listitem>
+ </varlistentry>
<varlistentry>
<term><varname>composefs.enabled</varname></term>
<listitem><para>This can be <literal>yes</literal>, <literal>no</literal>, <literal>maybe</literal>,
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