-
Simon McVittie authored
A lot of the code added here is #if 0. Normally we don't commit commented-out or #ifdef'd out code, but in this case it helps tools like gvimdiff to have enough context to figure out which parts of the file in Flatpak correspond to which parts of the file in pressure-vessel. Signed-off-by:
Simon McVittie <smcv@collabora.com>
Simon McVittie authoredA lot of the code added here is #if 0. Normally we don't commit commented-out or #ifdef'd out code, but in this case it helps tools like gvimdiff to have enough context to figure out which parts of the file in Flatpak correspond to which parts of the file in pressure-vessel. Signed-off-by:
Simon McVittie <smcv@collabora.com>
flatpak-run.c 43.84 KiB
/*
* Cut-down version of common/flatpak-run.c from Flatpak
* Last updated: Flatpak 1.6.1
*
* Copyright © 2017-2019 Collabora Ltd.
* Copyright © 2014-2019 Red Hat, Inc
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This program 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/>.
*
* Authors:
* Alexander Larsson <alexl@redhat.com>
*/
#include "config.h"
#include "flatpak-run-private.h"
#include <sys/utsname.h>
#include <X11/Xauth.h>
#include <gio/gio.h>
#include "libglnx/libglnx.h"
#include "flatpak-utils-base-private.h"
#include "flatpak-utils-private.h"
/* In Flatpak this is optional, in pressure-vessel not so much */
#define ENABLE_XAUTH
const char * const abs_usrmerged_dirs[] =
{
"/bin",
"/lib",
"/lib32",
"/lib64",
"/sbin",
NULL
};
const char * const *flatpak_abs_usrmerged_dirs = abs_usrmerged_dirs;
static char *
extract_unix_path_from_dbus_address (const char *address)
{
const char *path, *path_end;
if (address == NULL)
return NULL;
if (!g_str_has_prefix (address, "unix:"))
return NULL;
path = strstr (address, "path=");
if (path == NULL)
return NULL;
path += strlen ("path=");
path_end = path;
while (*path_end != 0 && *path_end != ',')
path_end++;
return g_strndup (path, path_end - path);
}
#ifdef ENABLE_XAUTH
static gboolean
auth_streq (char *str,
char *au_str,
int au_len)
{
return au_len == strlen (str) && memcmp (str, au_str, au_len) == 0;
}
static gboolean
xauth_entry_should_propagate (Xauth *xa,
char *hostname,
char *number)
{
/* ensure entry isn't for remote access */
if (xa->family != FamilyLocal && xa->family != FamilyWild)
return FALSE;
/* ensure entry is for this machine */
if (xa->family == FamilyLocal && !auth_streq (hostname, xa->address, xa->address_length))
return FALSE;
/* ensure entry is for this session */
if (xa->number != NULL && !auth_streq (number, xa->number, xa->number_length))
return FALSE;
return TRUE;
}
static void
write_xauth (char *number, FILE *output)
{
Xauth *xa, local_xa;
char *filename;
FILE *f;
struct utsname unames;
if (uname (&unames))
{
g_warning ("uname failed");
return;
}
filename = XauFileName ();
f = fopen (filename, "rb");
if (f == NULL)
return;
while (TRUE)
{
xa = XauReadAuth (f);
if (xa == NULL)
break;
if (xauth_entry_should_propagate (xa, unames.nodename, number))
{
local_xa = *xa;
if (local_xa.number)
{
local_xa.number = (char *) "99";
local_xa.number_length = 2;
}
if (!XauWriteAuth (output, &local_xa))
g_warning ("xauth write error");
}
XauDisposeAuth (xa);
}
fclose (f);
}
#endif /* ENABLE_XAUTH */
void
flatpak_run_add_x11_args (FlatpakBwrap *bwrap,
gboolean allowed)
{
g_autofree char *x11_socket = NULL;
const char *display;
/* Always cover /tmp/.X11-unix, that way we never see the host one in case
* we have access to the host /tmp. If you request X access we'll put the right
* thing in this anyway.
*/
flatpak_bwrap_add_args (bwrap,
"--tmpfs", "/tmp/.X11-unix",
NULL);
if (!allowed)
{
flatpak_bwrap_unset_env (bwrap, "DISPLAY");
return;
}
g_debug ("Allowing x11 access");
display = g_getenv ("DISPLAY");
if (display && display[0] == ':' && g_ascii_isdigit (display[1]))
{
const char *display_nr = &display[1];
const char *display_nr_end = display_nr;
g_autofree char *d = NULL;
while (g_ascii_isdigit (*display_nr_end))
display_nr_end++;
d = g_strndup (display_nr, display_nr_end - display_nr);
x11_socket = g_strdup_printf ("/tmp/.X11-unix/X%s", d);
flatpak_bwrap_add_args (bwrap,
"--ro-bind", x11_socket, "/tmp/.X11-unix/X99",
NULL);
flatpak_bwrap_set_env (bwrap, "DISPLAY", ":99.0", TRUE);
#ifdef ENABLE_XAUTH
g_auto(GLnxTmpfile) xauth_tmpf = { 0, };
if (glnx_open_anonymous_tmpfile_full (O_RDWR | O_CLOEXEC, "/tmp", &xauth_tmpf, NULL))
{
FILE *output = fdopen (xauth_tmpf.fd, "wb");
if (output != NULL)
{
/* fd is now owned by output, steal it from the tmpfile */
int tmp_fd = dup (glnx_steal_fd (&xauth_tmpf.fd));
if (tmp_fd != -1)
{
g_autofree char *dest = g_strdup_printf ("/run/user/%d/Xauthority", getuid ());
write_xauth (d, output);
flatpak_bwrap_add_args_data_fd (bwrap, "--ro-bind-data", tmp_fd, dest);
flatpak_bwrap_set_env (bwrap, "XAUTHORITY", dest, TRUE);
}
fclose (output);
if (tmp_fd != -1)
lseek (tmp_fd, 0, SEEK_SET);
}
}
#endif
}
else
{
flatpak_bwrap_unset_env (bwrap, "DISPLAY");
}
}
gboolean
flatpak_run_add_wayland_args (FlatpakBwrap *bwrap)
{
const char *wayland_display;
g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
g_autofree char *wayland_socket = NULL;
g_autofree char *sandbox_wayland_socket = NULL;
gboolean res = FALSE;
struct stat statbuf;
wayland_display = g_getenv ("WAYLAND_DISPLAY");
if (!wayland_display)
wayland_display = "wayland-0";
wayland_socket = g_build_filename (user_runtime_dir, wayland_display, NULL);
sandbox_wayland_socket = g_strdup_printf ("/run/user/%d/%s", getuid (), wayland_display);
if (stat (wayland_socket, &statbuf) == 0 &&
(statbuf.st_mode & S_IFMT) == S_IFSOCK)
{
res = TRUE;
flatpak_bwrap_add_args (bwrap,
"--ro-bind", wayland_socket, sandbox_wayland_socket,
NULL);
}
return res;
}
/* Try to find a default server from a pulseaudio confguration file */
static char *
flatpak_run_get_pulseaudio_server_user_config (const char *path)
{
g_autoptr(GFile) file = g_file_new_for_path (path);
g_autoptr(GError) my_error = NULL;
g_autoptr(GFileInputStream) input_stream = NULL;
g_autoptr(GDataInputStream) data_stream = NULL;
size_t len;
input_stream = g_file_read (file, NULL, &my_error);
if (my_error)
{
g_debug ("Pulseaudio user configuration file '%s': %s", path, my_error->message);
return NULL;
}
data_stream = g_data_input_stream_new (G_INPUT_STREAM (input_stream));
while (TRUE)
{
g_autofree char *line = g_data_input_stream_read_line (data_stream, &len, NULL, NULL);
if (line == NULL)
break;
g_strchug (line);
if ((*line == '\0') || (*line == ';') || (*line == '#'))
continue;
if (g_str_has_prefix (line, ".include "))
{
g_autofree char *rec_path = g_strdup (line + 9);
g_strstrip (rec_path);
char *found = flatpak_run_get_pulseaudio_server_user_config (rec_path);
if (found)
return found;
}
else if (g_str_has_prefix (line, "["))
{
return NULL;
}
else
{
g_auto(GStrv) tokens = g_strsplit (line, "=", 2);
if ((tokens[0] != NULL) && (tokens[1] != NULL))
{
g_strchomp (tokens[0]);
if (strcmp ("default-server", tokens[0]) == 0)
{
g_strstrip (tokens[1]);
g_debug ("Found pulseaudio socket from configuration file '%s': %s", path, tokens[1]);
return g_strdup (tokens[1]);
}
}
}
}
return NULL;
}
static char *
flatpak_run_get_pulseaudio_server (void)
{
const char * pulse_clientconfig;
char *pulse_server;
g_autofree char *pulse_user_config = NULL;
pulse_server = g_strdup (g_getenv ("PULSE_SERVER"));
if (pulse_server)
return pulse_server;
pulse_clientconfig = g_getenv ("PULSE_CLIENTCONFIG");
if (pulse_clientconfig)
return flatpak_run_get_pulseaudio_server_user_config (pulse_clientconfig);
pulse_user_config = g_build_filename (g_get_user_config_dir (), "pulse/client.conf", NULL);
pulse_server = flatpak_run_get_pulseaudio_server_user_config (pulse_user_config);
if (pulse_server)
return pulse_server;
pulse_server = flatpak_run_get_pulseaudio_server_user_config ("/etc/pulse/client.conf");
if (pulse_server)
return pulse_server;
return NULL;
}
static char *
flatpak_run_parse_pulse_server (const char *value)
{
g_auto(GStrv) servers = g_strsplit (value, " ", 0);
gsize i;
for (i = 0; servers[i] != NULL; i++)
{
const char *server = servers[i];
if (g_str_has_prefix (server, "{"))
{
const char * closing = strstr (server, "}");
if (closing == NULL)
continue;
server = closing + 1;
}
if (g_str_has_prefix (server, "unix:"))
return g_strdup (server + 5);
}
return NULL;
}
void
flatpak_run_add_pulseaudio_args (FlatpakBwrap *bwrap)
{
g_autofree char *pulseaudio_server = flatpak_run_get_pulseaudio_server ();
g_autofree char *pulseaudio_socket = NULL;
g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
if (pulseaudio_server)
pulseaudio_socket = flatpak_run_parse_pulse_server (pulseaudio_server);
if (!pulseaudio_socket)
pulseaudio_socket = g_build_filename (user_runtime_dir, "pulse/native", NULL);
flatpak_bwrap_unset_env (bwrap, "PULSE_SERVER");
/* SteamOS system-wide PulseAudio instance */
if (!g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS))
{
g_clear_pointer (&pulseaudio_socket, g_free);
pulseaudio_socket = g_strdup ("/var/run/pulse/native");
}
if (g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS))
{
gboolean share_shm = FALSE; /* TODO: When do we add this? */
g_autofree char *client_config = g_strdup_printf ("enable-shm=%s\n", share_shm ? "yes" : "no");
g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/pulse/native", getuid ());
g_autofree char *pulse_server = g_strdup_printf ("unix:/run/user/%d/pulse/native", getuid ());
g_autofree char *config_path = g_strdup_printf ("/run/user/%d/pulse/config", getuid ());
/* FIXME - error handling */
if (!flatpak_bwrap_add_args_data (bwrap, "pulseaudio", client_config, -1, config_path, NULL))
return;
flatpak_bwrap_add_args (bwrap,
"--ro-bind", pulseaudio_socket, sandbox_socket_path,
NULL);
flatpak_bwrap_set_env (bwrap, "PULSE_SERVER", pulse_server, TRUE);
flatpak_bwrap_set_env (bwrap, "PULSE_CLIENTCONFIG", config_path, TRUE);
}
else
g_debug ("Could not find pulseaudio socket");
/* Also allow ALSA access. This was added in 1.8, and is not ideally named. However,
* since the practical permission of ALSA and PulseAudio are essentially the same, and
* since we don't want to add more permissions for something we plan to replace with
* portals/pipewire going forward we reinterpret pulseaudio to also mean ALSA.
*/
if (g_file_test ("/dev/snd", G_FILE_TEST_IS_DIR))
flatpak_bwrap_add_args (bwrap, "--dev-bind", "/dev/snd", "/dev/snd", NULL);
}
/* Simplified from Flatpak: we never restrict access to the D-Bus system bus */
gboolean
flatpak_run_add_system_dbus_args (FlatpakBwrap *app_bwrap)
{
const char *dbus_address = g_getenv ("DBUS_SYSTEM_BUS_ADDRESS");
g_autofree char *dbus_system_socket = NULL;
if (dbus_address != NULL)
dbus_system_socket = extract_unix_path_from_dbus_address (dbus_address);
else if (g_file_test ("/var/run/dbus/system_bus_socket", G_FILE_TEST_EXISTS))
dbus_system_socket = g_strdup ("/var/run/dbus/system_bus_socket");
if (dbus_system_socket != NULL)
{
flatpak_bwrap_add_args (app_bwrap,
"--ro-bind", dbus_system_socket, "/run/dbus/system_bus_socket",
NULL);
flatpak_bwrap_set_env (app_bwrap, "DBUS_SYSTEM_BUS_ADDRESS", "unix:path=/run/dbus/system_bus_socket", TRUE);
return TRUE;
}
return FALSE;
}
/* Simplified from Flatpak: we never restrict access to the D-Bus session bus */
gboolean
flatpak_run_add_session_dbus_args (FlatpakBwrap *app_bwrap)
{
const char *dbus_address = g_getenv ("DBUS_SESSION_BUS_ADDRESS");
g_autofree char *dbus_session_socket = NULL;
g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/bus", getuid ());
g_autofree char *sandbox_dbus_address = g_strdup_printf ("unix:path=/run/user/%d/bus", getuid ());
if (dbus_address != NULL)
{
dbus_session_socket = extract_unix_path_from_dbus_address (dbus_address);
}
else
{
g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
struct stat statbuf;
dbus_session_socket = g_build_filename (user_runtime_dir, "bus", NULL);
if (stat (dbus_session_socket, &statbuf) < 0
|| (statbuf.st_mode & S_IFMT) != S_IFSOCK
|| statbuf.st_uid != getuid ())
return FALSE;
}
if (dbus_session_socket != NULL)
{
flatpak_bwrap_add_args (app_bwrap,
"--ro-bind", dbus_session_socket, sandbox_socket_path,
NULL);
flatpak_bwrap_set_env (app_bwrap, "DBUS_SESSION_BUS_ADDRESS", sandbox_dbus_address, TRUE);
return TRUE;
}
return FALSE;
}
#if 0
static gboolean
flatpak_run_add_a11y_dbus_args (FlatpakBwrap *app_bwrap,
FlatpakBwrap *proxy_arg_bwrap,
FlatpakContext *context,
FlatpakRunFlags flags)
{
g_autoptr(GDBusConnection) session_bus = NULL;
g_autofree char *a11y_address = NULL;
g_autoptr(GError) local_error = NULL;
g_autoptr(GDBusMessage) reply = NULL;
g_autoptr(GDBusMessage) msg = NULL;
g_autofree char *proxy_socket = NULL;
if ((flags & FLATPAK_RUN_FLAG_NO_A11Y_BUS_PROXY) != 0)
return FALSE;
session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
if (session_bus == NULL)
return FALSE;
msg = g_dbus_message_new_method_call ("org.a11y.Bus", "/org/a11y/bus", "org.a11y.Bus", "GetAddress");
g_dbus_message_set_body (msg, g_variant_new ("()"));
reply =
g_dbus_connection_send_message_with_reply_sync (session_bus, msg,
G_DBUS_SEND_MESSAGE_FLAGS_NONE,
30000,
NULL,
NULL,
NULL);
if (reply)
{
if (g_dbus_message_to_gerror (reply, &local_error))
{
if (!g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))
g_message ("Can't find a11y bus: %s", local_error->message);
}
else
{
g_variant_get (g_dbus_message_get_body (reply),
"(s)", &a11y_address);
}
}
if (!a11y_address)
return FALSE;
proxy_socket = create_proxy_socket ("a11y-bus-proxy-XXXXXX");
if (proxy_socket == NULL)
return FALSE;
g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/at-spi-bus", getuid ());
g_autofree char *sandbox_dbus_address = g_strdup_printf ("unix:path=/run/user/%d/at-spi-bus", getuid ());
flatpak_bwrap_add_args (proxy_arg_bwrap,
a11y_address,
proxy_socket, "--filter", "--sloppy-names",
"--call=org.a11y.atspi.Registry=org.a11y.atspi.Socket.Embed@/org/a11y/atspi/accessible/root",
"--call=org.a11y.atspi.Registry=org.a11y.atspi.Socket.Unembed@/org/a11y/atspi/accessible/root",
"--call=org.a11y.atspi.Registry=org.a11y.atspi.Registry.GetRegisteredEvents@/org/a11y/atspi/registry",
"--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.GetKeystrokeListeners@/org/a11y/atspi/registry/deviceeventcontroller",
"--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.GetDeviceEventListeners@/org/a11y/atspi/registry/deviceeventcontroller",
"--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.NotifyListenersSync@/org/a11y/atspi/registry/deviceeventcontroller",
"--call=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.NotifyListenersAsync@/org/a11y/atspi/registry/deviceeventcontroller",
NULL);
if ((flags & FLATPAK_RUN_FLAG_LOG_A11Y_BUS) != 0)
flatpak_bwrap_add_args (proxy_arg_bwrap, "--log", NULL);
flatpak_bwrap_add_args (app_bwrap,
"--ro-bind", proxy_socket, sandbox_socket_path,
NULL);
flatpak_bwrap_set_env (app_bwrap, "AT_SPI_BUS_ADDRESS", sandbox_dbus_address, TRUE);
return TRUE;
}
/* This wraps the argv in a bwrap call, primary to allow the
command to be run with a proper /.flatpak-info with data
taken from app_info_path */
static gboolean
add_bwrap_wrapper (FlatpakBwrap *bwrap,
const char *app_info_path,
GError **error)
{
glnx_autofd int app_info_fd = -1;
g_auto(GLnxDirFdIterator) dir_iter = { 0 };
struct dirent *dent;
g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
g_autofree char *proxy_socket_dir = g_build_filename (user_runtime_dir, ".dbus-proxy/", NULL);
app_info_fd = open (app_info_path, O_RDONLY | O_CLOEXEC);
if (app_info_fd == -1)
return glnx_throw_errno_prefix (error, _("Failed to open app info file"));
if (!glnx_dirfd_iterator_init_at (AT_FDCWD, "/", FALSE, &dir_iter, error))
return FALSE;
flatpak_bwrap_add_arg (bwrap, flatpak_get_bwrap ());
while (TRUE)
{
glnx_autofd int o_path_fd = -1;
struct statfs stfs;
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dir_iter, &dent, NULL, error))
return FALSE;
if (dent == NULL)
break;
if (strcmp (dent->d_name, ".flatpak-info") == 0)
continue;
/* O_PATH + fstatfs is the magic that we need to statfs without automounting the target */
o_path_fd = openat (dir_iter.fd, dent->d_name, O_PATH | O_NOFOLLOW | O_CLOEXEC);
if (o_path_fd == -1 || fstatfs (o_path_fd, &stfs) != 0 || stfs.f_type == AUTOFS_SUPER_MAGIC)
continue; /* AUTOFS mounts are risky and can cause us to block (see issue #1633), so ignore it. Its unlikely the proxy needs such a directory. */
if (dent->d_type == DT_DIR)
{
if (strcmp (dent->d_name, "tmp") == 0 ||
strcmp (dent->d_name, "var") == 0 ||
strcmp (dent->d_name, "run") == 0)
flatpak_bwrap_add_arg (bwrap, "--bind");
else
flatpak_bwrap_add_arg (bwrap, "--ro-bind");
flatpak_bwrap_add_arg_printf (bwrap, "/%s", dent->d_name);
flatpak_bwrap_add_arg_printf (bwrap, "/%s", dent->d_name);
}
else if (dent->d_type == DT_LNK)
{
g_autofree gchar *target = NULL;
target = glnx_readlinkat_malloc (dir_iter.fd, dent->d_name,
NULL, error);
if (target == NULL)
return FALSE;
flatpak_bwrap_add_args (bwrap, "--symlink", target, NULL);
flatpak_bwrap_add_arg_printf (bwrap, "/%s", dent->d_name);
}
}
flatpak_bwrap_add_args (bwrap, "--bind", proxy_socket_dir, proxy_socket_dir, NULL);
/* This is a file rather than a bind mount, because it will then
not be unmounted from the namespace when the namespace dies. */
flatpak_bwrap_add_args_data_fd (bwrap, "--file", glnx_steal_fd (&app_info_fd), "/.flatpak-info");
if (!flatpak_bwrap_bundle_args (bwrap, 1, -1, FALSE, error))
return FALSE;
return TRUE;
}
static gboolean
start_dbus_proxy (FlatpakBwrap *app_bwrap,
FlatpakBwrap *proxy_arg_bwrap,
const char *app_info_path,
GError **error)
{
char x = 'x';
const char *proxy;
g_autofree char *commandline = NULL;
g_autoptr(FlatpakBwrap) proxy_bwrap = NULL;
int sync_fds[2] = {-1, -1};
int proxy_start_index;
g_auto(GStrv) minimal_envp = NULL;
minimal_envp = flatpak_run_get_minimal_env (FALSE, FALSE);
proxy_bwrap = flatpak_bwrap_new (NULL);
if (!add_bwrap_wrapper (proxy_bwrap, app_info_path, error))
return FALSE;
proxy = g_getenv ("FLATPAK_DBUSPROXY");
if (proxy == NULL)
proxy = DBUSPROXY;
flatpak_bwrap_add_arg (proxy_bwrap, proxy);
proxy_start_index = proxy_bwrap->argv->len;
if (pipe2 (sync_fds, O_CLOEXEC) < 0)
{
g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno),
_("Unable to create sync pipe"));
return FALSE;
}
/* read end goes to app */
flatpak_bwrap_add_args_data_fd (app_bwrap, "--sync-fd", sync_fds[0], NULL);
/* write end goes to proxy */
flatpak_bwrap_add_fd (proxy_bwrap, sync_fds[1]);
flatpak_bwrap_add_arg_printf (proxy_bwrap, "--fd=%d", sync_fds[1]);
/* Note: This steals the fds from proxy_arg_bwrap */
flatpak_bwrap_append_bwrap (proxy_bwrap, proxy_arg_bwrap);
if (!flatpak_bwrap_bundle_args (proxy_bwrap, proxy_start_index, -1, TRUE, error))
return FALSE;
flatpak_bwrap_finish (proxy_bwrap);
commandline = flatpak_quote_argv ((const char **) proxy_bwrap->argv->pdata, -1);
g_debug ("Running '%s'", commandline);
/* We use LEAVE_DESCRIPTORS_OPEN to work around dead-lock, see flatpak_close_fds_workaround */
if (!g_spawn_async (NULL,
(char **) proxy_bwrap->argv->pdata,
NULL,
G_SPAWN_SEARCH_PATH | G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
flatpak_bwrap_child_setup_cb, proxy_bwrap->fds,
NULL, error))
return FALSE;
/* The write end can be closed now, otherwise the read below will hang of xdg-dbus-proxy
fails to start. */
g_clear_pointer (&proxy_bwrap, flatpak_bwrap_free);
/* Sync with proxy, i.e. wait until its listening on the sockets */
if (read (sync_fds[0], &x, 1) != 1)
{
g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno),
_("Failed to sync with dbus proxy"));
return FALSE;
}
return TRUE;
}
static int
flatpak_extension_compare_by_path (gconstpointer _a,
gconstpointer _b)
{
const FlatpakExtension *a = _a;
const FlatpakExtension *b = _b;
return g_strcmp0 (a->directory, b->directory);
}
gboolean
flatpak_run_add_extension_args (FlatpakBwrap *bwrap,
GKeyFile *metakey,
const char *full_ref,
gboolean use_ld_so_cache,
char **extensions_out,
GCancellable *cancellable,
GError **error)
{
g_auto(GStrv) parts = NULL;
g_autoptr(GString) used_extensions = g_string_new ("");
gboolean is_app;
GList *extensions, *path_sorted_extensions, *l;
g_autoptr(GString) ld_library_path = g_string_new ("");
int count = 0;
g_autoptr(GHashTable) mounted_tmpfs =
g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
g_autoptr(GHashTable) created_symlink =
g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
parts = g_strsplit (full_ref, "/", 0);
if (g_strv_length (parts) != 4)
return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Failed to determine parts from ref: %s"), full_ref);
is_app = strcmp (parts[0], "app") == 0;
extensions = flatpak_list_extensions (metakey,
parts[2], parts[3]);
/* First we apply all the bindings, they are sorted alphabetically in order for parent directory
to be mounted before child directories */
path_sorted_extensions = g_list_copy (extensions);
path_sorted_extensions = g_list_sort (path_sorted_extensions, flatpak_extension_compare_by_path);
for (l = path_sorted_extensions; l != NULL; l = l->next)
{
FlatpakExtension *ext = l->data;
g_autofree char *directory = g_build_filename (is_app ? "/app" : "/usr", ext->directory, NULL);
g_autofree char *full_directory = g_build_filename (directory, ext->subdir_suffix, NULL);
g_autofree char *ref = g_build_filename (full_directory, ".ref", NULL);
g_autofree char *real_ref = g_build_filename (ext->files_path, ext->directory, ".ref", NULL);
if (ext->needs_tmpfs)
{
g_autofree char *parent = g_path_get_dirname (directory);
if (g_hash_table_lookup (mounted_tmpfs, parent) == NULL)
{
flatpak_bwrap_add_args (bwrap,
"--tmpfs", parent,
NULL);
g_hash_table_insert (mounted_tmpfs, g_steal_pointer (&parent), "mounted");
}
}
flatpak_bwrap_add_args (bwrap,
"--ro-bind", ext->files_path, full_directory,
NULL);
if (g_file_test (real_ref, G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap,
"--lock-file", ref,
NULL);
}
g_list_free (path_sorted_extensions);
/* Then apply library directories and file merging, in extension prio order */
for (l = extensions; l != NULL; l = l->next)
{
FlatpakExtension *ext = l->data;
g_autofree char *directory = g_build_filename (is_app ? "/app" : "/usr", ext->directory, NULL);
g_autofree char *full_directory = g_build_filename (directory, ext->subdir_suffix, NULL);
int i;
if (used_extensions->len > 0)
g_string_append (used_extensions, ";");
g_string_append (used_extensions, ext->installed_id);
g_string_append (used_extensions, "=");
if (ext->commit != NULL)
g_string_append (used_extensions, ext->commit);
else
g_string_append (used_extensions, "local");
if (ext->add_ld_path)
{
g_autofree char *ld_path = g_build_filename (full_directory, ext->add_ld_path, NULL);
if (use_ld_so_cache)
{
g_autofree char *contents = g_strconcat (ld_path, "\n", NULL);
/* We prepend app or runtime and a counter in order to get the include order correct for the conf files */
g_autofree char *ld_so_conf_file = g_strdup_printf ("%s-%03d-%s.conf", parts[0], ++count, ext->installed_id);
g_autofree char *ld_so_conf_file_path = g_build_filename ("/run/flatpak/ld.so.conf.d", ld_so_conf_file, NULL);
if (!flatpak_bwrap_add_args_data (bwrap, "ld-so-conf",
contents, -1, ld_so_conf_file_path, error))
return FALSE;
}
else
{
if (ld_library_path->len != 0)
g_string_append (ld_library_path, ":");
g_string_append (ld_library_path, ld_path);
}
}
for (i = 0; ext->merge_dirs != NULL && ext->merge_dirs[i] != NULL; i++)
{
g_autofree char *parent = g_path_get_dirname (directory);
g_autofree char *merge_dir = g_build_filename (parent, ext->merge_dirs[i], NULL);
g_autofree char *source_dir = g_build_filename (ext->files_path, ext->merge_dirs[i], NULL);
g_auto(GLnxDirFdIterator) source_iter = { 0 };
struct dirent *dent;
if (glnx_dirfd_iterator_init_at (AT_FDCWD, source_dir, TRUE, &source_iter, NULL))
{
while (glnx_dirfd_iterator_next_dent (&source_iter, &dent, NULL, NULL) && dent != NULL)
{
g_autofree char *symlink_path = g_build_filename (merge_dir, dent->d_name, NULL);
/* Only create the first, because extensions are listed in prio order */
if (g_hash_table_lookup (created_symlink, symlink_path) == NULL)
{
g_autofree char *symlink = g_build_filename (directory, ext->merge_dirs[i], dent->d_name, NULL);
flatpak_bwrap_add_args (bwrap,
"--symlink", symlink, symlink_path,
NULL);
g_hash_table_insert (created_symlink, g_steal_pointer (&symlink_path), "created");
}
}
}
}
}
g_list_free_full (extensions, (GDestroyNotify) flatpak_extension_free);
if (ld_library_path->len != 0)
{
const gchar *old_ld_path = g_environ_getenv (bwrap->envp, "LD_LIBRARY_PATH");
if (old_ld_path != NULL && *old_ld_path != 0)
{
if (is_app)
{
g_string_append (ld_library_path, ":");
g_string_append (ld_library_path, old_ld_path);
}
else
{
g_string_prepend (ld_library_path, ":");
g_string_prepend (ld_library_path, old_ld_path);
}
}
flatpak_bwrap_set_env (bwrap, "LD_LIBRARY_PATH", ld_library_path->str, TRUE);
}
if (extensions_out)
*extensions_out = g_string_free (g_steal_pointer (&used_extensions), FALSE);
return TRUE;
}
gboolean
flatpak_run_add_environment_args (FlatpakBwrap *bwrap,
const char *app_info_path,
FlatpakRunFlags flags,
const char *app_id,
FlatpakContext *context,
GFile *app_id_dir,
GPtrArray *previous_app_id_dirs,
FlatpakExports **exports_out,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GError) my_error = NULL;
g_autoptr(FlatpakExports) exports = NULL;
g_autoptr(FlatpakBwrap) proxy_arg_bwrap = flatpak_bwrap_new (flatpak_bwrap_empty_env);
gboolean has_wayland = FALSE;
gboolean allow_x11 = FALSE;
if ((context->shares & FLATPAK_CONTEXT_SHARED_IPC) == 0)
{
g_debug ("Disallowing ipc access");
flatpak_bwrap_add_args (bwrap, "--unshare-ipc", NULL);
}
if ((context->shares & FLATPAK_CONTEXT_SHARED_NETWORK) == 0)
{
g_debug ("Disallowing network access");
flatpak_bwrap_add_args (bwrap, "--unshare-net", NULL);
}
if (context->devices & FLATPAK_CONTEXT_DEVICE_ALL)
{
flatpak_bwrap_add_args (bwrap,
"--dev-bind", "/dev", "/dev",
NULL);
/* Don't expose the host /dev/shm, just the device nodes, unless explicitly allowed */
if (g_file_test ("/dev/shm", G_FILE_TEST_IS_DIR))
{
if ((context->devices & FLATPAK_CONTEXT_DEVICE_SHM) == 0)
flatpak_bwrap_add_args (bwrap,
"--tmpfs", "/dev/shm",
NULL);
}
else if (g_file_test ("/dev/shm", G_FILE_TEST_IS_SYMLINK))
{
g_autofree char *link = flatpak_readlink ("/dev/shm", NULL);
/* On debian (with sysv init) the host /dev/shm is a symlink to /run/shm, so we can't
mount on top of it. */
if (g_strcmp0 (link, "/run/shm") == 0)
{
if (context->devices & FLATPAK_CONTEXT_DEVICE_SHM &&
g_file_test ("/run/shm", G_FILE_TEST_IS_DIR))
flatpak_bwrap_add_args (bwrap,
"--bind", "/run/shm", "/run/shm",
NULL);
else
flatpak_bwrap_add_args (bwrap,
"--dir", "/run/shm",
NULL);
}
else
g_warning ("Unexpected /dev/shm symlink %s", link);
}
}
else
{
flatpak_bwrap_add_args (bwrap,
"--dev", "/dev",
NULL);
if (context->devices & FLATPAK_CONTEXT_DEVICE_DRI)
{
g_debug ("Allowing dri access");
int i;
char *dri_devices[] = {
"/dev/dri",
/* mali */
"/dev/mali",
"/dev/mali0",
"/dev/umplock",
/* nvidia */
"/dev/nvidiactl",
"/dev/nvidia-modeset",
/* nvidia OpenCL/CUDA */
"/dev/nvidia-uvm",
"/dev/nvidia-uvm-tools",
};
for (i = 0; i < G_N_ELEMENTS (dri_devices); i++)
{
if (g_file_test (dri_devices[i], G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap, "--dev-bind", dri_devices[i], dri_devices[i], NULL);
}
/* Each Nvidia card gets its own device.
This is a fairly arbitrary limit but ASUS sells mining boards supporting 20 in theory. */
char nvidia_dev[14]; /* /dev/nvidia plus up to 2 digits */
for (i = 0; i < 20; i++)
{
g_snprintf (nvidia_dev, sizeof (nvidia_dev), "/dev/nvidia%d", i);
if (g_file_test (nvidia_dev, G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap, "--dev-bind", nvidia_dev, nvidia_dev, NULL);
}
}
if (context->devices & FLATPAK_CONTEXT_DEVICE_KVM)
{
g_debug ("Allowing kvm access");
if (g_file_test ("/dev/kvm", G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap, "--dev-bind", "/dev/kvm", "/dev/kvm", NULL);
}
if (context->devices & FLATPAK_CONTEXT_DEVICE_SHM)
{
/* This is a symlink to /run/shm on debian, so bind to real target */
g_autofree char *real_dev_shm = realpath ("/dev/shm", NULL);
g_debug ("Allowing /dev/shm access (as %s)", real_dev_shm);
if (real_dev_shm != NULL)
flatpak_bwrap_add_args (bwrap, "--bind", real_dev_shm, "/dev/shm", NULL);
}
}
flatpak_context_append_bwrap_filesystem (context, bwrap, app_id, app_id_dir, previous_app_id_dirs, &exports);
if (context->sockets & FLATPAK_CONTEXT_SOCKET_WAYLAND)
{
g_debug ("Allowing wayland access");
has_wayland = flatpak_run_add_wayland_args (bwrap);
}
if ((context->sockets & FLATPAK_CONTEXT_SOCKET_FALLBACK_X11) != 0)
allow_x11 = !has_wayland;
else
allow_x11 = (context->sockets & FLATPAK_CONTEXT_SOCKET_X11) != 0;
flatpak_run_add_x11_args (bwrap, allow_x11);
if (context->sockets & FLATPAK_CONTEXT_SOCKET_SSH_AUTH)
{
flatpak_run_add_ssh_args (bwrap);
}
if (context->sockets & FLATPAK_CONTEXT_SOCKET_PULSEAUDIO)
{
g_debug ("Allowing pulseaudio access");
flatpak_run_add_pulseaudio_args (bwrap);
}
if (context->sockets & FLATPAK_CONTEXT_SOCKET_PCSC)
{
flatpak_run_add_pcsc_args (bwrap);
}
if (context->sockets & FLATPAK_CONTEXT_SOCKET_CUPS)
{
flatpak_run_add_cups_args (bwrap);
}
flatpak_run_add_session_dbus_args (bwrap, proxy_arg_bwrap, context, flags, app_id);
flatpak_run_add_system_dbus_args (bwrap, proxy_arg_bwrap, context, flags);
flatpak_run_add_a11y_dbus_args (bwrap, proxy_arg_bwrap, context, flags);
if (g_environ_getenv (bwrap->envp, "LD_LIBRARY_PATH") != NULL)
{
/* LD_LIBRARY_PATH is overridden for setuid helper, so pass it as cmdline arg */
flatpak_bwrap_add_args (bwrap,
"--setenv", "LD_LIBRARY_PATH", g_environ_getenv (bwrap->envp, "LD_LIBRARY_PATH"),
NULL);
flatpak_bwrap_unset_env (bwrap, "LD_LIBRARY_PATH");
}
if (g_environ_getenv (bwrap->envp, "TMPDIR") != NULL)
{
/* TMPDIR is overridden for setuid helper, so pass it as cmdline arg */
flatpak_bwrap_add_args (bwrap,
"--setenv", "TMPDIR", g_environ_getenv (bwrap->envp, "TMPDIR"),
NULL);
flatpak_bwrap_unset_env (bwrap, "TMPDIR");
}
/* Must run this before spawning the dbus proxy, to ensure it
ends up in the app cgroup */
if (!flatpak_run_in_transient_unit (app_id, &my_error))
{
/* We still run along even if we don't get a cgroup, as nothing
really depends on it. Its just nice to have */
g_debug ("Failed to run in transient scope: %s", my_error->message);
g_clear_error (&my_error);
}
if (!flatpak_bwrap_is_empty (proxy_arg_bwrap) &&
!start_dbus_proxy (bwrap, proxy_arg_bwrap, app_info_path, error))
return FALSE;
if (exports_out)
*exports_out = g_steal_pointer (&exports);
return TRUE;
}
typedef struct
{
const char *env;
const char *val;
} ExportData;
static const ExportData default_exports[] = {
{"PATH", "/app/bin:/usr/bin"},
/* We always want to unset LD_LIBRARY_PATH to avoid inheriting weird
* dependencies from the host. But if not using ld.so.cache this is
* later set. */
{"LD_LIBRARY_PATH", NULL},
{"XDG_CONFIG_DIRS", "/app/etc/xdg:/etc/xdg"},
{"XDG_DATA_DIRS", "/app/share:/usr/share"},
{"SHELL", "/bin/sh"},
{"TMPDIR", NULL}, /* Unset TMPDIR as it may not exist in the sandbox */
/* Some env vars are common enough and will affect the sandbox badly
if set on the host. We clear these always. */
{"PYTHONPATH", NULL},
{"PERLLIB", NULL},
{"PERL5LIB", NULL},
{"XCURSOR_PATH", NULL},
};
static const ExportData no_ld_so_cache_exports[] = {
{"LD_LIBRARY_PATH", "/app/lib"},
};
static const ExportData devel_exports[] = {
{"ACLOCAL_PATH", "/app/share/aclocal"},
{"C_INCLUDE_PATH", "/app/include"},
{"CPLUS_INCLUDE_PATH", "/app/include"},
{"LDFLAGS", "-L/app/lib "},
{"PKG_CONFIG_PATH", "/app/lib/pkgconfig:/app/share/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig"},
{"LC_ALL", "en_US.utf8"},
};
static void
add_exports (GPtrArray *env_array,
const ExportData *exports,
gsize n_exports)
{
int i;
for (i = 0; i < n_exports; i++)
{
if (exports[i].val)
g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", exports[i].env, exports[i].val));
}
}
char **
flatpak_run_get_minimal_env (gboolean devel, gboolean use_ld_so_cache)
{
GPtrArray *env_array;
static const char * const copy[] = {
"PWD",
"GDMSESSION",
"XDG_CURRENT_DESKTOP",
"XDG_SESSION_DESKTOP",
"DESKTOP_SESSION",
"EMAIL_ADDRESS",
"HOME",
"HOSTNAME",
"LOGNAME",
"REAL_NAME",
"TERM",
"USER",
"USERNAME",
};
static const char * const copy_nodevel[] = {
"LANG",
"LANGUAGE",
"LC_ALL",
"LC_ADDRESS",
"LC_COLLATE",
"LC_CTYPE",
"LC_IDENTIFICATION",
"LC_MEASUREMENT",
"LC_MESSAGES",
"LC_MONETARY",
"LC_NAME",
"LC_NUMERIC",
"LC_PAPER",
"LC_TELEPHONE",
"LC_TIME",
};
int i;
env_array = g_ptr_array_new_with_free_func (g_free);
add_exports (env_array, default_exports, G_N_ELEMENTS (default_exports));
if (!use_ld_so_cache)
add_exports (env_array, no_ld_so_cache_exports, G_N_ELEMENTS (no_ld_so_cache_exports));
if (devel)
add_exports (env_array, devel_exports, G_N_ELEMENTS (devel_exports));
for (i = 0; i < G_N_ELEMENTS (copy); i++)
{
const char *current = g_getenv (copy[i]);
if (current)
g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", copy[i], current));
}
if (!devel)
{
for (i = 0; i < G_N_ELEMENTS (copy_nodevel); i++)
{
const char *current = g_getenv (copy_nodevel[i]);
if (current)
g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", copy_nodevel[i], current));
}
}
g_ptr_array_add (env_array, NULL);
return (char **) g_ptr_array_free (env_array, FALSE);
}
static char **
apply_exports (char **envp,
const ExportData *exports,
gsize n_exports)
{
int i;
for (i = 0; i < n_exports; i++)
{
const char *value = exports[i].val;
if (value)
envp = g_environ_setenv (envp, exports[i].env, value, TRUE);
else
envp = g_environ_unsetenv (envp, exports[i].env);
}
return envp;
}
void
flatpak_run_apply_env_default (FlatpakBwrap *bwrap, gboolean use_ld_so_cache)
{
bwrap->envp = apply_exports (bwrap->envp, default_exports, G_N_ELEMENTS (default_exports));
if (!use_ld_so_cache)
bwrap->envp = apply_exports (bwrap->envp, no_ld_so_cache_exports, G_N_ELEMENTS (no_ld_so_cache_exports));
}
static void
flatpak_run_apply_env_prompt (FlatpakBwrap *bwrap, const char *app_id)
{
/* A custom shell prompt. FLATPAK_ID is always set.
* PS1 can be overwritten by runtime metadata or by --env overrides
*/
flatpak_bwrap_set_env (bwrap, "FLATPAK_ID", app_id, TRUE);
flatpak_bwrap_set_env (bwrap, "PS1", "[📦 $FLATPAK_ID \\W]\\$ ", FALSE);
}
#endif
void
flatpak_run_apply_env_appid (FlatpakBwrap *bwrap,
GFile *app_dir)
{
g_autoptr(GFile) app_dir_data = NULL;
g_autoptr(GFile) app_dir_config = NULL;
g_autoptr(GFile) app_dir_cache = NULL;
app_dir_data = g_file_get_child (app_dir, "data");
app_dir_config = g_file_get_child (app_dir, "config");
app_dir_cache = g_file_get_child (app_dir, "cache");
flatpak_bwrap_set_env (bwrap, "XDG_DATA_HOME", flatpak_file_get_path_cached (app_dir_data), TRUE);
flatpak_bwrap_set_env (bwrap, "XDG_CONFIG_HOME", flatpak_file_get_path_cached (app_dir_config), TRUE);
flatpak_bwrap_set_env (bwrap, "XDG_CACHE_HOME", flatpak_file_get_path_cached (app_dir_cache), TRUE);
if (g_getenv ("XDG_DATA_HOME"))
flatpak_bwrap_set_env (bwrap, "HOST_XDG_DATA_HOME", g_getenv ("XDG_DATA_HOME"), TRUE);
if (g_getenv ("XDG_CONFIG_HOME"))
flatpak_bwrap_set_env (bwrap, "HOST_XDG_CONFIG_HOME", g_getenv ("XDG_CONFIG_HOME"), TRUE);
if (g_getenv ("XDG_CACHE_HOME"))
flatpak_bwrap_set_env (bwrap, "HOST_XDG_CACHE_HOME", g_getenv ("XDG_CACHE_HOME"), TRUE);
}
#if 0
void
flatpak_run_apply_env_vars (FlatpakBwrap *bwrap, FlatpakContext *context)
{
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init (&iter, context->env_vars);
while (g_hash_table_iter_next (&iter, &key, &value))
{
const char *var = key;
const char *val = value;
if (val && val[0] != 0)
flatpak_bwrap_set_env (bwrap, var, val, TRUE);
else
flatpak_bwrap_unset_env (bwrap, var);
}
}
#endif
GFile *
flatpak_get_data_dir (const char *app_id)
{
g_autoptr(GFile) home = g_file_new_for_path (g_get_home_dir ());
g_autoptr(GFile) var_app = g_file_resolve_relative_path (home, ".var/app");
return g_file_get_child (var_app, app_id);
}
#if 0
gboolean
flatpak_ensure_data_dir (GFile *app_id_dir,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GFile) data_dir = g_file_get_child (app_id_dir, "data");
g_autoptr(GFile) cache_dir = g_file_get_child (app_id_dir, "cache");
g_autoptr(GFile) fontconfig_cache_dir = g_file_get_child (cache_dir, "fontconfig");
g_autoptr(GFile) tmp_dir = g_file_get_child (cache_dir, "tmp");
g_autoptr(GFile) config_dir = g_file_get_child (app_id_dir, "config");
if (!flatpak_mkdir_p (data_dir, cancellable, error))
return FALSE;
if (!flatpak_mkdir_p (cache_dir, cancellable, error))
return FALSE;
if (!flatpak_mkdir_p (fontconfig_cache_dir, cancellable, error))
return FALSE;
if (!flatpak_mkdir_p (tmp_dir, cancellable, error))
return FALSE;
if (!flatpak_mkdir_p (config_dir, cancellable, error))
return FALSE;
return TRUE;
}
#endif