libpriv: Enhance internal bwrap API

Make it a real struct with methods.  This noticeably increases
the ergonomics and design of the API.

The main goal here is to introduce the enum which defines whether or not the
rootfs is mutable or not. We move the "rofiles" mode from the RPM script code
down into the bwrap layer, which will make it easier to reuse for treecompose.

Closes: #560
Approved by: jlebon
This commit is contained in:
Colin Walters 2017-01-05 21:57:41 -05:00 committed by Atomic Bot
parent 50ab2983ab
commit f0ec738376
4 changed files with 297 additions and 198 deletions

View File

@ -22,6 +22,8 @@
#include "rpmostree-bwrap.h"
#include <err.h>
#include <stdio.h>
#include <systemd/sd-journal.h>
void
rpmostree_ptrarray_append_strdup (GPtrArray *argv_array, ...)
@ -35,30 +37,180 @@ rpmostree_ptrarray_append_strdup (GPtrArray *argv_array, ...)
va_end (args);
}
GPtrArray *
rpmostree_bwrap_base_argv_new_for_rootfs (int rootfs_fd, GError **error)
struct RpmOstreeBwrap {
guint refcount;
gboolean executed;
int rootfs_fd;
GPtrArray *argv;
const char *child_argv0;
char *rofiles_mnt;
GSpawnChildSetupFunc child_setup_func;
gpointer child_setup_data;
};
RpmOstreeBwrap *
rpmostree_bwrap_ref (RpmOstreeBwrap *bwrap)
{
g_autoptr(GPtrArray) bwrap_argv = g_ptr_array_new_with_free_func (g_free);
bwrap->refcount++;
return bwrap;
}
void
rpmostree_bwrap_unref (RpmOstreeBwrap *bwrap)
{
bwrap->refcount--;
if (bwrap->refcount > 0)
return;
if (bwrap->rofiles_mnt)
{
g_autoptr(GError) tmp_error = NULL;
const char *fusermount_argv[] = { "fusermount", "-u", bwrap->rofiles_mnt, 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;
}
(void) unlinkat (AT_FDCWD, bwrap->rofiles_mnt, AT_REMOVEDIR);
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);
}
g_ptr_array_unref (bwrap->argv);
g_free (bwrap->rofiles_mnt);
g_free (bwrap);
}
void
rpmostree_bwrap_append_bwrap_argv (RpmOstreeBwrap *bwrap, ...)
{
va_list args;
char *arg;
g_assert (!bwrap->executed);
va_start (args, bwrap);
while ((arg = va_arg (args, char *)))
g_ptr_array_add (bwrap->argv, g_strdup (arg));
va_end (args);
}
void
rpmostree_bwrap_append_child_argv (RpmOstreeBwrap *bwrap, ...)
{
va_list args;
char *arg;
g_assert (!bwrap->executed);
va_start (args, bwrap);
while ((arg = va_arg (args, char *)))
{
char *v = g_strdup (arg);
g_ptr_array_add (bwrap->argv, v);
/* Stash argv0 for error messages */
if (!bwrap->child_argv0)
bwrap->child_argv0 = v;
}
va_end (args);
}
static void
child_setup_fchdir (gpointer user_data)
{
int fd = GPOINTER_TO_INT (user_data);
if (fchdir (fd) < 0)
err (1, "fchdir");
}
static gboolean
setup_rofiles_usr (RpmOstreeBwrap *bwrap,
GError **error)
{
gboolean ret = FALSE;
int estatus;
const char *rofiles_argv[] = { "rofiles-fuse", "./usr", NULL, NULL};
gboolean mntpoint_created = FALSE;
bwrap->rofiles_mnt = g_strdup ("/tmp/rofiles-fuse.XXXXXX");
rofiles_argv[2] = bwrap->rofiles_mnt;
if (!glnx_mkdtempat (AT_FDCWD, bwrap->rofiles_mnt, 0700, error))
goto out;
mntpoint_created = TRUE;
if (!g_spawn_sync (NULL, (char**)rofiles_argv, NULL, G_SPAWN_SEARCH_PATH,
child_setup_fchdir, GINT_TO_POINTER (bwrap->rootfs_fd),
NULL, NULL, &estatus, error))
goto out;
if (!g_spawn_check_exit_status (estatus, error))
goto out;
rpmostree_bwrap_append_bwrap_argv (bwrap, "--bind", bwrap->rofiles_mnt, "/usr", NULL);
ret = TRUE;
out:
if (!ret && mntpoint_created)
(void) unlinkat (AT_FDCWD, bwrap->rofiles_mnt, AT_REMOVEDIR);
return ret;
}
RpmOstreeBwrap *
rpmostree_bwrap_new (int rootfs_fd,
RpmOstreeBwrapMutability mutable,
GError **error,
...)
{
va_list args;
RpmOstreeBwrap *retval = NULL;
g_autoptr(RpmOstreeBwrap) ret = g_new0 (RpmOstreeBwrap, 1);
static const char *usr_links[] = {"lib", "lib32", "lib64", "bin", "sbin"};
rpmostree_ptrarray_append_strdup (bwrap_argv,
WITH_BUBBLEWRAP_PATH,
"--dev", "/dev",
"--proc", "/proc",
"--dir", "/tmp",
"--chdir", "/",
"--ro-bind", "/sys/block", "/sys/block",
"--ro-bind", "/sys/bus", "/sys/bus",
"--ro-bind", "/sys/class", "/sys/class",
"--ro-bind", "/sys/dev", "/sys/dev",
"--ro-bind", "/sys/devices", "/sys/devices",
NULL);
ret->refcount = 1;
ret->rootfs_fd = rootfs_fd;
ret->argv = g_ptr_array_new_with_free_func (g_free);
rpmostree_bwrap_append_bwrap_argv (ret,
WITH_BUBBLEWRAP_PATH,
"--dev", "/dev",
"--proc", "/proc",
"--dir", "/tmp",
"--chdir", "/",
"--ro-bind", "/sys/block", "/sys/block",
"--ro-bind", "/sys/bus", "/sys/bus",
"--ro-bind", "/sys/class", "/sys/class",
"--ro-bind", "/sys/dev", "/sys/dev",
"--ro-bind", "/sys/devices", "/sys/devices",
NULL);
for (guint i = 0; i < G_N_ELEMENTS (usr_links); i++)
{
const char *subdir = usr_links[i];
struct stat stbuf;
char *path;
g_autofree char *srcpath = NULL;
g_autofree char *destpath = NULL;
if (fstatat (rootfs_fd, subdir, &stbuf, AT_SYMLINK_NOFOLLOW) < 0)
{
@ -72,70 +224,68 @@ rpmostree_bwrap_base_argv_new_for_rootfs (int rootfs_fd, GError **error)
else if (!S_ISLNK (stbuf.st_mode))
continue;
g_ptr_array_add (bwrap_argv, g_strdup ("--symlink"));
path = g_strconcat ("usr/", subdir, NULL);
g_ptr_array_add (bwrap_argv, path);
path = g_strconcat ("/", subdir, NULL);
g_ptr_array_add (bwrap_argv, path);
srcpath = g_strconcat ("usr/", subdir, NULL);
destpath = g_strconcat ("/", subdir, NULL);
rpmostree_bwrap_append_bwrap_argv (ret, "--symlink", srcpath, destpath, NULL);
}
return g_steal_pointer (&bwrap_argv);
switch (mutable)
{
case RPMOSTREE_BWRAP_IMMUTABLE:
rpmostree_bwrap_append_bwrap_argv (ret, "--ro-bind", "usr", "/usr", NULL);
break;
case RPMOSTREE_BWRAP_MUTATE_ROFILES:
if (!setup_rofiles_usr (ret, error))
goto out;
break;
case RPMOSTREE_BWRAP_MUTATE_FREELY:
rpmostree_bwrap_append_bwrap_argv (ret, "--bind", "usr", "/usr", NULL);
break;
}
{ const char *arg;
va_start (args, error);
while ((arg = va_arg (args, char *)))
g_ptr_array_add (ret->argv, g_strdup (arg));
va_end (args);
}
retval = g_steal_pointer (&ret);
out:
return retval;
}
static void
child_setup_fchdir (gpointer user_data)
{
int fd = GPOINTER_TO_INT (user_data);
if (fchdir (fd) < 0)
err (1, "fchdir");
}
gboolean
rpmostree_run_sync_fchdir_setup (char **argv_array, GSpawnFlags flags,
int rootfs_fd, GError **error)
{
int estatus;
if (!g_spawn_sync (NULL, argv_array, NULL, flags,
child_setup_fchdir, GINT_TO_POINTER (rootfs_fd),
NULL, NULL, &estatus, error))
return FALSE;
if (!g_spawn_check_exit_status (estatus, error))
return FALSE;
return TRUE;
}
typedef struct {
int rootfs_fd;
GSpawnChildSetupFunc func;
gpointer data;
} ChildSetupData;
static void
bwrap_child_setup (gpointer data)
{
ChildSetupData *cdata = data;
RpmOstreeBwrap *bwrap = data;
if (fchdir (cdata->rootfs_fd) < 0)
if (fchdir (bwrap->rootfs_fd) < 0)
err (1, "fchdir");
if (cdata->func)
cdata->func (cdata->data);
if (bwrap->child_setup_func)
bwrap->child_setup_func (bwrap->child_setup_data);
}
void
rpmostree_bwrap_set_child_setup (RpmOstreeBwrap *bwrap,
GSpawnChildSetupFunc func,
gpointer data)
{
g_assert (!bwrap->executed);
bwrap->child_setup_func = func;
bwrap->child_setup_data = data;
}
gboolean
rpmostree_run_bwrap_sync_setup (char **argv_array,
int rootfs_fd,
GSpawnChildSetupFunc func,
gpointer data,
GError **error)
rpmostree_bwrap_run (RpmOstreeBwrap *bwrap,
GError **error)
{
int estatus;
const char *current_lang = getenv ("LANG");
ChildSetupData csetupdata = { rootfs_fd, func, data };
g_assert (!bwrap->executed);
bwrap->executed = TRUE;
if (!current_lang)
current_lang = "C";
@ -151,43 +301,43 @@ rpmostree_run_bwrap_sync_setup (char **argv_array,
lang_var,
NULL};
if (!g_spawn_sync (NULL, argv_array, (char**) bwrap_env, G_SPAWN_SEARCH_PATH,
bwrap_child_setup, &csetupdata,
/* Add the final NULL */
g_ptr_array_add (bwrap->argv, NULL);
if (!g_spawn_sync (NULL, (char**)bwrap->argv->pdata, (char**) bwrap_env, G_SPAWN_SEARCH_PATH,
bwrap_child_setup, bwrap,
NULL, NULL, &estatus, error))
return FALSE;
{
g_prefix_error (error, "Executing bwrap(%s): ", bwrap->child_argv0);
return FALSE;
}
if (!g_spawn_check_exit_status (estatus, error))
return FALSE;
{
g_prefix_error (error, "Executing bwrap(%s): ", bwrap->child_argv0);
return FALSE;
}
}
return TRUE;
}
gboolean
rpmostree_run_bwrap_sync (char **argv_array, int rootfs_fd, GError **error)
{
return rpmostree_run_bwrap_sync_setup (argv_array, rootfs_fd, NULL, NULL, error);
}
/* Execute /bin/true inside a bwrap container on the host */
gboolean
rpmostree_bwrap_selftest (GError **error)
{
glnx_fd_close int host_root_dfd = -1;
g_autoptr(GPtrArray) bwrap_argv = NULL;
g_autoptr(RpmOstreeBwrap) bwrap = NULL;
if (!glnx_opendirat (AT_FDCWD, "/", TRUE, &host_root_dfd, error))
return FALSE;
bwrap_argv = rpmostree_bwrap_base_argv_new_for_rootfs (host_root_dfd, error);
if (!bwrap_argv)
bwrap = rpmostree_bwrap_new (host_root_dfd, RPMOSTREE_BWRAP_IMMUTABLE, error, NULL);
if (!bwrap)
return FALSE;
rpmostree_ptrarray_append_strdup (bwrap_argv,
"--ro-bind", "usr", "/usr",
NULL);
g_ptr_array_add (bwrap_argv, g_strdup ("true"));
g_ptr_array_add (bwrap_argv, NULL);
if (!rpmostree_run_bwrap_sync ((char**)bwrap_argv->pdata, host_root_dfd, error))
rpmostree_bwrap_append_child_argv (bwrap, "true", NULL);
if (!rpmostree_bwrap_run (bwrap, error))
{
g_prefix_error (error, "bwrap test failed, see <https://github.com/projectatomic/rpm-ostree/pull/429>: ");
return FALSE;

View File

@ -24,17 +24,32 @@
#include "libglnx.h"
GPtrArray *rpmostree_bwrap_base_argv_new_for_rootfs (int rootfs, GError **error);
typedef enum {
RPMOSTREE_BWRAP_IMMUTABLE = 0,
RPMOSTREE_BWRAP_MUTATE_ROFILES,
RPMOSTREE_BWRAP_MUTATE_FREELY
} RpmOstreeBwrapMutability;
typedef struct RpmOstreeBwrap RpmOstreeBwrap;
RpmOstreeBwrap *rpmostree_bwrap_ref (RpmOstreeBwrap *bwrap);
void rpmostree_bwrap_unref (RpmOstreeBwrap *bwrap);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(RpmOstreeBwrap, rpmostree_bwrap_unref)
/* TODO - move this utility elsewhere */
void rpmostree_ptrarray_append_strdup (GPtrArray *argv_array, ...) G_GNUC_NULL_TERMINATED;
gboolean rpmostree_run_sync_fchdir_setup (char **argv_array, GSpawnFlags flags,
int rootfs_fd, GError **error);
RpmOstreeBwrap *rpmostree_bwrap_new (int rootfs,
RpmOstreeBwrapMutability mutable,
GError **error,
...) G_GNUC_NULL_TERMINATED;
gboolean rpmostree_run_bwrap_sync (char **argv_array, int rootfs_fd, GError **error);
gboolean rpmostree_run_bwrap_sync_setup (char **argv_array, int rootfs_fd,
GSpawnChildSetupFunc func,
gpointer data,
GError **error);
void rpmostree_bwrap_append_bwrap_argv (RpmOstreeBwrap *bwrap, ...) G_GNUC_NULL_TERMINATED;
void rpmostree_bwrap_append_child_argv (RpmOstreeBwrap *bwrap, ...) G_GNUC_NULL_TERMINATED;
void rpmostree_bwrap_set_child_setup (RpmOstreeBwrap *bwrap,
GSpawnChildSetupFunc func,
gpointer data);
gboolean rpmostree_bwrap_run (RpmOstreeBwrap *bwrap, GError **error);
gboolean rpmostree_bwrap_selftest (GError **error);

View File

@ -58,22 +58,20 @@ run_sync_in_root_at (int rootfs_fd,
gpointer data,
GError **error)
{
g_autoptr(GPtrArray) bwrap_argv = NULL;
bwrap_argv = rpmostree_bwrap_base_argv_new_for_rootfs (rootfs_fd, error);
if (!bwrap_argv)
return FALSE;
g_autoptr(RpmOstreeBwrap) bwrap = NULL;
/* Bind all of the primary toplevel dirs; unlike the script case, treecompose
* isn't yet operating on hardlinks, so we can just bind mount things mutably.
*/
rpmostree_ptrarray_append_strdup (bwrap_argv,
"--bind", "usr", "/usr",
"--bind", "var", "/var",
"--bind", "etc", "/etc",
NULL);
bwrap = rpmostree_bwrap_new (rootfs_fd, RPMOSTREE_BWRAP_MUTATE_FREELY, error,
"--bind", "var", "/var",
"--bind", "etc", "/etc",
NULL);
if (!bwrap)
return FALSE;
rpmostree_bwrap_append_child_argv (bwrap, binpath, NULL);
g_ptr_array_add (bwrap_argv, g_strdup (binpath));
/* https://github.com/projectatomic/bubblewrap/issues/91 */
{ gboolean first = TRUE;
for (char **iter = child_argv; iter && *iter; iter++)
@ -81,17 +79,15 @@ run_sync_in_root_at (int rootfs_fd,
if (first)
first = FALSE;
else
g_ptr_array_add (bwrap_argv, g_strdup (*iter));
rpmostree_bwrap_append_child_argv (bwrap, *iter, NULL);
}
}
g_ptr_array_add (bwrap_argv, NULL);
if (!rpmostree_run_bwrap_sync_setup ((char**)bwrap_argv->pdata, rootfs_fd,
setup_func, data, error))
{
g_prefix_error (error, "Executing bwrap(%s): ", child_argv[0]);
return FALSE;
}
if (setup_func)
rpmostree_bwrap_set_child_setup (bwrap, setup_func, data);
if (!rpmostree_bwrap_run (bwrap, error))
return FALSE;
return TRUE;
}

View File

@ -21,7 +21,6 @@
#include "config.h"
#include <gio/gio.h>
#include <systemd/sd-journal.h>
#include "rpmostree-output.h"
#include "rpmostree-bwrap.h"
#include <err.h>
@ -77,38 +76,6 @@ static const KnownRpmScriptKind unsupported_scripts[] = {
RPMTAG_VERIFYSCRIPT, RPMTAG_VERIFYSCRIPTPROG, RPMTAG_VERIFYSCRIPTFLAGS},
};
static void
fusermount_cleanup (const char *mountpoint)
{
g_autoptr(GError) tmp_error = NULL;
const char *fusermount_argv[] = { "fusermount", "-u", mountpoint, 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);
}
static RpmOstreeScriptAction
lookup_script_action (DnfPackage *package,
GHashTable *ignored_scripts,
@ -172,44 +139,27 @@ run_script_in_bwrap_container (int rootfs_fd,
GError **error)
{
gboolean ret = FALSE;
char *rofiles_mnt = strdupa ("/tmp/rofiles-fuse.XXXXXX");
const char *rofiles_argv[] = { "rofiles-fuse", "./usr", rofiles_mnt, NULL};
const char *pkg_script = glnx_strjoina (name, ".", scriptdesc+1);
const char *postscript_name = glnx_strjoina ("/", pkg_script);
const char *postscript_path_container = glnx_strjoina ("/usr/", postscript_name);
const char *postscript_path_host;
gboolean mntpoint_created = FALSE;
gboolean fuse_mounted = FALSE;
g_autoptr(GPtrArray) bwrap_argv = g_ptr_array_new ();
const char *postscript_path_container = glnx_strjoina ("/usr", postscript_name);
const char *postscript_path_host = postscript_path_container + 1;
g_autoptr(RpmOstreeBwrap) bwrap = NULL;
gboolean created_var_tmp = FALSE;
if (!glnx_mkdtempat (AT_FDCWD, rofiles_mnt, 0700, error))
goto out;
mntpoint_created = TRUE;
if (!rpmostree_run_sync_fchdir_setup ((char**)rofiles_argv, G_SPAWN_SEARCH_PATH,
rootfs_fd, error))
{
g_prefix_error (error, "Executing rofiles-fuse: ");
goto out;
}
fuse_mounted = TRUE;
postscript_path_host = glnx_strjoina (rofiles_mnt, "/", postscript_name);
/* TODO - Create a pipe and send this to bwrap so it's inside the
* tmpfs
* tmpfs. Note the +1 on the path to skip the leading /.
*/
if (!g_file_set_contents (postscript_path_host, script, -1, error))
if (!glnx_file_replace_contents_at (rootfs_fd, postscript_path_host,
(guint8*)script, -1,
GLNX_FILE_REPLACE_NODATASYNC,
NULL, error))
{
g_prefix_error (error, "Writing script to %s: ", postscript_path_host);
goto out;
}
if (chmod (postscript_path_host, 0755) != 0)
if (fchmodat (rootfs_fd, postscript_path_host, 0755, 0) != 0)
{
g_prefix_error (error, "chmod %s: ", postscript_path_host);
glnx_set_error_from_errno (error);
goto out;
}
@ -236,40 +186,28 @@ run_script_in_bwrap_container (int rootfs_fd,
else
created_var_tmp = TRUE;
bwrap_argv = rpmostree_bwrap_base_argv_new_for_rootfs (rootfs_fd, error);
if (!bwrap_argv)
bwrap = rpmostree_bwrap_new (rootfs_fd, RPMOSTREE_BWRAP_MUTATE_ROFILES, error,
/* Scripts can see a /var with compat links like alternatives */
"--ro-bind", "./var", "/var",
/* But no need to access persistent /tmp, so make it /tmp */
"--bind", "/tmp", "/var/tmp",
/* Allow RPM scripts to change the /etc defaults */
"--symlink", "usr/etc", "/etc",
NULL);
if (!bwrap)
goto out;
rpmostree_ptrarray_append_strdup (bwrap_argv,
"--bind", rofiles_mnt, "/usr",
/* Scripts can see a /var with compat links like alternatives */
"--ro-bind", "./var", "/var",
/* But no need to access persistent /tmp, so make it /tmp */
"--bind", "/tmp", "/var/tmp",
/* Allow RPM scripts to change the /etc defaults */
"--symlink", "usr/etc", "/etc",
NULL);
rpmostree_bwrap_append_child_argv (bwrap,
postscript_path_container,
/* http://www.rpm.org/max-rpm/s1-rpm-inside-scripts.html#S3-RPM-INSIDE-PRE-SCRIPT */
"1",
NULL);
g_ptr_array_add (bwrap_argv, g_strdup (postscript_path_container));
/* http://www.rpm.org/max-rpm/s1-rpm-inside-scripts.html#S3-RPM-INSIDE-PRE-SCRIPT */
g_ptr_array_add (bwrap_argv, g_strdup ("1"));
g_ptr_array_add (bwrap_argv, NULL);
if (!rpmostree_run_bwrap_sync ((char**)bwrap_argv->pdata, rootfs_fd, error))
{
g_prefix_error (error, "Executing bwrap: ");
goto out;
}
if (!rpmostree_bwrap_run (bwrap, error))
goto out;
ret = TRUE;
out:
if (fuse_mounted)
{
(void) unlink (postscript_path_host);
fusermount_cleanup (rofiles_mnt);
}
if (mntpoint_created)
(void) unlinkat (AT_FDCWD, rofiles_mnt, AT_REMOVEDIR);
if (created_var_tmp)
(void) unlinkat (rootfs_fd, "var/tmp", AT_REMOVEDIR);
return ret;