diff --git a/src/libpriv/rpmostree-bwrap.c b/src/libpriv/rpmostree-bwrap.c index 982146c785..e2f8bdfb57 100644 --- a/src/libpriv/rpmostree-bwrap.c +++ b/src/libpriv/rpmostree-bwrap.c @@ -25,6 +25,9 @@ #include #include +static void +teardown_rofiles (GLnxTmpDir *mnt_tmp); + void rpmostree_ptrarray_append_strdup (GPtrArray *argv_array, ...) { @@ -47,7 +50,8 @@ struct RpmOstreeBwrap { GSubprocessLauncher *launcher; /* 🚀 */ GPtrArray *argv; const char *child_argv0; - GLnxTmpDir rofiles_mnt; + GLnxTmpDir rofiles_mnt_usr; + GLnxTmpDir rofiles_mnt_etc; GSpawnChildSetupFunc child_setup_func; gpointer child_setup_data; @@ -67,37 +71,8 @@ rpmostree_bwrap_unref (RpmOstreeBwrap *bwrap) if (bwrap->refcount > 0) return; - if (bwrap->rofiles_mnt.initialized) - { - g_autoptr(GError) tmp_error = NULL; - const char *fusermount_argv[] = { "fusermount", "-u", bwrap->rofiles_mnt.path, NULL}; - int estatus; - - if (!g_spawn_sync (NULL, (char**)fusermount_argv, NULL, G_SPAWN_SEARCH_PATH, - NULL, NULL, NULL, NULL, &estatus, &tmp_error)) - { - g_prefix_error (&tmp_error, "Executing fusermount: "); - goto out; - } - if (!g_spawn_check_exit_status (estatus, &tmp_error)) - { - g_prefix_error (&tmp_error, "Executing fusermount: "); - goto out; - } - - out: - /* We don't want a failure to unmount to be fatal, so all we do here - * is log. Though in practice what we *really* want is for the - * fusermount to be in the bwrap namespace, and hence tied by the - * kernel to the lifecycle of the container. This would require - * special casing for somehow doing FUSE mounts in bwrap. Which - * would be hard because NO_NEW_PRIVS turns off the setuid bits for - * fuse. - */ - if (tmp_error) - sd_journal_print (LOG_WARNING, "%s", tmp_error->message); - } - (void)glnx_tmpdir_delete (&bwrap->rofiles_mnt, NULL, NULL); + teardown_rofiles (&bwrap->rofiles_mnt_usr); + teardown_rofiles (&bwrap->rofiles_mnt_etc); g_clear_object (&bwrap->launcher); g_ptr_array_unref (bwrap->argv); @@ -189,15 +164,20 @@ child_setup_fchdir (gpointer user_data) } static gboolean -setup_rofiles_usr (RpmOstreeBwrap *bwrap, - GError **error) +setup_rofiles (RpmOstreeBwrap *bwrap, + const char *path, + GLnxTmpDir *mnt_tmp, + GError **error) { - const char *rofiles_argv[] = { "rofiles-fuse", "--copyup", "./usr", NULL, NULL}; + GLNX_AUTO_PREFIX_ERROR ("rofiles setup", error); + const char *relpath = path + strspn (path, "/"); + const char *rofiles_argv[] = { "rofiles-fuse", "--copyup", relpath, NULL, NULL}; - if (!glnx_mkdtemp ("rpmostree-rofiles-fuse.XXXXXX", 0700, &bwrap->rofiles_mnt, error)) + g_auto(GLnxTmpDir) local_mnt_tmp = { 0, }; + if (!glnx_mkdtemp ("rpmostree-rofiles-fuse.XXXXXX", 0700, &local_mnt_tmp, error)) return FALSE; - const char *rofiles_mntpath = bwrap->rofiles_mnt.path; + const char *rofiles_mntpath = local_mnt_tmp.path; rofiles_argv[3] = rofiles_mntpath; int estatus; @@ -208,17 +188,55 @@ setup_rofiles_usr (RpmOstreeBwrap *bwrap, if (!g_spawn_check_exit_status (estatus, error)) return FALSE; - rpmostree_bwrap_bind_readwrite (bwrap, rofiles_mntpath, "/usr"); + rpmostree_bwrap_bind_readwrite (bwrap, rofiles_mntpath, path); - /* also mount /etc from the rofiles mount to allow RPM scripts to change defaults, while - * still being protected; note we use bind to ensure symlinks work, see: - * https://github.com/projectatomic/rpm-ostree/pull/640 */ - const char *rofiles_etc_mntpath = glnx_strjoina (rofiles_mntpath, "/etc"); - rpmostree_bwrap_bind_readwrite (bwrap, rofiles_etc_mntpath, "/etc"); + /* And transfer ownership of the tmpdir */ + *mnt_tmp = local_mnt_tmp; + local_mnt_tmp.initialized = FALSE; return TRUE; } +static void +teardown_rofiles (GLnxTmpDir *mnt_tmp) +{ + g_assert (mnt_tmp); + if (!mnt_tmp->initialized) + return; + + g_autoptr(GError) tmp_error = NULL; + const char *fusermount_argv[] = { "fusermount", "-u", mnt_tmp->path, NULL}; + int estatus; + + GLNX_AUTO_PREFIX_ERROR ("rofiles teardown", &tmp_error); + + if (!g_spawn_sync (NULL, (char**)fusermount_argv, NULL, G_SPAWN_SEARCH_PATH, + NULL, NULL, NULL, NULL, &estatus, &tmp_error)) + { + g_prefix_error (&tmp_error, "Executing fusermount: "); + goto out; + } + if (!g_spawn_check_exit_status (estatus, &tmp_error)) + { + g_prefix_error (&tmp_error, "Executing fusermount: "); + goto out; + } + + (void)glnx_tmpdir_delete (mnt_tmp, NULL, NULL); + + out: + /* We don't want a failure to unmount to be fatal, so all we do here + * is log. Though in practice what we *really* want is for the + * fusermount to be in the bwrap namespace, and hence tied by the + * kernel to the lifecycle of the container. This would require + * special casing for somehow doing FUSE mounts in bwrap. Which + * would be hard because NO_NEW_PRIVS turns off the setuid bits for + * fuse. + */ + if (tmp_error) + sd_journal_print (LOG_WARNING, "%s", tmp_error->message); +} + /* nspawn by default doesn't give us CAP_NET_ADMIN; see * https://pagure.io/releng/issue/6602#comment-71214 * https://pagure.io/koji/pull-request/344#comment-21060 @@ -350,7 +368,11 @@ rpmostree_bwrap_new (int rootfs_fd, rpmostree_bwrap_bind_read (ret, "usr", "/usr"); break; case RPMOSTREE_BWRAP_MUTATE_ROFILES: - if (!setup_rofiles_usr (ret, error)) + if (!setup_rofiles (ret, "/usr", + &ret->rofiles_mnt_usr, error)) + return NULL; + if (!setup_rofiles (ret, "/etc", + &ret->rofiles_mnt_etc, error)) return NULL; break; case RPMOSTREE_BWRAP_MUTATE_FREELY: diff --git a/src/libpriv/rpmostree-core.c b/src/libpriv/rpmostree-core.c index f736cb31f9..d54aad065d 100644 --- a/src/libpriv/rpmostree-core.c +++ b/src/libpriv/rpmostree-core.c @@ -3301,8 +3301,10 @@ apply_rpmfi_overrides (RpmOstreeContext *self, continue; else if (g_str_has_prefix (fn, "etc/")) { - /* The tree uses usr/etc */ - fn = modified_fn = g_strconcat ("usr/", fn, NULL); + /* Changing /etc is OK; note "normally" we maintain + * usr/etc but this runs right after %pre, where + * we're in the middle of running scripts. + */ } else if (!g_str_has_prefix (fn, "usr/")) { @@ -3876,6 +3878,16 @@ rpmostree_context_assemble (RpmOstreeContext *self, &var_lib_rpm_statedir, error)) return FALSE; + if (!glnx_fstatat_allow_noent (tmprootfs_dfd, "usr/etc", NULL, 0, error)) + return FALSE; + gboolean renamed_etc = (errno == 0); + if (renamed_etc) + { + /* In general now, we place contents in /etc when running scripts */ + if (!glnx_renameat (tmprootfs_dfd, "usr/etc", tmprootfs_dfd, "etc", error)) + return FALSE; + } + /* We're technically deviating from RPM here by running all the %pre's * beforehand, rather than each package's %pre & %post in order. Though I * highly doubt this should cause any issues. The advantage of doing it @@ -3900,10 +3912,10 @@ rpmostree_context_assemble (RpmOstreeContext *self, } rpmostree_output_task_end ("%u done", n_pre_scripts_run); - if (faccessat (tmprootfs_dfd, "usr/etc/passwd", F_OK, 0) == 0) + if (faccessat (tmprootfs_dfd, "etc/passwd", F_OK, 0) == 0) { g_autofree char *contents = - glnx_file_get_contents_utf8_at (tmprootfs_dfd, "usr/etc/passwd", + glnx_file_get_contents_utf8_at (tmprootfs_dfd, "etc/passwd", NULL, cancellable, error); if (!contents) return FALSE; @@ -3916,10 +3928,10 @@ rpmostree_context_assemble (RpmOstreeContext *self, } } - if (faccessat (tmprootfs_dfd, "usr/etc/group", F_OK, 0) == 0) + if (faccessat (tmprootfs_dfd, "etc/group", F_OK, 0) == 0) { g_autofree char *contents = - glnx_file_get_contents_utf8_at (tmprootfs_dfd, "usr/etc/group", + glnx_file_get_contents_utf8_at (tmprootfs_dfd, "etc/group", NULL, cancellable, error); if (!contents) return FALSE; @@ -3999,6 +4011,13 @@ rpmostree_context_assemble (RpmOstreeContext *self, if (!rpmostree_passwd_complete_rpm_layering (tmprootfs_dfd, error)) return FALSE; } + + /* Undo the /etc move above */ + if (renamed_etc) + { + if (!glnx_renameat (tmprootfs_dfd, "etc", tmprootfs_dfd, "usr/etc", error)) + return FALSE; + } } else { diff --git a/src/libpriv/rpmostree-passwd-util.c b/src/libpriv/rpmostree-passwd-util.c index 834ed7cbab..d91ddb30d2 100644 --- a/src/libpriv/rpmostree-passwd-util.c +++ b/src/libpriv/rpmostree-passwd-util.c @@ -1261,13 +1261,13 @@ rpmostree_passwd_complete_rpm_layering (int rootfs_dfd, for (guint i = 0; i < G_N_ELEMENTS (usrlib_pwgrp_files); i++) { const char *file = usrlib_pwgrp_files[i]; - /* And now the inverse: /usr/etc/passwd -> /usr/lib/passwd */ - if (!glnx_renameat (rootfs_dfd, glnx_strjoina ("usr/etc/", file), + /* And now the inverse: /etc/passwd -> /usr/lib/passwd */ + if (!glnx_renameat (rootfs_dfd, glnx_strjoina ("etc/", file), rootfs_dfd, glnx_strjoina ("usr/lib/", file), error)) return FALSE; /* /usr/etc/passwd.rpmostreesave -> /usr/etc/passwd */ - if (!glnx_renameat (rootfs_dfd, glnx_strjoina ("usr/etc/", file, ".rpmostreesave"), - rootfs_dfd, glnx_strjoina ("usr/etc/", file), error)) + if (!glnx_renameat (rootfs_dfd, glnx_strjoina ("etc/", file, ".rpmostreesave"), + rootfs_dfd, glnx_strjoina ("etc/", file), error)) return FALSE; } /* However, we leave the (potentially modified) shadow files in place. diff --git a/src/libpriv/rpmostree-postprocess.c b/src/libpriv/rpmostree-postprocess.c index a9ec4281e0..40d4dffabe 100644 --- a/src/libpriv/rpmostree-postprocess.c +++ b/src/libpriv/rpmostree-postprocess.c @@ -63,6 +63,12 @@ run_bwrap_mutably (int rootfs_fd, GCancellable *cancellable, GError **error) { + if (unified_core_mode) + { + if (!glnx_renameat (rootfs_fd, "usr/etc", rootfs_fd, "etc", error)) + return FALSE; + } + RpmOstreeBwrapMutability mut = unified_core_mode ? RPMOSTREE_BWRAP_MUTATE_ROFILES : RPMOSTREE_BWRAP_MUTATE_FREELY; g_autoptr(RpmOstreeBwrap) bwrap = rpmostree_bwrap_new (rootfs_fd, mut, error); @@ -70,14 +76,9 @@ run_bwrap_mutably (int rootfs_fd, return FALSE; if (unified_core_mode) - { - rpmostree_bwrap_bind_read (bwrap, "./var", "/var"); - } + rpmostree_bwrap_bind_read (bwrap, "var", "/var"); else - { - rpmostree_bwrap_bind_readwrite (bwrap, "var", "/var"); - rpmostree_bwrap_bind_readwrite (bwrap, "usr/etc", "/etc"); - } + rpmostree_bwrap_bind_readwrite (bwrap, "var", "/var"); rpmostree_bwrap_append_child_argv (bwrap, binpath, NULL); @@ -95,6 +96,12 @@ run_bwrap_mutably (int rootfs_fd, if (!rpmostree_bwrap_run (bwrap, cancellable, error)) return FALSE; + if (unified_core_mode) + { + if (!glnx_renameat (rootfs_fd, "etc", rootfs_fd, "usr/etc", error)) + return FALSE; + } + return TRUE; } diff --git a/src/libpriv/rpmostree-scripts.c b/src/libpriv/rpmostree-scripts.c index 3596cf6d18..c529d474ba 100644 --- a/src/libpriv/rpmostree-scripts.c +++ b/src/libpriv/rpmostree-scripts.c @@ -953,7 +953,13 @@ rpmostree_deployment_sanitycheck_true (int rootfs_fd, if (!bwrap) return FALSE; - rpmostree_bwrap_bind_read (bwrap, "./usr/etc", "/etc"); + /* This function can be run both via the core, where we maintain etc as /etc, + * or post-deployment checkout, where it's usr/etc. + */ + if (!glnx_fstatat_allow_noent (rootfs_fd, "usr/etc", NULL, AT_SYMLINK_NOFOLLOW, error)) + return FALSE; + const char *bind_src = (errno == ENOENT) ? "etc" : "usr/etc"; + rpmostree_bwrap_bind_read (bwrap, bind_src, "/etc"); rpmostree_bwrap_append_child_argv (bwrap, "/usr/bin/true", NULL); if (!rpmostree_bwrap_run (bwrap, cancellable, error)) return FALSE;