diff --git a/src/libpriv/rpmostree-bwrap.c b/src/libpriv/rpmostree-bwrap.c index 13e7c698..02131846 100644 --- a/src/libpriv/rpmostree-bwrap.c +++ b/src/libpriv/rpmostree-bwrap.c @@ -22,6 +22,8 @@ #include "rpmostree-bwrap.h" #include +#include +#include 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 : "); return FALSE; diff --git a/src/libpriv/rpmostree-bwrap.h b/src/libpriv/rpmostree-bwrap.h index fc2ffe5e..a5d6ab75 100644 --- a/src/libpriv/rpmostree-bwrap.h +++ b/src/libpriv/rpmostree-bwrap.h @@ -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); diff --git a/src/libpriv/rpmostree-postprocess.c b/src/libpriv/rpmostree-postprocess.c index e853dd18..16adb5f7 100644 --- a/src/libpriv/rpmostree-postprocess.c +++ b/src/libpriv/rpmostree-postprocess.c @@ -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; } diff --git a/src/libpriv/rpmostree-scripts.c b/src/libpriv/rpmostree-scripts.c index b80219bf..6c033099 100644 --- a/src/libpriv/rpmostree-scripts.c +++ b/src/libpriv/rpmostree-scripts.c @@ -21,7 +21,6 @@ #include "config.h" #include -#include #include "rpmostree-output.h" #include "rpmostree-bwrap.h" #include @@ -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;