Skip to content

Commit

Permalink
core: Maintain /usr/etc as /etc when running scripts
Browse files Browse the repository at this point in the history
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
  • Loading branch information
cgwalters authored and rh-atomic-bot committed Oct 5, 2018
1 parent 51e90ea commit 25b6c59
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 62 deletions.
110 changes: 66 additions & 44 deletions src/libpriv/rpmostree-bwrap.c
Original file line number Diff line number Diff line change
Expand Up @@ -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, ...)
{
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down
31 changes: 25 additions & 6 deletions src/libpriv/rpmostree-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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/"))
{
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
{
Expand Down
8 changes: 4 additions & 4 deletions src/libpriv/rpmostree-passwd-util.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
21 changes: 14 additions & 7 deletions src/libpriv/rpmostree-postprocess.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,22 @@ 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);
if (!bwrap)
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);

Expand All @@ -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;
}

Expand Down
8 changes: 7 additions & 1 deletion src/libpriv/rpmostree-scripts.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 25b6c59

Please sign in to comment.