core: Maintain /usr/etc as /etc when running scripts

In preparation for running in default Docker permissions where
we can `chroot()` and `makedev()` but not e.g. create bind mounts,
move `/usr/etc` to `/etc` when running scripts.

The script processing is also entangled with our passwd/group
file handling, so change those functions called from the core too.

It's tempting to basically maintain `/usr/etc` as `/etc` all
the way from immediately after checkout to just before commit.

We can't change how we do imports now; perhaps importing
RPMs into ostree as `usr/etc` was just a mistake in retrospect,
but oh well.

Closes: #1592
Approved by: jlebon
This commit is contained in:
Colin Walters 2018-09-28 10:44:28 -04:00 committed by Atomic Bot
parent a86ad96669
commit 1d031fe51e
5 changed files with 135 additions and 80 deletions

View File

@ -25,6 +25,9 @@
#include <stdio.h>
#include <systemd/sd-journal.h>
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
@ -348,13 +366,19 @@ rpmostree_bwrap_new (int rootfs_fd,
{
case RPMOSTREE_BWRAP_IMMUTABLE:
rpmostree_bwrap_bind_read (ret, "usr", "/usr");
rpmostree_bwrap_bind_read (ret, "etc", "/etc");
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:
rpmostree_bwrap_bind_readwrite (ret, "usr", "/usr");
rpmostree_bwrap_bind_readwrite (ret, "etc", "/etc");
break;
}

View File

@ -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/"))
{
@ -3809,6 +3811,21 @@ rpmostree_context_assemble (RpmOstreeContext *self,
gboolean skip_sanity_check = FALSE;
g_variant_dict_lookup (self->spec->dict, "skip-sanity-check", "b", &skip_sanity_check);
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;
/* But leave a compat symlink, as we used to bind mount, so scripts
* could still use that too.
*/
if (symlinkat ("../etc", tmprootfs_dfd, "usr/etc") < 0)
return glnx_throw_errno_prefix (error, "symlinkat");
}
/* NB: we're not running scripts right now for removals, so this is only for overlays and
* replacements */
if (overlays->len > 0 || overrides_replace->len > 0)
@ -3900,10 +3917,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 +3933,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;
@ -4008,6 +4025,16 @@ rpmostree_context_assemble (RpmOstreeContext *self,
return FALSE;
}
/* Undo the /etc move above */
if (renamed_etc)
{
/* Remove the symlink and swap back */
if (!glnx_unlinkat (tmprootfs_dfd, "usr/etc", 0, error))
return FALSE;
if (!glnx_renameat (tmprootfs_dfd, "etc", tmprootfs_dfd, "usr/etc", error))
return FALSE;
}
/* And clean up var/tmp, we don't want it in commits */
if (!glnx_shutil_rm_rf_at (tmprootfs_dfd, "var/tmp", cancellable, error))
return FALSE;

View File

@ -1200,7 +1200,7 @@ rpmostree_passwd_prepare_rpm_layering (int rootfs_dfd,
for (guint i = 0; i < G_N_ELEMENTS (pwgrp_shadow_files); i++)
{
const char *file = pwgrp_shadow_files[i];
g_autofree char *src = g_strconcat ("usr/etc/", file, NULL);
g_autofree char *src = g_strconcat ("etc/", file, NULL);
if (!glnx_fstatat_allow_noent (rootfs_dfd, src, NULL, AT_SYMLINK_NOFOLLOW, error))
return FALSE;
@ -1221,7 +1221,7 @@ rpmostree_passwd_prepare_rpm_layering (int rootfs_dfd,
{
const char *file = usrlib_pwgrp_files[i];
g_autofree char *usrlibfile = g_strconcat ("usr/lib/", file, NULL);
g_autofree char *usretcfile = g_strconcat ("usr/etc/", file, NULL);
g_autofree char *usretcfile = g_strconcat ("etc/", file, NULL);
g_autofree char *usrlibfiletmp = g_strconcat ("usr/lib/", file, ".tmp", NULL);
/* Retain the current copies in /etc as backups */
@ -1229,7 +1229,7 @@ rpmostree_passwd_prepare_rpm_layering (int rootfs_dfd,
glnx_strjoina (usretcfile, ".rpmostreesave"), error))
return FALSE;
/* Copy /usr/lib/{passwd,group} -> /usr/etc (breaking hardlinks) */
/* Copy /usr/lib/{passwd,group} -> /etc (breaking hardlinks) */
if (!glnx_file_copy_at (rootfs_dfd, usrlibfile, NULL,
rootfs_dfd, usretcfile,
GLNX_FILE_COPY_NOXATTRS,
@ -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))
/* /etc/passwd.rpmostreesave -> /etc/passwd */
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.

View File

@ -63,6 +63,15 @@ run_bwrap_mutably (int rootfs_fd,
GCancellable *cancellable,
GError **error)
{
/* For scripts, it's /etc, not /usr/etc */
if (!glnx_renameat (rootfs_fd, "usr/etc", rootfs_fd, "etc", error))
return FALSE;
/* But leave a compat symlink, as we used to bind mount, so scripts
* could still use that too.
*/
if (symlinkat ("../etc", rootfs_fd, "usr/etc") < 0)
return glnx_throw_errno_prefix (error, "symlinkat");
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 +79,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 +99,12 @@ run_bwrap_mutably (int rootfs_fd,
if (!rpmostree_bwrap_run (bwrap, cancellable, error))
return FALSE;
/* Remove the symlink and swap back */
if (!glnx_unlinkat (rootfs_fd, "usr/etc", 0, error))
return FALSE;
if (!glnx_renameat (rootfs_fd, "etc", rootfs_fd, "usr/etc", error))
return FALSE;
return TRUE;
}
@ -1629,6 +1639,10 @@ rpmostree_treefile_postprocessing (int rootfs_fd,
if (symlinkat ("../../" RPMOSTREE_RPMDB_LOCATION, rootfs_fd, "var/lib/rpm") < 0)
return glnx_throw_errno_prefix (error, "symlinkat(%s)", "var/lib/rpm");
/* Take care of /etc for these bits */
if (!rename_if_exists (rootfs_fd, "usr/etc", rootfs_fd, "etc", error))
return FALSE;
if (json_object_has_member (treefile, "remove-from-packages"))
{
remove = json_object_get_array_member (treefile, "remove-from-packages");
@ -1639,10 +1653,6 @@ rpmostree_treefile_postprocessing (int rootfs_fd,
if (!refsack)
return glnx_prefix_error (error, "Reading package set");
/* Backwards compatibility */
if (!rename_if_exists (rootfs_fd, "usr/etc", rootfs_fd, "etc", error))
return FALSE;
for (guint i = 0; i < len; i++)
{
JsonArray *elt = json_array_get_array_element (remove, i);
@ -1650,9 +1660,6 @@ rpmostree_treefile_postprocessing (int rootfs_fd,
return FALSE;
}
/* Backwards compatibility */
if (!rename_if_exists (rootfs_fd, "etc", rootfs_fd, "usr/etc", error))
return FALSE;
}
{
@ -1682,7 +1689,6 @@ rpmostree_treefile_postprocessing (int rootfs_fd,
return FALSE;
/* map back to /etc so relative symlinks work */
rpmostree_bwrap_bind_readwrite (bwrap, "usr/etc", "/etc");
rpmostree_bwrap_append_child_argv (bwrap, "realpath", "-z", "/etc/os-release", NULL);
g_autoptr(GBytes) out = NULL;
@ -1698,16 +1704,11 @@ rpmostree_treefile_postprocessing (int rootfs_fd,
pathbuf[len-1] = '\0';
path = pathbuf+1; /* skip initial '/' */
if (g_str_has_prefix (path, "etc/"))
{
g_autofree char *old_pathbuf = pathbuf;
path = pathbuf = g_strdup_printf ("usr/%s", path);
}
}
/* fallback on just overwriting etc/os-release */
if (!path)
path = "usr/etc/os-release";
path = "etc/os-release";
g_print ("Mutating /%s\n", path);
@ -1728,6 +1729,10 @@ rpmostree_treefile_postprocessing (int rootfs_fd,
}
}
/* Undo etc move again */
if (!rename_if_exists (rootfs_fd, "etc", rootfs_fd, "usr/etc", error))
return FALSE;
/* Copy in additional files before postprocessing */
if (!copy_additional_files (rootfs_fd, treefile_rs, treefile, cancellable, error))
return FALSE;

View File

@ -953,7 +953,6 @@ rpmostree_deployment_sanitycheck_true (int rootfs_fd,
if (!bwrap)
return FALSE;
rpmostree_bwrap_bind_read (bwrap, "./usr/etc", "/etc");
rpmostree_bwrap_append_child_argv (bwrap, "/usr/bin/true", NULL);
if (!rpmostree_bwrap_run (bwrap, cancellable, error))
return FALSE;