-
Ludovico de Nittis authored
The filesystem that is providing /usr might not be mounted in the same place in the current namespace, and on the host system where bwrap will be run. If it isn't, we need to use one path to do file I/O when inspecting it, but pass a different path to bwrap. Signed-off-by:
Ludovico de Nittis <ludovico.denittis@collabora.com>
Ludovico de Nittis authoredThe filesystem that is providing /usr might not be mounted in the same place in the current namespace, and on the host system where bwrap will be run. If it isn't, we need to use one path to do file I/O when inspecting it, but pass a different path to bwrap. Signed-off-by:
Ludovico de Nittis <ludovico.denittis@collabora.com>
bwrap.c 15.00 KiB
/*
* Copyright © 2017-2019 Collabora Ltd.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include <ftw.h>
#include <gio/gio.h>
#include "bwrap.h"
#include "resolve-in-sysroot.h"
/**
* pv_bwrap_run_sync:
* @bwrap: A #FlatpakBwrap on which flatpak_bwrap_finish() has been called
* @exit_status_out: (out) (optional): Used to return the exit status,
* or -1 if it could not be launched or was killed by a signal
* @error: Used to raise an error on failure
*
* Try to run a command. Its standard output and standard error go to
* pressure-vessel's own stdout and stderr.
*
* Returns: %TRUE if the subprocess runs to completion
*/
gboolean
pv_bwrap_run_sync (FlatpakBwrap *bwrap,
int *exit_status_out,
GError **error)
{
gint exit_status;
g_autofree gchar *output = NULL;
g_autofree gchar *errors = NULL;
guint i;
g_autoptr(GString) command = g_string_new ("");
GArray *child_setup_data = NULL;
g_return_val_if_fail (bwrap != NULL, FALSE);
g_return_val_if_fail (bwrap->argv->len >= 2, FALSE);
g_return_val_if_fail (pv_bwrap_was_finished (bwrap), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if (exit_status_out != NULL)
*exit_status_out = -1;
for (i = 0; i < bwrap->argv->len; i++)
{
g_autofree gchar *quoted = NULL;
char *unquoted = g_ptr_array_index (bwrap->argv, i);
if (unquoted == NULL)
break;
quoted = g_shell_quote (unquoted);
g_string_append_printf (command, " %s", quoted);
}
if (bwrap->fds != NULL && bwrap->fds->len > 0)
child_setup_data = bwrap->fds;
g_debug ("run:%s", command->str);
/* We use LEAVE_DESCRIPTORS_OPEN to work around a deadlock in older GLib,
* see flatpak_close_fds_workaround */
if (!g_spawn_sync (NULL, /* cwd */
(char **) bwrap->argv->pdata,
bwrap->envp,
(G_SPAWN_SEARCH_PATH |
G_SPAWN_LEAVE_DESCRIPTORS_OPEN),
flatpak_bwrap_child_setup_cb,
child_setup_data,
&output,
&errors,
&exit_status,
error))
return FALSE;
g_print ("%s", output);
g_printerr ("%s", errors);
if (exit_status_out != NULL)
{
if (WIFEXITED (exit_status))
*exit_status_out = WEXITSTATUS (exit_status);
}
if (!g_spawn_check_exit_status (exit_status, error))
return FALSE;
return TRUE;
}
/**
* pv_bwrap_execve:
* @bwrap: A #FlatpakBwrap on which flatpak_bwrap_finish() has been called
* @original_stdout: If > 0, `dup2()` this file descriptor onto stdout
* @error: Used to raise an error on failure
*
* Attempt to replace the current process with the given bwrap command.
* If unable to do so, raise an error.
*
* Returns: %FALSE
*/
gboolean
pv_bwrap_execve (FlatpakBwrap *bwrap,
int original_stdout,
GError **error)
{
int saved_errno;
g_return_val_if_fail (bwrap != NULL, FALSE);
g_return_val_if_fail (bwrap->argv->len >= 2, FALSE);
g_return_val_if_fail (pv_bwrap_was_finished (bwrap), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
g_debug ("Replacing self with bwrap...");
if (bwrap->fds != NULL && bwrap->fds->len > 0)
flatpak_bwrap_child_setup_cb (bwrap->fds);
fflush (stdout);
fflush (stderr);
if (original_stdout > 0 &&
dup2 (original_stdout, STDOUT_FILENO) != STDOUT_FILENO)
return glnx_throw_errno_prefix (error,
"Unable to make fd %d a copy of fd %d",
STDOUT_FILENO, original_stdout);
execve (bwrap->argv->pdata[0],
(char * const *) bwrap->argv->pdata,
bwrap->envp);
saved_errno = errno;
/* If we are still here then execve failed */
g_set_error (error,
G_IO_ERROR,
g_io_error_from_errno (saved_errno),
"Error replacing self with bwrap: %s",
g_strerror (saved_errno));
return FALSE;
}
/**
* pv_bwrap_bind_usr:
* @bwrap: The #FlatpakBwrap
* @provider_in_host_namespace: The directory we will
* use to provide the container's `/usr`, `/lib*` etc.,
* in the form of an absolute path that can be resolved
* on the host system
* @provider_in_current_namespace: The same directory, this time
* in the form of an absolute path that can be resolved
* in the namespace where pressure-vessel-wrap is running.
* For example, if we run in a Flatpak container and we intend
* to mount the host system's `/usr`, then
* @provider_in_host_namespace would be `/`
* but @provider_in_current_namespace would be `/run/host`.
* @provider_in_container_namespace: Absolute path of the location
* at which we will mount @provider_in_host_namespace in the
* final container
* @error: Used to raise an error on failure
*
* Append arguments to @bwrap that will bind-mount `/usr` and associated
* directories from @host_path into @mount_point.
*
* If @host_path contains a `usr` directory, it is assumed to be a
* system root. Its `usr` directory is mounted on `${mount_point}/usr`
* in the container. Its `lib*`, `bin` and `sbin` directories are
* created as symbolic links in @mount_point, or mounted on subdirectories
* of @mount_point, as appropriate.
*
* If @host_path does not contain a `usr` directory, it is assumed to be
* a merged `/usr`. It is mounted on `${mount_point}/usr`, and `lib*`,
* `bin` and `sbin` symbolic links are created in @mount_point.
*
* In either case, if @host_path` contains `etc/alternatives` and/or
* `etc/ld.so.cache`, they are mounted on corresponding paths under
* @mount_point.
*
* Returns: %TRUE on success
*/
gboolean
pv_bwrap_bind_usr (FlatpakBwrap *bwrap,
const char *provider_in_host_namespace,
const char *provider_in_current_namespace,
const char *provider_in_container_namespace,
GError **error)
{
g_autofree gchar *usr = NULL;
g_autofree gchar *usr_in_current_namespace = NULL;
g_autofree gchar *dest = NULL;
gboolean host_path_is_usr = FALSE;
g_autoptr(GDir) dir = NULL;
const gchar *member = NULL;
static const char * const bind_etc[] =
{
"alternatives",
"ld.so.cache",
"ld.so.conf",
"ld.so.conf.d",
};
gsize i;
g_return_val_if_fail (bwrap != NULL, FALSE);
g_return_val_if_fail (!pv_bwrap_was_finished (bwrap), FALSE);
g_return_val_if_fail (provider_in_host_namespace != NULL, FALSE);
g_return_val_if_fail (provider_in_host_namespace[0] == '/', FALSE);
g_return_val_if_fail (provider_in_current_namespace != NULL, FALSE);
g_return_val_if_fail (provider_in_current_namespace[0] == '/', FALSE);
g_return_val_if_fail (provider_in_container_namespace != NULL, FALSE);
g_return_val_if_fail (provider_in_container_namespace[0] == '/', FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
usr = g_build_filename (provider_in_host_namespace, "usr", NULL);
usr_in_current_namespace = g_build_filename (provider_in_current_namespace,
"usr", NULL);
dest = g_build_filename (provider_in_container_namespace, "usr", NULL);
if (g_file_test (usr_in_current_namespace, G_FILE_TEST_IS_DIR))
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", usr, dest,
NULL);
}
else
{
/* host_path is assumed to be a merged /usr */
host_path_is_usr = TRUE;
flatpak_bwrap_add_args (bwrap,
"--ro-bind", provider_in_host_namespace, dest,
NULL);
}
g_clear_pointer (&dest, g_free);
dir = g_dir_open (provider_in_current_namespace, 0, error);
if (dir == NULL)
return FALSE;
for (member = g_dir_read_name (dir);
member != NULL;
member = g_dir_read_name (dir))
{
if ((g_str_has_prefix (member, "lib")
&& !g_str_equal (member, "libexec"))
|| g_str_equal (member, "bin")
|| g_str_equal (member, "sbin")
|| g_str_equal (member, ".ref"))
{
dest = g_build_filename (provider_in_container_namespace, member, NULL);
if (host_path_is_usr)
{
g_autofree gchar *target = g_build_filename ("usr",
member, NULL);
flatpak_bwrap_add_args (bwrap,
"--symlink", target, dest,
NULL);
}
else
{
g_autofree gchar *path_in_host = g_build_filename (provider_in_host_namespace,
member, NULL);
g_autofree gchar *path_in_current_namespace =
g_build_filename (provider_in_current_namespace, member, NULL);
g_autofree gchar *target = glnx_readlinkat_malloc (-1,
path_in_current_namespace,
NULL, NULL);
if (target != NULL)
{
flatpak_bwrap_add_args (bwrap,
"--symlink", target, dest,
NULL);
}
else
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", path_in_host, dest,
NULL);
}
}
g_clear_pointer (&dest, g_free);
}
}
for (i = 0; i < G_N_ELEMENTS (bind_etc); i++)
{
g_autofree gchar *path = g_build_filename (provider_in_host_namespace, "etc",
bind_etc[i], NULL);
g_autofree gchar *path_in_current_namespace = g_build_filename (provider_in_current_namespace,
"etc", bind_etc[i], NULL);
if (g_file_test (path_in_current_namespace, G_FILE_TEST_EXISTS))
{
dest = g_build_filename (provider_in_container_namespace, "etc", bind_etc[i], NULL);
flatpak_bwrap_add_args (bwrap,
"--ro-bind", path, dest,
NULL);
g_clear_pointer (&dest, g_free);
}
}
return TRUE;
}
/* nftw() doesn't have a user_data argument so we need to use a global
* variable :-( */
static struct
{
FlatpakBwrap *bwrap;
const char *source;
const char *dest;
} nftw_data;
static int
copy_tree_helper (const char *fpath,
const struct stat *sb,
int typeflag,
struct FTW *ftwbuf)
{
const char *path_in_container;
g_autofree gchar *target = NULL;
gsize prefix_len;
int fd = -1;
GError *error = NULL;
g_return_val_if_fail (g_str_has_prefix (fpath, nftw_data.source), 1);
g_return_val_if_fail (g_str_has_suffix (nftw_data.source, nftw_data.dest), 1);
prefix_len = strlen (nftw_data.source) - strlen (nftw_data.dest);
path_in_container = fpath + prefix_len;
switch (typeflag)
{
case FTW_D:
flatpak_bwrap_add_args (nftw_data.bwrap,
"--dir", path_in_container,
NULL);
break;
case FTW_SL:
target = glnx_readlinkat_malloc (-1, fpath, NULL, NULL);
flatpak_bwrap_add_args (nftw_data.bwrap,
"--symlink", target, path_in_container,
NULL);
break;
case FTW_F:
if (!glnx_openat_rdonly (AT_FDCWD, fpath, FALSE, &fd, &error))
{
g_warning ("Unable to copy file into container: %s",
error->message);
g_clear_error (&error);
}
flatpak_bwrap_add_args_data_fd (nftw_data.bwrap,
"--ro-bind-data", glnx_steal_fd (&fd),
path_in_container);
break;
default:
g_warning ("Don't know how to handle ftw type flag %d at %s",
typeflag, fpath);
}
return 0;
}
/**
* pv_bwrap_copy_tree:
* @bwrap: The #FlatpakBwrap
* @source: A copy of the desired @dest in a temporary directory,
* for example `/tmp/tmp12345678/overrides/lib`. The path must end
* with @dest.
* @dest: The destination path in the container, which must be absolute.
*
* For every file, directory or symbolic link in @source, add a
* corresponding read-only file, directory or symbolic link via the bwrap
* command-line, so that the files, directories and symbolic links in the
* container will persist even after @source has been deleted.
*/
void
pv_bwrap_copy_tree (FlatpakBwrap *bwrap,
const char *source,
const char *dest)
{
g_return_if_fail (nftw_data.bwrap == NULL);
g_return_if_fail (dest[0] == '/');
g_return_if_fail (g_str_has_suffix (source, dest));
nftw_data.bwrap = bwrap;
nftw_data.source = source;
nftw_data.dest = dest;
nftw (source, copy_tree_helper, 100, FTW_PHYS);
nftw_data.bwrap = NULL;
nftw_data.source = NULL;
nftw_data.dest = NULL;
}
/**
* pv_bwrap_add_api_filesystems:
* @bwrap: The #FlatpakBwrap
*
* Make basic API filesystems available.
*/
void
pv_bwrap_add_api_filesystems (FlatpakBwrap *bwrap)
{
flatpak_bwrap_add_args (bwrap,
"--dev-bind", "/dev", "/dev",
"--proc", "/proc",
"--ro-bind", "/sys", "/sys",
NULL);
if (g_file_test ("/dev/pts", G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap,
"--dev-bind", "/dev/pts", "/dev/pts",
NULL);
if (g_file_test ("/dev/shm", G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap,
"--dev-bind", "/dev/shm", "/dev/shm",
NULL);
}
FlatpakBwrap *
pv_bwrap_copy (FlatpakBwrap *bwrap)
{
FlatpakBwrap *ret;
g_return_val_if_fail (bwrap != NULL, NULL);
g_return_val_if_fail (!pv_bwrap_was_finished (bwrap), NULL);
/* bwrap can't own any fds, because if it did,
* flatpak_bwrap_append_bwrap() would steal them. */
g_return_val_if_fail (bwrap->fds == NULL || bwrap->fds->len == 0, NULL);
ret = flatpak_bwrap_new (NULL);
flatpak_bwrap_append_bwrap (ret, bwrap);
return ret;
}