Files
flatpak/common/flatpak-run.c
Simon McVittie 60e2cceb8c common: Replace all flatpak_debug2() with g_debug()
They are now equivalent.

Resolves: https://github.com/flatpak/flatpak/issues/5001
Signed-off-by: Simon McVittie <smcv@collabora.com>
2022-12-15 16:45:35 +00:00

4809 lines
170 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s:
* Copyright © 2014-2019 Red Hat, Inc
*
* 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 <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <gio/gdesktopappinfo.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/vfs.h>
#include <sys/wait.h>
#include <sys/personality.h>
#include <grp.h>
#include <unistd.h>
#include <gio/gunixfdlist.h>
#ifdef HAVE_DCONF
#include <dconf/dconf.h>
#endif
#ifdef HAVE_LIBMALCONTENT
#include <libmalcontent/malcontent.h>
#endif
#include "flatpak-syscalls-private.h"
#ifdef ENABLE_SECCOMP
#include <seccomp.h>
#endif
#ifdef ENABLE_XAUTH
#include <X11/Xauth.h>
#endif
#include <glib/gi18n-lib.h>
#include <gio/gio.h>
#include "libglnx.h"
#include "flatpak-run-private.h"
#include "flatpak-utils-base-private.h"
#include "flatpak-dir-private.h"
#include "flatpak-instance-private.h"
#include "flatpak-systemd-dbus-generated.h"
#include "flatpak-document-dbus-generated.h"
#include "flatpak-error.h"
#include "session-helper/flatpak-session-helper.h"
#define DEFAULT_SHELL "/bin/sh"
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);
}
/* This is part of the X11 protocol, so we can safely hard-code it here */
#define FamilyInternet6 (6)
#ifdef ENABLE_XAUTH
static gboolean
auth_streq (const char *str,
const char *au_str,
size_t au_len)
{
return au_len == strlen (str) && memcmp (str, au_str, au_len) == 0;
}
static gboolean
xauth_entry_should_propagate (const Xauth *xa,
int family,
const char *remote_hostname,
const char *local_hostname,
const char *number)
{
/* ensure entry isn't for a different type of access */
if (family != FamilyWild && xa->family != family && xa->family != FamilyWild)
return FALSE;
/* ensure entry isn't for remote access, except that if remote_hostname
* is specified, then remote access to that hostname is OK */
if (xa->family != FamilyWild && xa->family != FamilyLocal &&
(remote_hostname == NULL ||
!auth_streq (remote_hostname, xa->address, xa->address_length)))
return FALSE;
/* ensure entry is for this machine */
if (xa->family == FamilyLocal && !auth_streq (local_hostname, xa->address, xa->address_length))
{
/* OpenSUSE inherits the hostname value from DHCP without updating
* its X11 authentication cookie. The old hostname value can still
* be found in the environment variable XAUTHLOCALHOSTNAME.
* For reference:
* https://bugzilla.opensuse.org/show_bug.cgi?id=262309
* For this reason if we have a cookie whose address is equal to the
* variable XAUTHLOCALHOSTNAME, we still need to propagate it, but
* we also need to change its address to `unames.nodename`.
*/
const char *xauth_local_hostname;
xauth_local_hostname = g_getenv ("XAUTHLOCALHOSTNAME");
if (xauth_local_hostname == NULL)
return FALSE;
if (!auth_streq ((char *) xauth_local_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 (int family,
const char *remote_host,
const 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, family, remote_host,
unames.nodename, number))
{
local_xa = *xa;
if (local_xa.family == FamilyLocal &&
!auth_streq (unames.nodename, local_xa.address, local_xa.address_length))
{
/* If we decided to propagate this cookie, but its address
* doesn't match `unames.nodename`, we need to change it or
* inside the container it will not work.
*/
local_xa.address = unames.nodename;
local_xa.address_length = strlen (local_xa.address);
}
if (!XauWriteAuth (output, &local_xa))
g_warning ("xauth write error");
}
XauDisposeAuth (xa);
}
fclose (f);
}
#else /* !ENABLE_XAUTH */
/* When not doing Xauth, any distinct values will do, but use the same
* ones Xauth does so that we can refer to them in our unit test. */
#define FamilyLocal (256)
#define FamilyWild (65535)
#endif /* !ENABLE_XAUTH */
/*
* @family: (out) (not optional):
* @x11_socket: (out) (not optional):
* @display_nr_out: (out) (not optional):
*/
gboolean
flatpak_run_parse_x11_display (const char *display,
int *family,
char **x11_socket,
char **remote_host,
char **display_nr_out,
GError **error)
{
const char *colon;
const char *display_nr;
const char *display_nr_end;
/* Use the last ':', not the first, to cope with [::1]:0 */
colon = strrchr (display, ':');
if (colon == NULL)
return glnx_throw (error, "No colon found in DISPLAY=%s", display);
if (!g_ascii_isdigit (colon[1]))
return glnx_throw (error, "Colon not followed by a digit in DISPLAY=%s", display);
display_nr = &colon[1];
display_nr_end = display_nr;
while (g_ascii_isdigit (*display_nr_end))
display_nr_end++;
*display_nr_out = g_strndup (display_nr, display_nr_end - display_nr);
if (display == colon || g_str_has_prefix (display, "unix:"))
{
*family = FamilyLocal;
*x11_socket = g_strdup_printf ("/tmp/.X11-unix/X%s", *display_nr_out);
}
else if (display[0] == '[' && display[colon - display - 1] == ']')
{
*family = FamilyInternet6;
*remote_host = g_strndup (display + 1, colon - display - 2);
}
else
{
*family = FamilyWild;
*remote_host = g_strndup (display, colon - display);
}
return TRUE;
}
static void
flatpak_run_add_x11_args (FlatpakBwrap *bwrap,
gboolean allowed,
FlatpakContextShares shares)
{
g_autofree char *x11_socket = NULL;
const char *display;
g_autoptr(GError) local_error = NULL;
/* 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.
*
* We need to be a bit careful here, because there are two situations in
* which potentially hostile processes have access to /tmp and could
* create symlinks, which in principle could cause us to create the
* directory and mount the tmpfs at the target of the symlink instead
* of in the intended place:
*
* - With --filesystem=/tmp, it's the host /tmp - but because of the
* special historical status of /tmp/.X11-unix, we can assume that
* it is pre-created by the host system before user code gets to run.
*
* - When /tmp is shared between all instances of the same app ID,
* in principle the app has control over what's in /tmp, but in
* practice it can't interfere with /tmp/.X11-unix, because we do
* this unconditionally - therefore by the time app code runs,
* /tmp/.X11-unix is already a mount point, meaning the app cannot
* rename or delete it.
*/
flatpak_bwrap_add_args (bwrap,
"--tmpfs", "/tmp/.X11-unix",
NULL);
if (!allowed)
{
flatpak_bwrap_unset_env (bwrap, "DISPLAY");
return;
}
g_info ("Allowing x11 access");
display = g_getenv ("DISPLAY");
if (display != NULL)
{
g_autofree char *remote_host = NULL;
g_autofree char *display_nr = NULL;
int family = -1;
if (!flatpak_run_parse_x11_display (display, &family, &x11_socket,
&remote_host, &display_nr,
&local_error))
{
g_warning ("%s", local_error->message);
flatpak_bwrap_unset_env (bwrap, "DISPLAY");
return;
}
g_assert (display_nr != NULL);
if (x11_socket != NULL
&& g_file_test (x11_socket, G_FILE_TEST_EXISTS))
{
g_assert (g_str_has_prefix (x11_socket, "/tmp/.X11-unix/X"));
flatpak_bwrap_add_args (bwrap,
"--ro-bind", x11_socket, x11_socket,
NULL);
flatpak_bwrap_set_env (bwrap, "DISPLAY", display, TRUE);
}
else if ((shares & FLATPAK_CONTEXT_SHARED_NETWORK) == 0)
{
/* If DISPLAY is for example :42 but /tmp/.X11-unix/X42
* doesn't exist, then the only way this is going to work
* is if the app can connect to abstract socket
* @/tmp/.X11-unix/X42 or to TCP port localhost:6042,
* either of which requires a shared network namespace.
*
* Alternatively, if DISPLAY is othermachine:23, then we
* definitely need access to TCP port othermachine:6023. */
if (x11_socket != NULL)
g_warning ("X11 socket %s does not exist in filesystem.",
x11_socket);
else
g_warning ("Remote X11 display detected.");
g_warning ("X11 access will require --share=network permission.");
}
else if (x11_socket != NULL)
{
g_warning ("X11 socket %s does not exist in filesystem, "
"trying to use abstract socket instead.",
x11_socket);
}
else
{
g_debug ("Assuming --share=network gives access to remote X11");
}
#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)
{
static const char dest[] = "/run/flatpak/Xauthority";
write_xauth (family, remote_host, display_nr, 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");
}
}
static 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";
if (wayland_display[0] == '/')
wayland_socket = g_strdup (wayland_display);
else
wayland_socket = g_build_filename (user_runtime_dir, wayland_display, NULL);
if (!g_str_has_prefix (wayland_display, "wayland-") ||
strchr (wayland_display, '/') != NULL)
{
wayland_display = "wayland-0";
flatpak_bwrap_set_env (bwrap, "WAYLAND_DISPLAY", wayland_display, TRUE);
}
sandbox_wayland_socket = g_strdup_printf ("/run/flatpak/%s", 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);
flatpak_bwrap_add_runtime_dir_member (bwrap, wayland_display);
}
return res;
}
static void
flatpak_run_add_ssh_args (FlatpakBwrap *bwrap)
{
static const char sandbox_auth_socket[] = "/run/flatpak/ssh-auth";
const char * auth_socket;
auth_socket = g_getenv ("SSH_AUTH_SOCK");
if (!auth_socket)
return; /* ssh agent not present */
if (!g_file_test (auth_socket, G_FILE_TEST_EXISTS))
{
/* Let's clean it up, so that the application will not try to connect */
flatpak_bwrap_unset_env (bwrap, "SSH_AUTH_SOCK");
return;
}
flatpak_bwrap_add_args (bwrap,
"--ro-bind", auth_socket, sandbox_auth_socket,
NULL);
flatpak_bwrap_set_env (bwrap, "SSH_AUTH_SOCK", sandbox_auth_socket, TRUE);
}
static void
flatpak_run_add_pcsc_args (FlatpakBwrap *bwrap)
{
const char * pcsc_socket;
const char * sandbox_pcsc_socket = "/run/pcscd/pcscd.comm";
pcsc_socket = g_getenv ("PCSCLITE_CSOCK_NAME");
if (pcsc_socket)
{
if (!g_file_test (pcsc_socket, G_FILE_TEST_EXISTS))
{
flatpak_bwrap_unset_env (bwrap, "PCSCLITE_CSOCK_NAME");
return;
}
}
else
{
pcsc_socket = "/run/pcscd/pcscd.comm";
if (!g_file_test (pcsc_socket, G_FILE_TEST_EXISTS))
return;
}
flatpak_bwrap_add_args (bwrap,
"--ro-bind", pcsc_socket, sandbox_pcsc_socket,
NULL);
flatpak_bwrap_set_env (bwrap, "PCSCLITE_CSOCK_NAME", sandbox_pcsc_socket, TRUE);
}
static gboolean
flatpak_run_cups_check_server_is_socket (const char *server)
{
if (g_str_has_prefix (server, "/") && strstr (server, ":") == NULL)
return TRUE;
return FALSE;
}
/* Try to find a default server from a cups confguration file */
static char *
flatpak_run_get_cups_server_name_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_info ("CUPS 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 == '#'))
continue;
g_auto(GStrv) tokens = g_strsplit (line, " ", 2);
if ((tokens[0] != NULL) && (tokens[1] != NULL))
{
if (strcmp ("ServerName", tokens[0]) == 0)
{
g_strchug (tokens[1]);
if (flatpak_run_cups_check_server_is_socket (tokens[1]))
return g_strdup (tokens[1]);
}
}
}
return NULL;
}
static char *
flatpak_run_get_cups_server_name (void)
{
g_autofree char * cups_server = NULL;
g_autofree char * cups_config_path = NULL;
/* TODO
* we don't currently support cups servers located on the network, if such
* server is detected, we simply ignore it and in the worst case we fallback
* to the default socket
*/
cups_server = g_strdup (g_getenv ("CUPS_SERVER"));
if (cups_server && flatpak_run_cups_check_server_is_socket (cups_server))
return g_steal_pointer (&cups_server);
g_clear_pointer (&cups_server, g_free);
cups_config_path = g_build_filename (g_get_home_dir (), ".cups/client.conf", NULL);
cups_server = flatpak_run_get_cups_server_name_config (cups_config_path);
if (cups_server && flatpak_run_cups_check_server_is_socket (cups_server))
return g_steal_pointer (&cups_server);
g_clear_pointer (&cups_server, g_free);
cups_server = flatpak_run_get_cups_server_name_config ("/etc/cups/client.conf");
if (cups_server && flatpak_run_cups_check_server_is_socket (cups_server))
return g_steal_pointer (&cups_server);
// Fallback to default socket
return g_strdup ("/var/run/cups/cups.sock");
}
static void
flatpak_run_add_cups_args (FlatpakBwrap *bwrap)
{
g_autofree char * sandbox_server_name = g_strdup ("/var/run/cups/cups.sock");
g_autofree char * cups_server_name = flatpak_run_get_cups_server_name ();
if (!g_file_test (cups_server_name, G_FILE_TEST_EXISTS))
{
g_info ("Could not find CUPS server");
return;
}
flatpak_bwrap_add_args (bwrap,
"--ro-bind", cups_server_name, sandbox_server_name,
NULL);
}
static void
flatpak_run_add_gpg_agent_args (FlatpakBwrap *bwrap)
{
const char * agent_socket;
g_autofree char * sandbox_agent_socket = NULL;
g_autoptr(GError) gpgconf_error = NULL;
g_autoptr(GSubprocess) process = NULL;
g_autoptr(GInputStream) base_stream = NULL;
g_autoptr(GDataInputStream) data_stream = NULL;
process = g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE,
&gpgconf_error,
"gpgconf", "--list-dir", "agent-socket", NULL);
if (gpgconf_error)
{
g_info ("GPG-Agent directories: %s", gpgconf_error->message);
return;
}
base_stream = g_subprocess_get_stdout_pipe (process);
data_stream = g_data_input_stream_new (base_stream);
agent_socket = g_data_input_stream_read_line (data_stream,
NULL, NULL,
&gpgconf_error);
if (!agent_socket || gpgconf_error)
{
g_info ("GPG-Agent directories: %s", gpgconf_error->message);
return;
}
sandbox_agent_socket = g_strdup_printf ("/run/user/%d/gnupg/S.gpg-agent", getuid ());
flatpak_bwrap_add_args (bwrap,
"--ro-bind-try", agent_socket, sandbox_agent_socket,
NULL);
}
/* 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_info ("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_info ("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;
}
/*
* Parse a PulseAudio server string, as documented on
* https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/ServerStrings/.
* Returns the first supported server address, or NULL if none are supported,
* or NULL with @remote set if @value points to a remote server.
*/
static char *
flatpak_run_parse_pulse_server (const char *value,
gboolean *remote)
{
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, "{"))
{
/*
* TODO: compare the value within {} to the local hostname and D-Bus machine ID,
* and skip if it matches neither.
*/
const char * closing = strstr (server, "}");
if (closing == NULL)
continue;
server = closing + 1;
}
if (g_str_has_prefix (server, "unix:"))
return g_strdup (server + 5);
if (server[0] == '/')
return g_strdup (server);
if (g_str_has_prefix (server, "tcp:"))
{
*remote = TRUE;
return NULL;
}
}
return NULL;
}
/*
* Get the machine ID as used by PulseAudio. This is the systemd/D-Bus
* machine ID, or failing that, the hostname.
*/
static char *
flatpak_run_get_pulse_machine_id (void)
{
static const char * const machine_ids[] =
{
"/etc/machine-id",
"/var/lib/dbus/machine-id",
};
gsize i;
for (i = 0; i < G_N_ELEMENTS (machine_ids); i++)
{
g_autofree char *ret = NULL;
if (g_file_get_contents (machine_ids[i], &ret, NULL, NULL))
{
gsize j;
g_strstrip (ret);
for (j = 0; ret[j] != '\0'; j++)
{
if (!g_ascii_isxdigit (ret[j]))
break;
}
if (ret[0] != '\0' && ret[j] == '\0')
return g_steal_pointer (&ret);
}
}
return g_strdup (g_get_host_name ());
}
/*
* Get the directory used by PulseAudio for its configuration.
*/
static char *
flatpak_run_get_pulse_home (void)
{
/* Legacy path ~/.pulse is tried first, for compatibility */
{
const char *parent = g_get_home_dir ();
g_autofree char *ret = g_build_filename (parent, ".pulse", NULL);
if (g_file_test (ret, G_FILE_TEST_IS_DIR))
return g_steal_pointer (&ret);
}
/* The more modern path, usually ~/.config/pulse */
{
const char *parent = g_get_user_config_dir ();
/* Usually ~/.config/pulse */
g_autofree char *ret = g_build_filename (parent, "pulse", NULL);
if (g_file_test (ret, G_FILE_TEST_IS_DIR))
return g_steal_pointer (&ret);
}
return NULL;
}
/*
* Get the runtime directory used by PulseAudio for its socket.
*/
static char *
flatpak_run_get_pulse_runtime_dir (void)
{
const char *val = NULL;
val = g_getenv ("PULSE_RUNTIME_PATH");
if (val != NULL)
return realpath (val, NULL);
{
const char *user_runtime_dir = g_get_user_runtime_dir ();
if (user_runtime_dir != NULL)
{
g_autofree char *dir = g_build_filename (user_runtime_dir, "pulse", NULL);
if (g_file_test (dir, G_FILE_TEST_IS_DIR))
return realpath (dir, NULL);
}
}
{
g_autofree char *pulse_home = flatpak_run_get_pulse_home ();
g_autofree char *machine_id = flatpak_run_get_pulse_machine_id ();
if (pulse_home != NULL && machine_id != NULL)
{
/* This is usually a symlink, but we take its realpath() anyway */
g_autofree char *dir = g_strdup_printf ("%s/%s-runtime", pulse_home, machine_id);
if (g_file_test (dir, G_FILE_TEST_IS_DIR))
return realpath (dir, NULL);
}
}
return NULL;
}
static void
flatpak_run_add_pulseaudio_args (FlatpakBwrap *bwrap,
FlatpakContextShares shares)
{
g_autofree char *pulseaudio_server = flatpak_run_get_pulseaudio_server ();
g_autofree char *pulseaudio_socket = NULL;
g_autofree char *pulse_runtime_dir = flatpak_run_get_pulse_runtime_dir ();
gboolean remote = FALSE;
if (pulseaudio_server)
pulseaudio_socket = flatpak_run_parse_pulse_server (pulseaudio_server,
&remote);
if (pulseaudio_socket == NULL && !remote)
{
pulseaudio_socket = g_build_filename (pulse_runtime_dir, "native", NULL);
if (!g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS))
g_clear_pointer (&pulseaudio_socket, g_free);
}
if (pulseaudio_socket == NULL && !remote)
{
pulseaudio_socket = realpath ("/var/run/pulse/native", NULL);
if (pulseaudio_socket && !g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS))
g_clear_pointer (&pulseaudio_socket, g_free);
}
flatpak_bwrap_unset_env (bwrap, "PULSE_SERVER");
if (remote)
{
if ((shares & FLATPAK_CONTEXT_SHARED_NETWORK) == 0)
{
g_warning ("Remote PulseAudio server configured.");
g_warning ("PulseAudio access will require --share=network permission.");
}
g_info ("Using remote PulseAudio server \"%s\"", pulseaudio_server);
flatpak_bwrap_set_env (bwrap, "PULSE_SERVER", pulseaudio_server, TRUE);
}
else if (pulseaudio_socket && g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS))
{
static const char sandbox_socket_path[] = "/run/flatpak/pulse/native";
static const char pulse_server[] = "unix:/run/flatpak/pulse/native";
static const char config_path[] = "/run/flatpak/pulse/config";
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");
/* 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);
flatpak_bwrap_add_runtime_dir_member (bwrap, "pulse");
}
else
g_info ("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 (!remote && g_file_test ("/dev/snd", G_FILE_TEST_IS_DIR))
flatpak_bwrap_add_args (bwrap, "--dev-bind", "/dev/snd", "/dev/snd", NULL);
}
static void
flatpak_run_add_gssproxy_args (FlatpakBwrap *bwrap)
{
/* We only expose the gssproxy user service. The gssproxy system service is
* not intended to be exposed to sandboxed environments.
*/
g_autofree char *gssproxy_host_dir = g_build_filename (g_get_user_runtime_dir (), "gssproxy", NULL);
const char *gssproxy_sandboxed_dir = "/run/flatpak/gssproxy/";
if (g_file_test (gssproxy_host_dir, G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap, "--ro-bind", gssproxy_host_dir, gssproxy_sandboxed_dir, NULL);
}
static void
flatpak_run_add_resolved_args (FlatpakBwrap *bwrap)
{
const char *resolved_socket = "/run/systemd/resolve/io.systemd.Resolve";
if (g_file_test (resolved_socket, G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap, "--bind", resolved_socket, resolved_socket, NULL);
}
static void
flatpak_run_add_journal_args (FlatpakBwrap *bwrap)
{
g_autofree char *journal_socket_socket = g_strdup ("/run/systemd/journal/socket");
g_autofree char *journal_stdout_socket = g_strdup ("/run/systemd/journal/stdout");
if (g_file_test (journal_socket_socket, G_FILE_TEST_EXISTS))
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", journal_socket_socket, journal_socket_socket,
NULL);
}
if (g_file_test (journal_stdout_socket, G_FILE_TEST_EXISTS))
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", journal_stdout_socket, journal_stdout_socket,
NULL);
}
}
static char *
create_proxy_socket (char *template)
{
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);
g_autofree char *proxy_socket = g_build_filename (proxy_socket_dir, template, NULL);
int fd;
if (!glnx_shutil_mkdir_p_at (AT_FDCWD, proxy_socket_dir, 0755, NULL, NULL))
return NULL;
fd = g_mkstemp (proxy_socket);
if (fd == -1)
return NULL;
close (fd);
return g_steal_pointer (&proxy_socket);
}
static gboolean
flatpak_run_add_system_dbus_args (FlatpakBwrap *app_bwrap,
FlatpakBwrap *proxy_arg_bwrap,
FlatpakContext *context,
FlatpakRunFlags flags)
{
gboolean unrestricted, no_proxy;
const char *dbus_address = g_getenv ("DBUS_SYSTEM_BUS_ADDRESS");
g_autofree char *real_dbus_address = NULL;
g_autofree char *dbus_system_socket = NULL;
unrestricted = (context->sockets & FLATPAK_CONTEXT_SOCKET_SYSTEM_BUS) != 0;
if (unrestricted)
g_info ("Allowing system-dbus access");
no_proxy = (flags & FLATPAK_RUN_FLAG_NO_SYSTEM_BUS_PROXY) != 0;
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 && unrestricted)
{
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;
}
else if (!no_proxy && flatpak_context_get_needs_system_bus_proxy (context))
{
g_autofree char *proxy_socket = create_proxy_socket ("system-bus-proxy-XXXXXX");
if (proxy_socket == NULL)
return FALSE;
if (dbus_address)
real_dbus_address = g_strdup (dbus_address);
else
real_dbus_address = g_strdup_printf ("unix:path=%s", dbus_system_socket);
flatpak_bwrap_add_args (proxy_arg_bwrap, real_dbus_address, proxy_socket, NULL);
if (!unrestricted)
flatpak_context_add_bus_filters (context, NULL, FALSE, flags & FLATPAK_RUN_FLAG_SANDBOX, proxy_arg_bwrap);
if ((flags & FLATPAK_RUN_FLAG_LOG_SYSTEM_BUS) != 0)
flatpak_bwrap_add_args (proxy_arg_bwrap, "--log", NULL);
flatpak_bwrap_add_args (app_bwrap,
"--ro-bind", proxy_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;
}
static gboolean
flatpak_run_add_session_dbus_args (FlatpakBwrap *app_bwrap,
FlatpakBwrap *proxy_arg_bwrap,
FlatpakContext *context,
FlatpakRunFlags flags,
const char *app_id)
{
static const char sandbox_socket_path[] = "/run/flatpak/bus";
static const char sandbox_dbus_address[] = "unix:path=/run/flatpak/bus";
gboolean unrestricted, no_proxy;
const char *dbus_address = g_getenv ("DBUS_SESSION_BUS_ADDRESS");
g_autofree char *dbus_session_socket = NULL;
unrestricted = (context->sockets & FLATPAK_CONTEXT_SOCKET_SESSION_BUS) != 0;
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 (unrestricted)
g_info ("Allowing session-dbus access");
no_proxy = (flags & FLATPAK_RUN_FLAG_NO_SESSION_BUS_PROXY) != 0;
if (dbus_session_socket != NULL && unrestricted)
{
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);
flatpak_bwrap_add_runtime_dir_member (app_bwrap, "bus");
return TRUE;
}
else if (!no_proxy && dbus_address != NULL)
{
g_autofree char *proxy_socket = create_proxy_socket ("session-bus-proxy-XXXXXX");
if (proxy_socket == NULL)
return FALSE;
flatpak_bwrap_add_args (proxy_arg_bwrap, dbus_address, proxy_socket, NULL);
if (!unrestricted)
{
flatpak_context_add_bus_filters (context, app_id, TRUE, flags & FLATPAK_RUN_FLAG_SANDBOX, proxy_arg_bwrap);
/* Allow calling any interface+method on all portals, but only receive broadcasts under /org/desktop/portal */
flatpak_bwrap_add_arg (proxy_arg_bwrap,
"--call=org.freedesktop.portal.*=*");
flatpak_bwrap_add_arg (proxy_arg_bwrap,
"--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*");
}
if ((flags & FLATPAK_RUN_FLAG_LOG_SESSION_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, "DBUS_SESSION_BUS_ADDRESS", sandbox_dbus_address, TRUE);
flatpak_bwrap_add_runtime_dir_member (app_bwrap, "bus");
return TRUE;
}
return FALSE;
}
static gboolean
flatpak_run_add_a11y_dbus_args (FlatpakBwrap *app_bwrap,
FlatpakBwrap *proxy_arg_bwrap,
FlatpakContext *context,
FlatpakRunFlags flags)
{
static const char sandbox_socket_path[] = "/run/flatpak/at-spi-bus";
static const char sandbox_dbus_address[] = "unix:path=/run/flatpak/at-spi-bus";
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;
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 (bwrap, "--perms", "0600", NULL);
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;
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_info ("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);
}
void
flatpak_run_extend_ld_path (FlatpakBwrap *bwrap,
const char *prepend,
const char *append)
{
g_autoptr(GString) ld_library_path = g_string_new (g_environ_getenv (bwrap->envp, "LD_LIBRARY_PATH"));
if (prepend != NULL && *prepend != '\0')
{
if (ld_library_path->len > 0)
g_string_prepend (ld_library_path, ":");
g_string_prepend (ld_library_path, prepend);
}
if (append != NULL && *append != '\0')
{
if (ld_library_path->len > 0)
g_string_append (ld_library_path, ":");
g_string_append (ld_library_path, append);
}
flatpak_bwrap_set_env (bwrap, "LD_LIBRARY_PATH", ld_library_path->str, TRUE);
}
gboolean
flatpak_run_add_extension_args (FlatpakBwrap *bwrap,
GKeyFile *metakey,
FlatpakDecomposed *ref,
gboolean use_ld_so_cache,
const char *target_path,
char **extensions_out,
char **ld_path_out,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GString) used_extensions = g_string_new ("");
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);
g_autofree char *arch = flatpak_decomposed_dup_arch (ref);
const char *branch = flatpak_decomposed_get_branch (ref);
g_return_val_if_fail (target_path != NULL, FALSE);
extensions = flatpak_list_extensions (metakey, arch, branch);
/* 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 (target_path, ext->directory, NULL);
g_autofree char *full_directory = g_build_filename (directory, ext->subdir_suffix, NULL);
g_autofree char *ref_file = 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_file,
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 (target_path, 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", flatpak_decomposed_get_kind_str (ref), ++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 (extensions_out)
*extensions_out = g_string_free (g_steal_pointer (&used_extensions), FALSE);
if (ld_path_out)
*ld_path_out = g_string_free (g_steal_pointer (&ld_library_path), FALSE);
return TRUE;
}
/*
* @per_app_dir_lock_fd: If >= 0, make use of per-app directories in
* the host's XDG_RUNTIME_DIR to share /tmp between instances.
*/
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,
int per_app_dir_lock_fd,
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);
g_autofree char *xdg_dirs_conf = NULL;
gboolean has_wayland = FALSE;
gboolean allow_x11 = FALSE;
gboolean home_access = FALSE;
gboolean sandboxed = (flags & FLATPAK_RUN_FLAG_SANDBOX) != 0;
if ((context->shares & FLATPAK_CONTEXT_SHARED_IPC) == 0)
{
g_info ("Disallowing ipc access");
flatpak_bwrap_add_args (bwrap, "--unshare-ipc", NULL);
}
if ((context->shares & FLATPAK_CONTEXT_SHARED_NETWORK) == 0)
{
g_info ("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)
{
/* Don't do anything special: include shm in the
* shared /dev. The host and all sandboxes and subsandboxes
* all share /dev/shm */
}
else if ((context->features & FLATPAK_CONTEXT_FEATURE_PER_APP_DEV_SHM)
&& per_app_dir_lock_fd >= 0)
{
g_autofree char *shared_dev_shm = NULL;
/* The host and the original sandbox have separate /dev/shm,
* but we want other instances to be able to share /dev/shm with
* the first sandbox (except for subsandboxes run with
* flatpak-spawn --sandbox, which will have their own). */
if (!flatpak_instance_ensure_per_app_dev_shm (app_id,
per_app_dir_lock_fd,
&shared_dev_shm,
error))
return FALSE;
flatpak_bwrap_add_args (bwrap,
"--bind", shared_dev_shm, "/dev/shm",
NULL);
}
else
{
/* The host, the original sandbox and each subsandbox
* each have a separate /dev/shm. */
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 if ((context->features & FLATPAK_CONTEXT_FEATURE_PER_APP_DEV_SHM)
&& per_app_dir_lock_fd >= 0)
{
g_autofree char *shared_dev_shm = NULL;
/* The host and the original sandbox have separate /dev/shm,
* but we want other instances to be able to share /dev/shm,
* except for flatpak-spawn --subsandbox. */
if (!flatpak_instance_ensure_per_app_dev_shm (app_id,
per_app_dir_lock_fd,
&shared_dev_shm,
error))
return FALSE;
flatpak_bwrap_add_args (bwrap,
"--bind", shared_dev_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_info ("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_info ("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_info ("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);
}
else if ((context->features & FLATPAK_CONTEXT_FEATURE_PER_APP_DEV_SHM)
&& per_app_dir_lock_fd >= 0)
{
g_autofree char *shared_dev_shm = NULL;
if (!flatpak_instance_ensure_per_app_dev_shm (app_id,
per_app_dir_lock_fd,
&shared_dev_shm,
error))
return FALSE;
flatpak_bwrap_add_args (bwrap,
"--bind", shared_dev_shm, "/dev/shm",
NULL);
}
}
exports = flatpak_context_get_exports_full (context,
app_id_dir, previous_app_id_dirs,
TRUE, TRUE,
&xdg_dirs_conf, &home_access);
if (flatpak_exports_path_is_visible (exports, "/tmp"))
{
/* The original sandbox and any subsandboxes are both already
* going to share /tmp with the host, so by transitivity they will
* also share it with each other, and with all other instances. */
}
else if (per_app_dir_lock_fd >= 0 && !sandboxed)
{
g_autofree char *shared_tmp = NULL;
/* The host and the original sandbox have separate /tmp,
* but we want other instances to be able to share /tmp with the
* first sandbox, unless they were created by
* flatpak-spawn --sandbox.
*
* In apply_extra and `flatpak build`, per_app_dir_lock_fd is
* negative and we skip this. */
if (!flatpak_instance_ensure_per_app_tmp (app_id,
per_app_dir_lock_fd,
&shared_tmp,
error))
return FALSE;
flatpak_bwrap_add_args (bwrap,
"--bind", shared_tmp, "/tmp",
NULL);
}
flatpak_context_append_bwrap_filesystem (context, bwrap, app_id, app_id_dir,
exports, xdg_dirs_conf, home_access);
if (context->sockets & FLATPAK_CONTEXT_SOCKET_WAYLAND)
{
g_info ("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, context->shares);
if (context->sockets & FLATPAK_CONTEXT_SOCKET_SSH_AUTH)
{
flatpak_run_add_ssh_args (bwrap);
}
if (context->sockets & FLATPAK_CONTEXT_SOCKET_PULSEAUDIO)
{
g_info ("Allowing pulseaudio access");
flatpak_run_add_pulseaudio_args (bwrap, context->shares);
}
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);
}
if (context->sockets & FLATPAK_CONTEXT_SOCKET_GPG_AGENT)
{
flatpak_run_add_gpg_agent_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);
/* 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_info ("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"},
/* Unset temporary file paths as they may not exist in the sandbox */
{"TEMP", NULL},
{"TEMPDIR", NULL},
{"TMP", NULL},
{"TMPDIR", NULL},
/* We always use /run/user/UID, even if the user's XDG_RUNTIME_DIR
* outside the sandbox is somewhere else. Don't allow a different
* setting from outside the sandbox to overwrite this. */
{"XDG_RUNTIME_DIR", NULL},
/* Some env vars are common enough and will affect the sandbox badly
if set on the host. We clear these always. If updating this list,
also update the list in flatpak-run.xml. */
{"PYTHONPATH", NULL},
{"PERLLIB", NULL},
{"PERL5LIB", NULL},
{"XCURSOR_PATH", NULL},
{"GST_PLUGIN_PATH_1_0", NULL},
{"GST_REGISTRY", NULL},
{"GST_REGISTRY_1_0", NULL},
{"GST_PLUGIN_PATH", NULL},
{"GST_PLUGIN_SYSTEM_PATH", NULL},
{"GST_PLUGIN_SCANNER", NULL},
{"GST_PLUGIN_SCANNER_1_0", NULL},
{"GST_PLUGIN_SYSTEM_PATH_1_0", NULL},
{"GST_PRESET_PATH", NULL},
{"GST_PTP_HELPER", NULL},
{"GST_PTP_HELPER_1_0", NULL},
{"GST_INSTALL_PLUGINS_HELPER", NULL},
{"KRB5CCNAME", NULL},
{"XKB_CONFIG_ROOT", NULL},
{"GIO_EXTRA_MODULES", 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);
}
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;
g_autoptr(GFile) app_dir_state = 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");
/* Yes, this is inconsistent with data, config and cache. However, using
* this path lets apps provide backwards-compatibility with older Flatpak
* versions by using `--persist=.local/state --unset-env=XDG_STATE_DIR`. */
app_dir_state = g_file_get_child (app_dir, ".local/state");
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);
flatpak_bwrap_set_env (bwrap, "XDG_STATE_HOME", flatpak_file_get_path_cached (app_dir_state), 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 (g_getenv ("XDG_STATE_HOME"))
flatpak_bwrap_set_env (bwrap, "HOST_XDG_STATE_HOME", g_getenv ("XDG_STATE_HOME"), TRUE);
}
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)
flatpak_bwrap_set_env (bwrap, var, val, TRUE);
else
flatpak_bwrap_unset_env (bwrap, var);
}
}
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);
}
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");
g_autoptr(GFile) state_dir = g_file_get_child (app_id_dir, ".local/state");
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;
if (!flatpak_mkdir_p (state_dir, cancellable, error))
return FALSE;
return TRUE;
}
struct JobData
{
char *job;
GMainLoop *main_loop;
};
static void
job_removed_cb (SystemdManager *manager,
guint32 id,
char *job,
char *unit,
char *result,
struct JobData *data)
{
if (strcmp (job, data->job) == 0)
g_main_loop_quit (data->main_loop);
}
static gchar *
systemd_unit_name_escape (const gchar *in)
{
/* Adapted from systemd source */
GString * const str = g_string_sized_new (strlen (in));
for (; *in; in++)
{
if (g_ascii_isalnum (*in) || *in == ':' || *in == '_' || *in == '.')
g_string_append_c (str, *in);
else
g_string_append_printf (str, "\\x%02x", *in);
}
return g_string_free (str, FALSE);
}
gboolean
flatpak_run_in_transient_unit (const char *appid, GError **error)
{
g_autoptr(GDBusConnection) conn = NULL;
g_autofree char *path = NULL;
g_autofree char *address = NULL;
g_autofree char *name = NULL;
g_autofree char *appid_escaped = NULL;
g_autofree char *job = NULL;
SystemdManager *manager = NULL;
GVariantBuilder builder;
GVariant *properties = NULL;
GVariant *aux = NULL;
guint32 pid;
GMainLoop *main_loop = NULL;
struct JobData data;
gboolean res = FALSE;
g_autoptr(GMainContextPopDefault) main_context = NULL;
path = g_strdup_printf ("/run/user/%d/systemd/private", getuid ());
if (!g_file_test (path, G_FILE_TEST_EXISTS))
return flatpak_fail_error (error, FLATPAK_ERROR_SETUP_FAILED,
_("No systemd user session available, cgroups not available"));
main_context = flatpak_main_context_new_default ();
main_loop = g_main_loop_new (main_context, FALSE);
address = g_strconcat ("unix:path=", path, NULL);
conn = g_dbus_connection_new_for_address_sync (address,
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
NULL,
NULL, error);
if (!conn)
goto out;
manager = systemd_manager_proxy_new_sync (conn,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
NULL,
"/org/freedesktop/systemd1",
NULL, error);
if (!manager)
goto out;
appid_escaped = systemd_unit_name_escape (appid);
name = g_strdup_printf ("app-flatpak-%s-%d.scope", appid_escaped, getpid ());
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sv)"));
pid = getpid ();
g_variant_builder_add (&builder, "(sv)",
"PIDs",
g_variant_new_fixed_array (G_VARIANT_TYPE ("u"),
&pid, 1, sizeof (guint32))
);
properties = g_variant_builder_end (&builder);
aux = g_variant_new_array (G_VARIANT_TYPE ("(sa(sv))"), NULL, 0);
if (!systemd_manager_call_start_transient_unit_sync (manager,
name,
"fail",
properties,
aux,
&job,
NULL,
error))
goto out;
data.job = job;
data.main_loop = main_loop;
g_signal_connect (manager, "job-removed", G_CALLBACK (job_removed_cb), &data);
g_main_loop_run (main_loop);
res = TRUE;
out:
if (main_loop)
g_main_loop_unref (main_loop);
if (manager)
g_object_unref (manager);
return res;
}
static void
add_font_path_args (FlatpakBwrap *bwrap)
{
g_autoptr(GString) xml_snippet = g_string_new ("");
gchar *path_build_tmp = NULL;
g_autoptr(GFile) user_font1 = NULL;
g_autoptr(GFile) user_font2 = NULL;
g_autoptr(GFile) user_font_cache = NULL;
g_auto(GStrv) system_cache_dirs = NULL;
gboolean found_cache = FALSE;
int i;
g_string_append (xml_snippet,
"<?xml version=\"1.0\"?>\n"
"<!DOCTYPE fontconfig SYSTEM \"urn:fontconfig:fonts.dtd\">\n"
"<fontconfig>\n");
if (g_file_test (SYSTEM_FONTS_DIR, G_FILE_TEST_EXISTS))
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", SYSTEM_FONTS_DIR, "/run/host/fonts",
NULL);
g_string_append_printf (xml_snippet,
"\t<remap-dir as-path=\"%s\">/run/host/fonts</remap-dir>\n",
SYSTEM_FONTS_DIR);
}
if (g_file_test ("/usr/local/share/fonts", G_FILE_TEST_EXISTS))
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", "/usr/local/share/fonts", "/run/host/local-fonts",
NULL);
g_string_append_printf (xml_snippet,
"\t<remap-dir as-path=\"%s\">/run/host/local-fonts</remap-dir>\n",
"/usr/local/share/fonts");
}
system_cache_dirs = g_strsplit (SYSTEM_FONT_CACHE_DIRS, ":", 0);
for (i = 0; system_cache_dirs[i] != NULL; i++)
{
if (g_file_test (system_cache_dirs[i], G_FILE_TEST_EXISTS))
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", system_cache_dirs[i], "/run/host/fonts-cache",
NULL);
found_cache = TRUE;
break;
}
}
if (!found_cache)
{
/* We ensure these directories are never writable, or fontconfig
will use them to write the default cache */
flatpak_bwrap_add_args (bwrap,
"--tmpfs", "/run/host/fonts-cache",
"--remount-ro", "/run/host/fonts-cache",
NULL);
}
path_build_tmp = g_build_filename (g_get_user_data_dir (), "fonts", NULL);
user_font1 = g_file_new_for_path (path_build_tmp);
g_clear_pointer (&path_build_tmp, g_free);
path_build_tmp = g_build_filename (g_get_home_dir (), ".fonts", NULL);
user_font2 = g_file_new_for_path (path_build_tmp);
g_clear_pointer (&path_build_tmp, g_free);
if (g_file_query_exists (user_font1, NULL))
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", flatpak_file_get_path_cached (user_font1), "/run/host/user-fonts",
NULL);
g_string_append_printf (xml_snippet,
"\t<remap-dir as-path=\"%s\">/run/host/user-fonts</remap-dir>\n",
flatpak_file_get_path_cached (user_font1));
}
else if (g_file_query_exists (user_font2, NULL))
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", flatpak_file_get_path_cached (user_font2), "/run/host/user-fonts",
NULL);
g_string_append_printf (xml_snippet,
"\t<remap-dir as-path=\"%s\">/run/host/user-fonts</remap-dir>\n",
flatpak_file_get_path_cached (user_font2));
}
path_build_tmp = g_build_filename (g_get_user_cache_dir (), "fontconfig", NULL);
user_font_cache = g_file_new_for_path (path_build_tmp);
g_clear_pointer (&path_build_tmp, g_free);
if (g_file_query_exists (user_font_cache, NULL))
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", flatpak_file_get_path_cached (user_font_cache), "/run/host/user-fonts-cache",
NULL);
}
else
{
/* We ensure these directories are never writable, or fontconfig
will use them to write the default cache */
flatpak_bwrap_add_args (bwrap,
"--tmpfs", "/run/host/user-fonts-cache",
"--remount-ro", "/run/host/user-fonts-cache",
NULL);
}
g_string_append (xml_snippet,
"</fontconfig>\n");
if (!flatpak_bwrap_add_args_data (bwrap, "font-dirs.xml", xml_snippet->str, xml_snippet->len, "/run/host/font-dirs.xml", NULL))
g_warning ("Unable to add fontconfig data snippet");
}
static void
add_icon_path_args (FlatpakBwrap *bwrap)
{
g_autofree gchar *user_icons_path = NULL;
g_autoptr(GFile) user_icons = NULL;
if (g_file_test ("/usr/share/icons", G_FILE_TEST_IS_DIR))
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", "/usr/share/icons", "/run/host/share/icons",
NULL);
}
user_icons_path = g_build_filename (g_get_user_data_dir (), "icons", NULL);
user_icons = g_file_new_for_path (user_icons_path);
if (g_file_query_exists (user_icons, NULL))
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", flatpak_file_get_path_cached (user_icons), "/run/host/user-share/icons",
NULL);
}
}
FlatpakContext *
flatpak_app_compute_permissions (GKeyFile *app_metadata,
GKeyFile *runtime_metadata,
GError **error)
{
g_autoptr(FlatpakContext) app_context = NULL;
app_context = flatpak_context_new ();
if (runtime_metadata != NULL)
{
if (!flatpak_context_load_metadata (app_context, runtime_metadata, error))
return NULL;
/* Don't inherit any permissions from the runtime, only things like env vars. */
flatpak_context_reset_permissions (app_context);
}
if (app_metadata != NULL &&
!flatpak_context_load_metadata (app_context, app_metadata, error))
return NULL;
return g_steal_pointer (&app_context);
}
#ifdef HAVE_DCONF
static void
add_dconf_key_to_keyfile (GKeyFile *keyfile,
DConfClient *client,
const char *key,
DConfReadFlags flags)
{
g_autofree char *group = g_path_get_dirname (key);
g_autofree char *k = g_path_get_basename (key);
GVariant *value = dconf_client_read_full (client, key, flags, NULL);
if (value)
{
g_autofree char *val = g_variant_print (value, TRUE);
g_key_file_set_value (keyfile, group + 1, k, val);
}
}
static void
add_dconf_dir_to_keyfile (GKeyFile *keyfile,
DConfClient *client,
const char *dir,
DConfReadFlags flags)
{
g_auto(GStrv) keys = NULL;
int i;
keys = dconf_client_list (client, dir, NULL);
for (i = 0; keys[i]; i++)
{
g_autofree char *k = g_strconcat (dir, keys[i], NULL);
if (dconf_is_dir (k, NULL))
add_dconf_dir_to_keyfile (keyfile, client, k, flags);
else if (dconf_is_key (k, NULL))
add_dconf_key_to_keyfile (keyfile, client, k, flags);
}
}
static void
add_dconf_locks_to_list (GString *s,
DConfClient *client,
const char *dir)
{
g_auto(GStrv) locks = NULL;
int i;
locks = dconf_client_list_locks (client, dir, NULL);
for (i = 0; locks[i]; i++)
{
g_string_append (s, locks[i]);
g_string_append_c (s, '\n');
}
}
#endif /* HAVE_DCONF */
static void
get_dconf_data (const char *app_id,
const char **paths,
const char *migrate_path,
char **defaults,
gsize *defaults_size,
char **values,
gsize *values_size,
char **locks,
gsize *locks_size)
{
#ifdef HAVE_DCONF
DConfClient *client = NULL;
g_autofree char *prefix = NULL;
#endif
g_autoptr(GKeyFile) defaults_data = NULL;
g_autoptr(GKeyFile) values_data = NULL;
g_autoptr(GString) locks_data = NULL;
defaults_data = g_key_file_new ();
values_data = g_key_file_new ();
locks_data = g_string_new ("");
#ifdef HAVE_DCONF
client = dconf_client_new ();
prefix = flatpak_dconf_path_for_app_id (app_id);
if (migrate_path)
{
g_info ("Add values in dir '%s', prefix is '%s'", migrate_path, prefix);
if (flatpak_dconf_path_is_similar (migrate_path, prefix))
add_dconf_dir_to_keyfile (values_data, client, migrate_path, DCONF_READ_USER_VALUE);
else
g_warning ("Ignoring D-Conf migrate-path setting %s", migrate_path);
}
g_info ("Add defaults in dir %s", prefix);
add_dconf_dir_to_keyfile (defaults_data, client, prefix, DCONF_READ_DEFAULT_VALUE);
g_info ("Add locks in dir %s", prefix);
add_dconf_locks_to_list (locks_data, client, prefix);
/* We allow extra paths for defaults and locks, but not for user values */
if (paths)
{
int i;
for (i = 0; paths[i]; i++)
{
if (dconf_is_dir (paths[i], NULL))
{
g_info ("Add defaults in dir %s", paths[i]);
add_dconf_dir_to_keyfile (defaults_data, client, paths[i], DCONF_READ_DEFAULT_VALUE);
g_info ("Add locks in dir %s", paths[i]);
add_dconf_locks_to_list (locks_data, client, paths[i]);
}
else if (dconf_is_key (paths[i], NULL))
{
g_info ("Add individual key %s", paths[i]);
add_dconf_key_to_keyfile (defaults_data, client, paths[i], DCONF_READ_DEFAULT_VALUE);
add_dconf_key_to_keyfile (values_data, client, paths[i], DCONF_READ_USER_VALUE);
}
else
{
g_warning ("Ignoring settings path '%s': neither dir nor key", paths[i]);
}
}
}
#endif
*defaults = g_key_file_to_data (defaults_data, defaults_size, NULL);
*values = g_key_file_to_data (values_data, values_size, NULL);
*locks_size = locks_data->len;
*locks = g_string_free (g_steal_pointer (&locks_data), FALSE);
#ifdef HAVE_DCONF
g_object_unref (client);
#endif
}
static gboolean
flatpak_run_add_dconf_args (FlatpakBwrap *bwrap,
const char *app_id,
GKeyFile *metakey,
GError **error)
{
g_auto(GStrv) paths = NULL;
g_autofree char *migrate_path = NULL;
g_autofree char *defaults = NULL;
g_autofree char *values = NULL;
g_autofree char *locks = NULL;
gsize defaults_size;
gsize values_size;
gsize locks_size;
if (metakey)
{
paths = g_key_file_get_string_list (metakey,
FLATPAK_METADATA_GROUP_DCONF,
FLATPAK_METADATA_KEY_DCONF_PATHS,
NULL, NULL);
migrate_path = g_key_file_get_string (metakey,
FLATPAK_METADATA_GROUP_DCONF,
FLATPAK_METADATA_KEY_DCONF_MIGRATE_PATH,
NULL);
}
get_dconf_data (app_id,
(const char **) paths,
migrate_path,
&defaults, &defaults_size,
&values, &values_size,
&locks, &locks_size);
if (defaults_size != 0 &&
!flatpak_bwrap_add_args_data (bwrap,
"dconf-defaults",
defaults, defaults_size,
"/etc/glib-2.0/settings/defaults",
error))
return FALSE;
if (locks_size != 0 &&
!flatpak_bwrap_add_args_data (bwrap,
"dconf-locks",
locks, locks_size,
"/etc/glib-2.0/settings/locks",
error))
return FALSE;
/* We do a one-time conversion of existing dconf settings to a keyfile.
* Only do that once the app stops requesting dconf access.
*/
if (migrate_path)
{
g_autofree char *filename = NULL;
filename = g_build_filename (g_get_home_dir (),
".var/app", app_id,
"config/glib-2.0/settings/keyfile",
NULL);
g_info ("writing D-Conf values to %s", filename);
if (values_size != 0 && !g_file_test (filename, G_FILE_TEST_EXISTS))
{
g_autofree char *dir = g_path_get_dirname (filename);
if (g_mkdir_with_parents (dir, 0700) == -1)
{
g_warning ("failed creating dirs for %s", filename);
return FALSE;
}
if (!g_file_set_contents (filename, values, values_size, error))
{
g_warning ("failed writing %s", filename);
return FALSE;
}
}
}
return TRUE;
}
gboolean
flatpak_run_add_app_info_args (FlatpakBwrap *bwrap,
GFile *app_files,
GFile *original_app_files,
GBytes *app_deploy_data,
const char *app_extensions,
GFile *runtime_files,
GFile *original_runtime_files,
GBytes *runtime_deploy_data,
const char *runtime_extensions,
const char *app_id,
const char *app_branch,
FlatpakDecomposed *runtime_ref,
GFile *app_id_dir,
FlatpakContext *final_app_context,
FlatpakContext *cmdline_context,
gboolean sandbox,
gboolean build,
gboolean devel,
char **app_info_path_out,
int instance_id_fd,
char **instance_id_host_dir_out,
GError **error)
{
g_autofree char *info_path = NULL;
g_autofree char *bwrapinfo_path = NULL;
int fd, fd2, fd3;
g_autoptr(GKeyFile) keyfile = NULL;
g_autofree char *runtime_path = NULL;
const char *group;
g_autofree char *instance_id = NULL;
glnx_autofd int lock_fd = -1;
g_autofree char *instance_id_host_dir = NULL;
g_autofree char *instance_id_sandbox_dir = NULL;
g_autofree char *instance_id_lock_file = NULL;
g_autofree char *arch = flatpak_decomposed_dup_arch (runtime_ref);
g_return_val_if_fail (app_id != NULL, FALSE);
instance_id = flatpak_instance_allocate_id (&instance_id_host_dir, &lock_fd);
if (instance_id == NULL)
return flatpak_fail_error (error, FLATPAK_ERROR_SETUP_FAILED, _("Unable to allocate instance id"));
instance_id_sandbox_dir = g_strdup_printf ("/run/flatpak/.flatpak/%s", instance_id);
instance_id_lock_file = g_build_filename (instance_id_sandbox_dir, ".ref", NULL);
flatpak_bwrap_add_args (bwrap,
"--ro-bind",
instance_id_host_dir,
instance_id_sandbox_dir,
"--lock-file",
instance_id_lock_file,
NULL);
flatpak_bwrap_add_runtime_dir_member (bwrap, ".flatpak");
/* Keep the .ref lock held until we've started bwrap to avoid races */
flatpak_bwrap_add_noinherit_fd (bwrap, glnx_steal_fd (&lock_fd));
info_path = g_build_filename (instance_id_host_dir, "info", NULL);
keyfile = g_key_file_new ();
if (original_app_files)
group = FLATPAK_METADATA_GROUP_APPLICATION;
else
group = FLATPAK_METADATA_GROUP_RUNTIME;
g_key_file_set_string (keyfile, group, FLATPAK_METADATA_KEY_NAME, app_id);
g_key_file_set_string (keyfile, group, FLATPAK_METADATA_KEY_RUNTIME,
flatpak_decomposed_get_ref (runtime_ref));
g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_INSTANCE_ID, instance_id);
if (app_id_dir)
{
g_autofree char *instance_path = g_file_get_path (app_id_dir);
g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_INSTANCE_PATH, instance_path);
}
if (app_files)
{
g_autofree char *app_path = g_file_get_path (app_files);
g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_APP_PATH, app_path);
}
if (original_app_files != NULL && original_app_files != app_files)
{
g_autofree char *app_path = g_file_get_path (original_app_files);
g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_ORIGINAL_APP_PATH, app_path);
}
if (app_deploy_data)
g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_APP_COMMIT, flatpak_deploy_data_get_commit (app_deploy_data));
if (app_extensions && *app_extensions != 0)
g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_APP_EXTENSIONS, app_extensions);
runtime_path = g_file_get_path (runtime_files);
g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_RUNTIME_PATH, runtime_path);
if (runtime_files != original_runtime_files)
{
g_autofree char *path = g_file_get_path (original_runtime_files);
g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_ORIGINAL_RUNTIME_PATH, path);
}
if (runtime_deploy_data)
g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_RUNTIME_COMMIT, flatpak_deploy_data_get_commit (runtime_deploy_data));
if (runtime_extensions && *runtime_extensions != 0)
g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_RUNTIME_EXTENSIONS, runtime_extensions);
if (app_branch != NULL)
g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_BRANCH, app_branch);
g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_ARCH, arch);
g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_FLATPAK_VERSION, PACKAGE_VERSION);
if ((final_app_context->sockets & FLATPAK_CONTEXT_SOCKET_SESSION_BUS) == 0)
g_key_file_set_boolean (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_SESSION_BUS_PROXY, TRUE);
if ((final_app_context->sockets & FLATPAK_CONTEXT_SOCKET_SYSTEM_BUS) == 0)
g_key_file_set_boolean (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_SYSTEM_BUS_PROXY, TRUE);
if (sandbox)
g_key_file_set_boolean (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_SANDBOX, TRUE);
if (build)
g_key_file_set_boolean (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_BUILD, TRUE);
if (devel)
g_key_file_set_boolean (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_DEVEL, TRUE);
if (cmdline_context)
{
g_autoptr(GPtrArray) cmdline_args = g_ptr_array_new_with_free_func (g_free);
flatpak_context_to_args (cmdline_context, cmdline_args);
if (cmdline_args->len > 0)
{
g_key_file_set_string_list (keyfile, FLATPAK_METADATA_GROUP_INSTANCE,
FLATPAK_METADATA_KEY_EXTRA_ARGS,
(const char * const *) cmdline_args->pdata,
cmdline_args->len);
}
}
flatpak_context_save_metadata (final_app_context, TRUE, keyfile);
if (!g_key_file_save_to_file (keyfile, info_path, error))
return FALSE;
/* We want to create a file on /.flatpak-info that the app cannot modify, which
we do by creating a read-only bind mount. This way one can openat()
/proc/$pid/root, and if that succeeds use openat via that to find the
unfakable .flatpak-info file. However, there is a tiny race in that if
you manage to open /proc/$pid/root, but then the pid dies, then
every mount but the root is unmounted in the namespace, so the
.flatpak-info will be empty. We fix this by first creating a real file
with the real info in, then bind-mounting on top of that, the same info.
This way even if the bind-mount is unmounted we can find the real data.
*/
fd = open (info_path, O_RDONLY);
if (fd == -1)
{
int errsv = errno;
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
_("Failed to open flatpak-info file: %s"), g_strerror (errsv));
return FALSE;
}
fd2 = open (info_path, O_RDONLY);
if (fd2 == -1)
{
close (fd);
int errsv = errno;
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
_("Failed to open flatpak-info file: %s"), g_strerror (errsv));
return FALSE;
}
flatpak_bwrap_add_args (bwrap, "--perms", "0600", NULL);
flatpak_bwrap_add_args_data_fd (bwrap,
"--file", fd, "/.flatpak-info");
flatpak_bwrap_add_args_data_fd (bwrap,
"--ro-bind-data", fd2, "/.flatpak-info");
/* Tell the application that it's running under Flatpak in a generic way. */
flatpak_bwrap_add_args (bwrap,
"--setenv", "container", "flatpak",
NULL);
if (!flatpak_bwrap_add_args_data (bwrap,
"container-manager",
"flatpak\n", -1,
"/run/host/container-manager",
error))
return FALSE;
bwrapinfo_path = g_build_filename (instance_id_host_dir, "bwrapinfo.json", NULL);
fd3 = open (bwrapinfo_path, O_RDWR | O_CREAT, 0644);
if (fd3 == -1)
{
close (fd);
close (fd2);
int errsv = errno;
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
_("Failed to open bwrapinfo.json file: %s"), g_strerror (errsv));
return FALSE;
}
/* NOTE: It is important that this takes place after bwrapinfo.json is created,
otherwise start notifications in the portal may not work. */
if (instance_id_fd != -1)
{
gsize instance_id_position = 0;
gsize instance_id_size = strlen (instance_id);
while (instance_id_size > 0)
{
gssize bytes_written = write (instance_id_fd, instance_id + instance_id_position, instance_id_size);
if (G_UNLIKELY (bytes_written <= 0))
{
int errsv = bytes_written == -1 ? errno : ENOSPC;
if (errsv == EINTR)
continue;
close (fd);
close (fd2);
close (fd3);
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
_("Failed to write to instance id fd: %s"), g_strerror (errsv));
return FALSE;
}
instance_id_position += bytes_written;
instance_id_size -= bytes_written;
}
close (instance_id_fd);
}
flatpak_bwrap_add_args_data_fd (bwrap, "--info-fd", fd3, NULL);
if (app_info_path_out != NULL)
*app_info_path_out = g_strdup_printf ("/proc/self/fd/%d", fd);
if (instance_id_host_dir_out != NULL)
*instance_id_host_dir_out = g_steal_pointer (&instance_id_host_dir);
return TRUE;
}
static void
add_tzdata_args (FlatpakBwrap *bwrap,
GFile *runtime_files)
{
g_autofree char *raw_timezone = flatpak_get_timezone ();
g_autofree char *timezone_content = g_strdup_printf ("%s\n", raw_timezone);
g_autofree char *localtime_content = g_strconcat ("../usr/share/zoneinfo/", raw_timezone, NULL);
g_autoptr(GFile) runtime_zoneinfo = NULL;
if (runtime_files)
runtime_zoneinfo = g_file_resolve_relative_path (runtime_files, "share/zoneinfo");
/* Check for runtime /usr/share/zoneinfo */
if (runtime_zoneinfo != NULL && g_file_query_exists (runtime_zoneinfo, NULL))
{
/* Check for host /usr/share/zoneinfo */
if (g_file_test ("/usr/share/zoneinfo", G_FILE_TEST_IS_DIR))
{
/* Here we assume the host timezone file exist in the host data */
flatpak_bwrap_add_args (bwrap,
"--ro-bind", "/usr/share/zoneinfo", "/usr/share/zoneinfo",
"--symlink", localtime_content, "/etc/localtime",
NULL);
}
else
{
g_autoptr(GFile) runtime_tzfile = g_file_resolve_relative_path (runtime_zoneinfo, raw_timezone);
/* Check if host timezone file exist in the runtime tzdata */
if (g_file_query_exists (runtime_tzfile, NULL))
flatpak_bwrap_add_args (bwrap,
"--symlink", localtime_content, "/etc/localtime",
NULL);
}
}
flatpak_bwrap_add_args_data (bwrap, "timezone",
timezone_content, -1, "/etc/timezone",
NULL);
}
static void
add_monitor_path_args (gboolean use_session_helper,
FlatpakBwrap *bwrap)
{
g_autoptr(AutoFlatpakSessionHelper) session_helper = NULL;
g_autofree char *monitor_path = NULL;
g_autofree char *pkcs11_socket_path = NULL;
g_autoptr(GVariant) session_data = NULL;
if (use_session_helper)
{
session_helper =
flatpak_session_helper_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
FLATPAK_SESSION_HELPER_BUS_NAME,
FLATPAK_SESSION_HELPER_PATH,
NULL, NULL);
}
if (session_helper &&
flatpak_session_helper_call_request_session_sync (session_helper,
&session_data,
NULL, NULL))
{
if (g_variant_lookup (session_data, "path", "s", &monitor_path))
flatpak_bwrap_add_args (bwrap,
"--ro-bind", monitor_path, "/run/host/monitor",
"--symlink", "/run/host/monitor/resolv.conf", "/etc/resolv.conf",
"--symlink", "/run/host/monitor/host.conf", "/etc/host.conf",
"--symlink", "/run/host/monitor/hosts", "/etc/hosts",
"--symlink", "/run/host/monitor/gai.conf", "/etc/gai.conf",
NULL);
if (g_variant_lookup (session_data, "pkcs11-socket", "s", &pkcs11_socket_path))
{
static const char sandbox_pkcs11_socket_path[] = "/run/flatpak/p11-kit/pkcs11";
const char *trusted_module_contents =
"# This overrides the runtime p11-kit-trusted module with a client one talking to the trust module on the host\n"
"module: p11-kit-client.so\n";
if (flatpak_bwrap_add_args_data (bwrap, "p11-kit-trust.module",
trusted_module_contents, -1,
"/etc/pkcs11/modules/p11-kit-trust.module", NULL))
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", pkcs11_socket_path, sandbox_pkcs11_socket_path,
NULL);
flatpak_bwrap_unset_env (bwrap, "P11_KIT_SERVER_ADDRESS");
flatpak_bwrap_add_runtime_dir_member (bwrap, "p11-kit");
}
}
}
else
{
if (g_file_test ("/etc/resolv.conf", G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap,
"--ro-bind", "/etc/resolv.conf", "/etc/resolv.conf",
NULL);
if (g_file_test ("/etc/host.conf", G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap,
"--ro-bind", "/etc/host.conf", "/etc/host.conf",
NULL);
if (g_file_test ("/etc/hosts", G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap,
"--ro-bind", "/etc/hosts", "/etc/hosts",
NULL);
if (g_file_test ("/etc/gai.conf", G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap,
"--ro-bind", "/etc/gai.conf", "/etc/gai.conf",
NULL);
}
}
static void
add_document_portal_args (FlatpakBwrap *bwrap,
const char *app_id,
char **out_mount_path)
{
g_autoptr(GDBusConnection) session_bus = NULL;
g_autofree char *doc_mount_path = NULL;
session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
if (session_bus)
{
g_autoptr(GError) local_error = NULL;
g_autoptr(GDBusMessage) reply = NULL;
g_autoptr(GDBusMessage) msg =
g_dbus_message_new_method_call ("org.freedesktop.portal.Documents",
"/org/freedesktop/portal/documents",
"org.freedesktop.portal.Documents",
"GetMountPoint");
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_info ("Document portal not available, not mounting /run/flatpak/doc");
else
g_message ("Can't get document portal: %s", local_error->message);
}
else
{
static const char dst_path[] = "/run/flatpak/doc";
g_autofree char *src_path = NULL;
g_variant_get (g_dbus_message_get_body (reply),
"(^ay)", &doc_mount_path);
src_path = g_strdup_printf ("%s/by-app/%s",
doc_mount_path, app_id);
flatpak_bwrap_add_args (bwrap, "--bind", src_path, dst_path, NULL);
flatpak_bwrap_add_runtime_dir_member (bwrap, "doc");
}
}
}
*out_mount_path = g_steal_pointer (&doc_mount_path);
}
#ifdef ENABLE_SECCOMP
static const uint32_t seccomp_x86_64_extra_arches[] = { SCMP_ARCH_X86, 0, };
#ifdef SCMP_ARCH_AARCH64
static const uint32_t seccomp_aarch64_extra_arches[] = { SCMP_ARCH_ARM, 0 };
#endif
/*
* @negative_errno: Result code as returned by libseccomp functions
*
* Translate a libseccomp error code into an error message. libseccomp
* mostly returns negative `errno` values such as `-ENOMEM`, but some
* standard `errno` values are used for non-standard purposes where their
* `strerror()` would be misleading.
*
* Returns: a string version of @negative_errno if possible
*/
static const char *
flatpak_seccomp_strerror (int negative_errno)
{
g_return_val_if_fail (negative_errno < 0, "Non-negative error value from libseccomp?");
g_return_val_if_fail (negative_errno > INT_MIN, "Out of range error value from libseccomp?");
switch (negative_errno)
{
case -EDOM:
return "Architecture specific failure";
case -EFAULT:
return "Internal libseccomp failure (unknown syscall?)";
case -ECANCELED:
return "System failure beyond the control of libseccomp";
}
/* e.g. -ENOMEM: the result of strerror() is good enough */
return g_strerror (-negative_errno);
}
static inline void
cleanup_seccomp (void *p)
{
scmp_filter_ctx *pp = (scmp_filter_ctx *) p;
if (*pp)
seccomp_release (*pp);
}
static gboolean
setup_seccomp (FlatpakBwrap *bwrap,
const char *arch,
gulong allowed_personality,
FlatpakRunFlags run_flags,
GError **error)
{
gboolean multiarch = (run_flags & FLATPAK_RUN_FLAG_MULTIARCH) != 0;
gboolean devel = (run_flags & FLATPAK_RUN_FLAG_DEVEL) != 0;
__attribute__((cleanup (cleanup_seccomp))) scmp_filter_ctx seccomp = NULL;
/**** BEGIN NOTE ON CODE SHARING
*
* There are today a number of different Linux container
* implementations. That will likely continue for long into the
* future. But we can still try to share code, and it's important
* to do so because it affects what library and application writers
* can do, and we should support code portability between different
* container tools.
*
* This syscall blocklist is copied from linux-user-chroot, which was in turn
* clearly influenced by the Sandstorm.io blocklist.
*
* If you make any changes here, I suggest sending the changes along
* to other sandbox maintainers. Using the libseccomp list is also
* an appropriate venue:
* https://groups.google.com/forum/#!forum/libseccomp
*
* A non-exhaustive list of links to container tooling that might
* want to share this blocklist:
*
* https://github.com/sandstorm-io/sandstorm
* in src/sandstorm/supervisor.c++
* https://github.com/flatpak/flatpak.git
* in common/flatpak-run.c
* https://git.gnome.org/browse/linux-user-chroot
* in src/setup-seccomp.c
*
* Other useful resources:
* https://github.com/systemd/systemd/blob/HEAD/src/shared/seccomp-util.c
* https://github.com/moby/moby/blob/HEAD/profiles/seccomp/default.json
*
**** END NOTE ON CODE SHARING
*/
struct
{
int scall;
int errnum;
struct scmp_arg_cmp *arg;
} syscall_blocklist[] = {
/* Block dmesg */
{SCMP_SYS (syslog), EPERM},
/* Useless old syscall */
{SCMP_SYS (uselib), EPERM},
/* Don't allow disabling accounting */
{SCMP_SYS (acct), EPERM},
/* Don't allow reading current quota use */
{SCMP_SYS (quotactl), EPERM},
/* Don't allow access to the kernel keyring */
{SCMP_SYS (add_key), EPERM},
{SCMP_SYS (keyctl), EPERM},
{SCMP_SYS (request_key), EPERM},
/* Scary VM/NUMA ops */
{SCMP_SYS (move_pages), EPERM},
{SCMP_SYS (mbind), EPERM},
{SCMP_SYS (get_mempolicy), EPERM},
{SCMP_SYS (set_mempolicy), EPERM},
{SCMP_SYS (migrate_pages), EPERM},
/* Don't allow subnamespace setups: */
{SCMP_SYS (unshare), EPERM},
{SCMP_SYS (setns), EPERM},
{SCMP_SYS (mount), EPERM},
{SCMP_SYS (umount), EPERM},
{SCMP_SYS (umount2), EPERM},
{SCMP_SYS (pivot_root), EPERM},
{SCMP_SYS (chroot), EPERM},
#if defined(__s390__) || defined(__s390x__) || defined(__CRIS__)
/* Architectures with CONFIG_CLONE_BACKWARDS2: the child stack
* and flags arguments are reversed so the flags come second */
{SCMP_SYS (clone), EPERM, &SCMP_A1 (SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER)},
#else
/* Normally the flags come first */
{SCMP_SYS (clone), EPERM, &SCMP_A0 (SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER)},
#endif
/* Don't allow faking input to the controlling tty (CVE-2017-5226) */
{SCMP_SYS (ioctl), EPERM, &SCMP_A1 (SCMP_CMP_MASKED_EQ, 0xFFFFFFFFu, (int) TIOCSTI)},
/* seccomp can't look into clone3()'s struct clone_args to check whether
* the flags are OK, so we have no choice but to block clone3().
* Return ENOSYS so user-space will fall back to clone().
* (GHSA-67h7-w3jq-vh4q; see also https://github.com/moby/moby/commit/9f6b562d) */
{SCMP_SYS (clone3), ENOSYS},
/* New mount manipulation APIs can also change our VFS. There's no
* legitimate reason to do these in the sandbox, so block all of them
* rather than thinking about which ones might be dangerous.
* (GHSA-67h7-w3jq-vh4q) */
{SCMP_SYS (open_tree), ENOSYS},
{SCMP_SYS (move_mount), ENOSYS},
{SCMP_SYS (fsopen), ENOSYS},
{SCMP_SYS (fsconfig), ENOSYS},
{SCMP_SYS (fsmount), ENOSYS},
{SCMP_SYS (fspick), ENOSYS},
{SCMP_SYS (mount_setattr), ENOSYS},
};
struct
{
int scall;
int errnum;
struct scmp_arg_cmp *arg;
} syscall_nondevel_blocklist[] = {
/* Profiling operations; we expect these to be done by tools from outside
* the sandbox. In particular perf has been the source of many CVEs.
*/
{SCMP_SYS (perf_event_open), EPERM},
/* Don't allow you to switch to bsd emulation or whatnot */
{SCMP_SYS (personality), EPERM, &SCMP_A0 (SCMP_CMP_NE, allowed_personality)},
{SCMP_SYS (ptrace), EPERM}
};
/* Blocklist all but unix, inet, inet6 and netlink */
struct
{
int family;
FlatpakRunFlags flags_mask;
} socket_family_allowlist[] = {
/* NOTE: Keep in numerical order */
{ AF_UNSPEC, 0 },
{ AF_LOCAL, 0 },
{ AF_INET, 0 },
{ AF_INET6, 0 },
{ AF_NETLINK, 0 },
{ AF_CAN, FLATPAK_RUN_FLAG_CANBUS },
{ AF_BLUETOOTH, FLATPAK_RUN_FLAG_BLUETOOTH },
};
int last_allowed_family;
int i, r;
g_auto(GLnxTmpfile) seccomp_tmpf = { 0, };
seccomp = seccomp_init (SCMP_ACT_ALLOW);
if (!seccomp)
return flatpak_fail_error (error, FLATPAK_ERROR_SETUP_FAILED, _("Initialize seccomp failed"));
if (arch != NULL)
{
uint32_t arch_id = 0;
const uint32_t *extra_arches = NULL;
if (strcmp (arch, "i386") == 0)
{
arch_id = SCMP_ARCH_X86;
}
else if (strcmp (arch, "x86_64") == 0)
{
arch_id = SCMP_ARCH_X86_64;
extra_arches = seccomp_x86_64_extra_arches;
}
else if (strcmp (arch, "arm") == 0)
{
arch_id = SCMP_ARCH_ARM;
}
#ifdef SCMP_ARCH_AARCH64
else if (strcmp (arch, "aarch64") == 0)
{
arch_id = SCMP_ARCH_AARCH64;
extra_arches = seccomp_aarch64_extra_arches;
}
#endif
/* We only really need to handle arches on multiarch systems.
* If only one arch is supported the default is fine */
if (arch_id != 0)
{
/* This *adds* the target arch, instead of replacing the
native one. This is not ideal, because we'd like to only
allow the target arch, but we can't really disallow the
native arch at this point, because then bubblewrap
couldn't continue running. */
r = seccomp_arch_add (seccomp, arch_id);
if (r < 0 && r != -EEXIST)
return flatpak_fail_error (error, FLATPAK_ERROR_SETUP_FAILED, _("Failed to add architecture to seccomp filter: %s"), flatpak_seccomp_strerror (r));
if (multiarch && extra_arches != NULL)
{
for (i = 0; extra_arches[i] != 0; i++)
{
r = seccomp_arch_add (seccomp, extra_arches[i]);
if (r < 0 && r != -EEXIST)
return flatpak_fail_error (error, FLATPAK_ERROR_SETUP_FAILED, _("Failed to add multiarch architecture to seccomp filter: %s"), flatpak_seccomp_strerror (r));
}
}
}
}
/* TODO: Should we filter the kernel keyring syscalls in some way?
* We do want them to be used by desktop apps, but they could also perhaps
* leak system stuff or secrets from other apps.
*/
for (i = 0; i < G_N_ELEMENTS (syscall_blocklist); i++)
{
int scall = syscall_blocklist[i].scall;
int errnum = syscall_blocklist[i].errnum;
g_return_val_if_fail (errnum == EPERM || errnum == ENOSYS, FALSE);
if (syscall_blocklist[i].arg)
r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (errnum), scall, 1, *syscall_blocklist[i].arg);
else
r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (errnum), scall, 0);
/* EFAULT means "internal libseccomp error", but in practice we get
* this for syscall numbers added via flatpak-syscalls-private.h
* when trying to filter them on a non-native architecture, because
* libseccomp cannot map the syscall number to a name and back to a
* number for the non-native architecture. */
if (r == -EFAULT)
g_debug ("Unable to block syscall %d: syscall not known to libseccomp?",
scall);
else if (r < 0)
return flatpak_fail_error (error, FLATPAK_ERROR_SETUP_FAILED, _("Failed to block syscall %d: %s"), scall, flatpak_seccomp_strerror (r));
}
if (!multiarch)
{
/* modify_ldt is a historic source of interesting information leaks,
* so it's disabled as a hardening measure.
* However, it is required to run old 16-bit applications
* as well as some Wine patches, so it's allowed in multiarch. */
int scall = SCMP_SYS (modify_ldt);
r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 0);
/* See above for the meaning of EFAULT. */
if (r == -EFAULT)
g_debug ("Unable to block syscall %d: syscall not known to libseccomp?",
scall);
else if (r < 0)
return flatpak_fail_error (error, FLATPAK_ERROR_SETUP_FAILED, _("Failed to block syscall %d: %s"), scall, flatpak_seccomp_strerror (r));
}
if (!devel)
{
for (i = 0; i < G_N_ELEMENTS (syscall_nondevel_blocklist); i++)
{
int scall = syscall_nondevel_blocklist[i].scall;
int errnum = syscall_nondevel_blocklist[i].errnum;
g_return_val_if_fail (errnum == EPERM || errnum == ENOSYS, FALSE);
if (syscall_nondevel_blocklist[i].arg)
r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (errnum), scall, 1, *syscall_nondevel_blocklist[i].arg);
else
r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (errnum), scall, 0);
/* See above for the meaning of EFAULT. */
if (r == -EFAULT)
g_debug ("Unable to block syscall %d: syscall not known to libseccomp?",
scall);
else if (r < 0)
return flatpak_fail_error (error, FLATPAK_ERROR_SETUP_FAILED, _("Failed to block syscall %d: %s"), scall, flatpak_seccomp_strerror (r));
}
}
/* Socket filtering doesn't work on e.g. i386, so ignore failures here
* However, we need to user seccomp_rule_add_exact to avoid libseccomp doing
* something else: https://github.com/seccomp/libseccomp/issues/8 */
last_allowed_family = -1;
for (i = 0; i < G_N_ELEMENTS (socket_family_allowlist); i++)
{
int family = socket_family_allowlist[i].family;
int disallowed;
if (socket_family_allowlist[i].flags_mask != 0 &&
(socket_family_allowlist[i].flags_mask & run_flags) != socket_family_allowlist[i].flags_mask)
continue;
for (disallowed = last_allowed_family + 1; disallowed < family; disallowed++)
{
/* Blocklist the in-between valid families */
seccomp_rule_add_exact (seccomp, SCMP_ACT_ERRNO (EAFNOSUPPORT), SCMP_SYS (socket), 1, SCMP_A0 (SCMP_CMP_EQ, disallowed));
}
last_allowed_family = family;
}
/* Blocklist the rest */
seccomp_rule_add_exact (seccomp, SCMP_ACT_ERRNO (EAFNOSUPPORT), SCMP_SYS (socket), 1, SCMP_A0 (SCMP_CMP_GE, last_allowed_family + 1));
if (!glnx_open_anonymous_tmpfile_full (O_RDWR | O_CLOEXEC, "/tmp", &seccomp_tmpf, error))
return FALSE;
r = seccomp_export_bpf (seccomp, seccomp_tmpf.fd);
if (r != 0)
return flatpak_fail_error (error, FLATPAK_ERROR_SETUP_FAILED, _("Failed to export bpf: %s"), flatpak_seccomp_strerror (r));
lseek (seccomp_tmpf.fd, 0, SEEK_SET);
flatpak_bwrap_add_args_data_fd (bwrap,
"--seccomp", glnx_steal_fd (&seccomp_tmpf.fd), NULL);
return TRUE;
}
#endif
static void
flatpak_run_setup_usr_links (FlatpakBwrap *bwrap,
GFile *runtime_files,
const char *sysroot)
{
int i;
if (runtime_files == NULL)
return;
for (i = 0; flatpak_abs_usrmerged_dirs[i] != NULL; i++)
{
const char *subdir = flatpak_abs_usrmerged_dirs[i];
g_autoptr(GFile) runtime_subdir = NULL;
g_assert (subdir[0] == '/');
/* Skip the '/' when using as a subdirectory of the runtime */
runtime_subdir = g_file_get_child (runtime_files, subdir + 1);
if (g_file_query_exists (runtime_subdir, NULL))
{
g_autofree char *link = g_strconcat ("usr", subdir, NULL);
g_autofree char *create = NULL;
if (sysroot != NULL)
create = g_strconcat (sysroot, subdir, NULL);
else
create = g_strdup (subdir);
flatpak_bwrap_add_args (bwrap,
"--symlink", link, create,
NULL);
}
else
{
g_info ("%s does not exist",
flatpak_file_get_path_cached (runtime_subdir));
}
}
}
gboolean
flatpak_run_setup_base_argv (FlatpakBwrap *bwrap,
GFile *runtime_files,
GFile *app_id_dir,
const char *arch,
FlatpakRunFlags flags,
GError **error)
{
g_autofree char *run_dir = NULL;
g_autofree char *passwd_contents = NULL;
g_autoptr(GString) group_contents = NULL;
const char *pkcs11_conf_contents = NULL;
struct group *g;
gulong pers;
gid_t gid = getgid ();
g_autoptr(GFile) etc = NULL;
run_dir = g_strdup_printf ("/run/user/%d", getuid ());
passwd_contents = g_strdup_printf ("%s:x:%d:%d:%s:%s:%s\n"
"nfsnobody:x:65534:65534:Unmapped user:/:/sbin/nologin\n",
g_get_user_name (),
getuid (), gid,
g_get_real_name (),
g_get_home_dir (),
DEFAULT_SHELL);
group_contents = g_string_new ("");
g = getgrgid (gid);
/* if NULL, the primary group is not known outside the container, so
* it might as well stay unknown inside the container... */
if (g != NULL)
g_string_append_printf (group_contents, "%s:x:%d:%s\n",
g->gr_name, gid, g_get_user_name ());
g_string_append (group_contents, "nfsnobody:x:65534:\n");
pkcs11_conf_contents =
"# Disable user pkcs11 config, because the host modules don't work in the runtime\n"
"user-config: none\n";
if ((flags & FLATPAK_RUN_FLAG_NO_PROC) == 0)
flatpak_bwrap_add_args (bwrap,
"--proc", "/proc",
NULL);
if (!(flags & FLATPAK_RUN_FLAG_PARENT_SHARE_PIDS))
flatpak_bwrap_add_arg (bwrap, "--unshare-pid");
flatpak_bwrap_add_args (bwrap,
"--dir", "/tmp",
"--dir", "/var/tmp",
"--dir", "/run/host",
"--perms", "0700", "--dir", run_dir,
"--setenv", "XDG_RUNTIME_DIR", run_dir,
"--symlink", "../run", "/var/run",
"--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",
"--ro-bind-try", "/proc/self/ns/user", "/run/.userns",
/* glib uses this like /etc/timezone */
"--symlink", "/etc/timezone", "/var/db/zoneinfo",
NULL);
if (flags & FLATPAK_RUN_FLAG_DIE_WITH_PARENT)
flatpak_bwrap_add_args (bwrap,
"--die-with-parent",
NULL);
if (flags & FLATPAK_RUN_FLAG_WRITABLE_ETC)
flatpak_bwrap_add_args (bwrap,
"--dir", "/usr/etc",
"--symlink", "usr/etc", "/etc",
NULL);
if (!flatpak_bwrap_add_args_data (bwrap, "passwd", passwd_contents, -1, "/etc/passwd", error))
return FALSE;
if (!flatpak_bwrap_add_args_data (bwrap, "group", group_contents->str, -1, "/etc/group", error))
return FALSE;
if (!flatpak_bwrap_add_args_data (bwrap, "pkcs11.conf", pkcs11_conf_contents, -1, "/etc/pkcs11/pkcs11.conf", error))
return FALSE;
if (g_file_test ("/etc/machine-id", G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap, "--ro-bind", "/etc/machine-id", "/etc/machine-id", NULL);
else if (g_file_test ("/var/lib/dbus/machine-id", G_FILE_TEST_EXISTS))
flatpak_bwrap_add_args (bwrap, "--ro-bind", "/var/lib/dbus/machine-id", "/etc/machine-id", NULL);
if (runtime_files)
etc = g_file_get_child (runtime_files, "etc");
if (etc != NULL &&
(flags & FLATPAK_RUN_FLAG_WRITABLE_ETC) == 0 &&
g_file_query_exists (etc, NULL))
{
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
struct dirent *dent;
gboolean inited;
inited = glnx_dirfd_iterator_init_at (AT_FDCWD, flatpak_file_get_path_cached (etc), FALSE, &dfd_iter, NULL);
while (inited)
{
g_autofree char *src = NULL;
g_autofree char *dest = NULL;
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, NULL, NULL) || dent == NULL)
break;
if (strcmp (dent->d_name, "passwd") == 0 ||
strcmp (dent->d_name, "group") == 0 ||
strcmp (dent->d_name, "machine-id") == 0 ||
strcmp (dent->d_name, "resolv.conf") == 0 ||
strcmp (dent->d_name, "host.conf") == 0 ||
strcmp (dent->d_name, "hosts") == 0 ||
strcmp (dent->d_name, "gai.conf") == 0 ||
strcmp (dent->d_name, "localtime") == 0 ||
strcmp (dent->d_name, "timezone") == 0 ||
strcmp (dent->d_name, "pkcs11") == 0)
continue;
src = g_build_filename (flatpak_file_get_path_cached (etc), dent->d_name, NULL);
dest = g_build_filename ("/etc", dent->d_name, NULL);
if (dent->d_type == DT_LNK)
{
g_autofree char *target = NULL;
target = glnx_readlinkat_malloc (dfd_iter.fd, dent->d_name,
NULL, error);
if (target == NULL)
return FALSE;
flatpak_bwrap_add_args (bwrap, "--symlink", target, dest, NULL);
}
else
{
flatpak_bwrap_add_args (bwrap, "--ro-bind", src, dest, NULL);
}
}
}
if (app_id_dir != NULL)
{
g_autoptr(GFile) app_cache_dir = g_file_get_child (app_id_dir, "cache");
g_autoptr(GFile) app_tmp_dir = g_file_get_child (app_cache_dir, "tmp");
g_autoptr(GFile) app_data_dir = g_file_get_child (app_id_dir, "data");
g_autoptr(GFile) app_config_dir = g_file_get_child (app_id_dir, "config");
flatpak_bwrap_add_args (bwrap,
/* These are nice to have as a fixed path */
"--bind", flatpak_file_get_path_cached (app_cache_dir), "/var/cache",
"--bind", flatpak_file_get_path_cached (app_data_dir), "/var/data",
"--bind", flatpak_file_get_path_cached (app_config_dir), "/var/config",
"--bind", flatpak_file_get_path_cached (app_tmp_dir), "/var/tmp",
NULL);
}
flatpak_run_setup_usr_links (bwrap, runtime_files, NULL);
add_tzdata_args (bwrap, runtime_files);
pers = PER_LINUX;
if ((flags & FLATPAK_RUN_FLAG_SET_PERSONALITY) &&
flatpak_is_linux32_arch (arch))
{
g_info ("Setting personality linux32");
pers = PER_LINUX32;
}
/* Always set the personallity, and clear all weird flags */
personality (pers);
#ifdef ENABLE_SECCOMP
if (!setup_seccomp (bwrap, arch, pers, flags, error))
return FALSE;
#endif
if ((flags & FLATPAK_RUN_FLAG_WRITABLE_ETC) == 0)
add_monitor_path_args ((flags & FLATPAK_RUN_FLAG_NO_SESSION_HELPER) == 0, bwrap);
return TRUE;
}
static gboolean
forward_file (XdpDbusDocuments *documents,
const char *app_id,
const char *file,
char **out_doc_id,
GError **error)
{
int fd, fd_id;
g_autofree char *doc_id = NULL;
g_autoptr(GUnixFDList) fd_list = NULL;
const char *perms[] = { "read", "write", NULL };
fd = open (file, O_PATH | O_CLOEXEC);
if (fd == -1)
return flatpak_fail (error, _("Failed to open %s"), file);
fd_list = g_unix_fd_list_new ();
fd_id = g_unix_fd_list_append (fd_list, fd, error);
close (fd);
if (!xdp_dbus_documents_call_add_sync (documents,
g_variant_new ("h", fd_id),
TRUE, /* reuse */
FALSE, /* not persistent */
fd_list,
&doc_id,
NULL,
NULL,
error))
{
if (error)
g_dbus_error_strip_remote_error (*error);
return FALSE;
}
if (!xdp_dbus_documents_call_grant_permissions_sync (documents,
doc_id,
app_id,
perms,
NULL,
error))
{
if (error)
g_dbus_error_strip_remote_error (*error);
return FALSE;
}
*out_doc_id = g_steal_pointer (&doc_id);
return TRUE;
}
static gboolean
add_rest_args (FlatpakBwrap *bwrap,
const char *app_id,
FlatpakExports *exports,
gboolean file_forwarding,
const char *doc_mount_path,
char *args[],
int n_args,
GError **error)
{
g_autoptr(AutoXdpDbusDocuments) documents = NULL;
gboolean forwarding = FALSE;
gboolean forwarding_uri = FALSE;
gboolean can_forward = TRUE;
int i;
if (file_forwarding && doc_mount_path == NULL)
{
g_message ("Can't get document portal mount path");
can_forward = FALSE;
}
else if (file_forwarding)
{
g_autoptr(GError) local_error = NULL;
documents = xdp_dbus_documents_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, 0,
"org.freedesktop.portal.Documents",
"/org/freedesktop/portal/documents",
NULL,
&local_error);
if (documents == NULL)
{
g_message ("Can't get document portal: %s", local_error->message);
can_forward = FALSE;
}
}
for (i = 0; i < n_args; i++)
{
g_autoptr(GFile) file = NULL;
if (file_forwarding &&
(strcmp (args[i], "@@") == 0 ||
strcmp (args[i], "@@u") == 0))
{
forwarding_uri = strcmp (args[i], "@@u") == 0;
forwarding = !forwarding;
continue;
}
if (can_forward && forwarding)
{
if (forwarding_uri)
{
if (g_str_has_prefix (args[i], "file:"))
file = g_file_new_for_uri (args[i]);
else if (G_IS_DIR_SEPARATOR (args[i][0]))
file = g_file_new_for_path (args[i]);
}
else
file = g_file_new_for_path (args[i]);
}
if (file && !flatpak_exports_path_is_visible (exports,
flatpak_file_get_path_cached (file)))
{
g_autofree char *doc_id = NULL;
g_autofree char *basename = NULL;
g_autofree char *doc_path = NULL;
if (!forward_file (documents, app_id, flatpak_file_get_path_cached (file),
&doc_id, error))
return FALSE;
basename = g_file_get_basename (file);
doc_path = g_build_filename (doc_mount_path, doc_id, basename, NULL);
if (forwarding_uri)
{
g_autofree char *path = doc_path;
doc_path = g_filename_to_uri (path, NULL, NULL);
/* This should never fail */
g_assert (doc_path != NULL);
}
g_info ("Forwarding file '%s' as '%s' to %s", args[i], doc_path, app_id);
flatpak_bwrap_add_arg (bwrap, doc_path);
}
else
flatpak_bwrap_add_arg (bwrap, args[i]);
}
return TRUE;
}
FlatpakContext *
flatpak_context_load_for_deploy (FlatpakDeploy *deploy,
GError **error)
{
g_autoptr(FlatpakContext) context = NULL;
g_autoptr(FlatpakContext) overrides = NULL;
g_autoptr(GKeyFile) metakey = NULL;
metakey = flatpak_deploy_get_metadata (deploy);
context = flatpak_app_compute_permissions (metakey, NULL, error);
if (context == NULL)
return NULL;
overrides = flatpak_deploy_get_overrides (deploy);
flatpak_context_merge (context, overrides);
return g_steal_pointer (&context);
}
static char *
calculate_ld_cache_checksum (GBytes *app_deploy_data,
GBytes *runtime_deploy_data,
const char *app_extensions,
const char *runtime_extensions)
{
g_autoptr(GChecksum) ld_so_checksum = g_checksum_new (G_CHECKSUM_SHA256);
if (app_deploy_data)
g_checksum_update (ld_so_checksum, (guchar *) flatpak_deploy_data_get_commit (app_deploy_data), -1);
g_checksum_update (ld_so_checksum, (guchar *) flatpak_deploy_data_get_commit (runtime_deploy_data), -1);
if (app_extensions)
g_checksum_update (ld_so_checksum, (guchar *) app_extensions, -1);
if (runtime_extensions)
g_checksum_update (ld_so_checksum, (guchar *) runtime_extensions, -1);
return g_strdup (g_checksum_get_string (ld_so_checksum));
}
static gboolean
add_ld_so_conf (FlatpakBwrap *bwrap,
GError **error)
{
const char *contents =
"include /run/flatpak/ld.so.conf.d/app-*.conf\n"
"include /app/etc/ld.so.conf\n"
"/app/lib\n"
"include /run/flatpak/ld.so.conf.d/runtime-*.conf\n";
return flatpak_bwrap_add_args_data (bwrap, "ld-so-conf",
contents, -1, "/etc/ld.so.conf", error);
}
static int
regenerate_ld_cache (GPtrArray *base_argv_array,
GArray *base_fd_array,
GFile *app_id_dir,
const char *checksum,
GFile *runtime_files,
gboolean generate_ld_so_conf,
GCancellable *cancellable,
GError **error)
{
g_autoptr(FlatpakBwrap) bwrap = NULL;
g_autoptr(GArray) combined_fd_array = NULL;
g_autoptr(GFile) ld_so_cache = NULL;
g_autoptr(GFile) ld_so_cache_tmp = NULL;
g_autofree char *sandbox_cache_path = NULL;
g_autofree char *tmp_basename = NULL;
g_auto(GStrv) minimal_envp = NULL;
g_autofree char *commandline = NULL;
int exit_status;
glnx_autofd int ld_so_fd = -1;
g_autoptr(GFile) ld_so_dir = NULL;
if (app_id_dir)
ld_so_dir = g_file_get_child (app_id_dir, ".ld.so");
else
{
g_autoptr(GFile) base_dir = g_file_new_for_path (g_get_user_cache_dir ());
ld_so_dir = g_file_resolve_relative_path (base_dir, "flatpak/ld.so");
}
ld_so_cache = g_file_get_child (ld_so_dir, checksum);
ld_so_fd = open (flatpak_file_get_path_cached (ld_so_cache), O_RDONLY);
if (ld_so_fd >= 0)
return glnx_steal_fd (&ld_so_fd);
g_info ("Regenerating ld.so.cache %s", flatpak_file_get_path_cached (ld_so_cache));
if (!flatpak_mkdir_p (ld_so_dir, cancellable, error))
return FALSE;
minimal_envp = flatpak_run_get_minimal_env (FALSE, FALSE);
bwrap = flatpak_bwrap_new (minimal_envp);
flatpak_bwrap_append_args (bwrap, base_argv_array);
flatpak_run_setup_usr_links (bwrap, runtime_files, NULL);
if (generate_ld_so_conf)
{
if (!add_ld_so_conf (bwrap, error))
return -1;
}
else
flatpak_bwrap_add_args (bwrap,
"--symlink", "../usr/etc/ld.so.conf", "/etc/ld.so.conf",
NULL);
tmp_basename = g_strconcat (checksum, ".XXXXXX", NULL);
glnx_gen_temp_name (tmp_basename);
sandbox_cache_path = g_build_filename ("/run/ld-so-cache-dir", tmp_basename, NULL);
ld_so_cache_tmp = g_file_get_child (ld_so_dir, tmp_basename);
flatpak_bwrap_add_args (bwrap,
"--unshare-pid",
"--unshare-ipc",
"--unshare-net",
"--proc", "/proc",
"--dev", "/dev",
"--bind", flatpak_file_get_path_cached (ld_so_dir), "/run/ld-so-cache-dir",
NULL);
flatpak_bwrap_sort_envp (bwrap);
flatpak_bwrap_envp_to_args (bwrap);
if (!flatpak_bwrap_bundle_args (bwrap, 1, -1, FALSE, error))
return -1;
flatpak_bwrap_add_args (bwrap,
"ldconfig", "-X", "-C", sandbox_cache_path, NULL);
flatpak_bwrap_finish (bwrap);
commandline = flatpak_quote_argv ((const char **) bwrap->argv->pdata, -1);
g_info ("Running: '%s'", commandline);
combined_fd_array = g_array_new (FALSE, TRUE, sizeof (int));
g_array_append_vals (combined_fd_array, base_fd_array->data, base_fd_array->len);
g_array_append_vals (combined_fd_array, bwrap->fds->data, bwrap->fds->len);
/* We use LEAVE_DESCRIPTORS_OPEN to work around dead-lock, see flatpak_close_fds_workaround */
if (!g_spawn_sync (NULL,
(char **) bwrap->argv->pdata,
bwrap->envp,
G_SPAWN_SEARCH_PATH | G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
flatpak_bwrap_child_setup_cb, combined_fd_array,
NULL, NULL,
&exit_status,
error))
return -1;
if (!WIFEXITED (exit_status) || WEXITSTATUS (exit_status) != 0)
{
flatpak_fail_error (error, FLATPAK_ERROR_SETUP_FAILED,
_("ldconfig failed, exit status %d"), exit_status);
return -1;
}
ld_so_fd = open (flatpak_file_get_path_cached (ld_so_cache_tmp), O_RDONLY);
if (ld_so_fd < 0)
{
flatpak_fail_error (error, FLATPAK_ERROR_SETUP_FAILED, _("Can't open generated ld.so.cache"));
return -1;
}
if (app_id_dir == NULL)
{
/* For runs without an app id dir we always regenerate the ld.so.cache */
unlink (flatpak_file_get_path_cached (ld_so_cache_tmp));
}
else
{
g_autoptr(GFile) active = g_file_get_child (ld_so_dir, "active");
/* For app-dirs we keep one checksum alive, by pointing the active symlink to it */
/* Rename to known name, possibly overwriting existing ref if race */
if (rename (flatpak_file_get_path_cached (ld_so_cache_tmp), flatpak_file_get_path_cached (ld_so_cache)) == -1)
{
glnx_set_error_from_errno (error);
return -1;
}
if (!flatpak_switch_symlink_and_remove (flatpak_file_get_path_cached (active),
checksum, error))
return -1;
}
return glnx_steal_fd (&ld_so_fd);
}
/* Check that this user is actually allowed to run this app. When running
* from the gnome-initial-setup session, an app filter might not be available. */
static gboolean
check_parental_controls (FlatpakDecomposed *app_ref,
FlatpakDeploy *deploy,
GCancellable *cancellable,
GError **error)
{
#ifdef HAVE_LIBMALCONTENT
g_autoptr(MctManager) manager = NULL;
g_autoptr(MctAppFilter) app_filter = NULL;
g_autoptr(GDBusConnection) system_bus = NULL;
g_autoptr(GError) local_error = NULL;
g_autoptr(GDesktopAppInfo) app_info = NULL;
gboolean allowed = FALSE;
system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error);
if (system_bus == NULL)
return FALSE;
manager = mct_manager_new (system_bus);
app_filter = mct_manager_get_app_filter (manager, getuid (),
MCT_GET_APP_FILTER_FLAGS_INTERACTIVE,
cancellable, &local_error);
if (g_error_matches (local_error, MCT_APP_FILTER_ERROR, MCT_APP_FILTER_ERROR_DISABLED))
{
g_info ("Skipping parental controls check for %s since parental "
"controls are disabled globally", flatpak_decomposed_get_ref (app_ref));
return TRUE;
}
else if (g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN) ||
g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER))
{
g_info ("Skipping parental controls check for %s since a required "
"service was not found", flatpak_decomposed_get_ref (app_ref));
return TRUE;
}
else if (local_error != NULL)
{
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
/* Always filter by app ID. Additionally, filter by app info (which runs
* multiple checks, including whether the app ID, executable path and
* content types are allowed) if available. If the flatpak contains
* multiple .desktop files, we use the main one. The app ID check is
* always done, as the binary executed by `flatpak run` isnt necessarily
* extracted from a .desktop file. */
allowed = mct_app_filter_is_flatpak_ref_allowed (app_filter, flatpak_decomposed_get_ref (app_ref));
/* Look up the apps main .desktop file. */
if (deploy != NULL && allowed)
{
g_autoptr(GFile) deploy_dir = NULL;
const char *deploy_path;
g_autofree char *desktop_file_name = NULL;
g_autofree char *desktop_file_path = NULL;
g_autofree char *app_id = flatpak_decomposed_dup_id (app_ref);
deploy_dir = flatpak_deploy_get_dir (deploy);
deploy_path = flatpak_file_get_path_cached (deploy_dir);
desktop_file_name = g_strconcat (app_id, ".desktop", NULL);
desktop_file_path = g_build_path (G_DIR_SEPARATOR_S,
deploy_path,
"export",
"share",
"applications",
desktop_file_name,
NULL);
app_info = g_desktop_app_info_new_from_filename (desktop_file_path);
}
if (app_info != NULL)
allowed = allowed && mct_app_filter_is_appinfo_allowed (app_filter,
G_APP_INFO (app_info));
if (!allowed)
return flatpak_fail_error (error, FLATPAK_ERROR_PERMISSION_DENIED,
/* Translators: The placeholder is for an app ref. */
_("Running %s is not allowed by the policy set by your administrator"),
flatpak_decomposed_get_ref (app_ref));
#endif /* HAVE_LIBMALCONTENT */
return TRUE;
}
static int
open_namespace_fd_if_needed (const char *path,
const char *other_path) {
struct stat s, other_s;
if (stat (path, &s) != 0)
return -1; /* No such namespace, ignore */
if (stat (other_path, &other_s) != 0)
return -1; /* No such namespace, ignore */
/* setns calls fail if the process is already in the desired namespace, hence the
check here to ensure the namespaces are different. */
if (s.st_ino != other_s.st_ino)
return open (path, O_RDONLY|O_CLOEXEC);
return -1;
}
gboolean
flatpak_run_app (FlatpakDecomposed *app_ref,
FlatpakDeploy *app_deploy,
const char *custom_app_path,
FlatpakContext *extra_context,
const char *custom_runtime,
const char *custom_runtime_version,
const char *custom_runtime_commit,
const char *custom_usr_path,
int parent_pid,
FlatpakRunFlags flags,
const char *cwd,
const char *custom_command,
char *args[],
int n_args,
int instance_id_fd,
char **instance_dir_out,
GCancellable *cancellable,
GError **error)
{
g_autoptr(FlatpakDeploy) runtime_deploy = NULL;
g_autoptr(GBytes) runtime_deploy_data = NULL;
g_autoptr(GBytes) app_deploy_data = NULL;
g_autoptr(GFile) app_files = NULL;
g_autoptr(GFile) original_app_files = NULL;
g_autoptr(GFile) runtime_files = NULL;
g_autoptr(GFile) original_runtime_files = NULL;
g_autoptr(GFile) bin_ldconfig = NULL;
g_autoptr(GFile) app_id_dir = NULL;
g_autoptr(GFile) real_app_id_dir = NULL;
g_autofree char *default_runtime_pref = NULL;
g_autoptr(FlatpakDecomposed) default_runtime = NULL;
g_autofree char *default_command = NULL;
g_autoptr(GKeyFile) metakey = NULL;
g_autoptr(GKeyFile) runtime_metakey = NULL;
g_autoptr(FlatpakBwrap) bwrap = NULL;
const char *command = "/bin/sh";
g_autoptr(GError) my_error = NULL;
g_autoptr(FlatpakDecomposed) runtime_ref = NULL;
int i;
g_autoptr(GPtrArray) previous_app_id_dirs = NULL;
g_autofree char *app_id = NULL;
g_autofree char *app_arch = NULL;
g_autofree char *app_info_path = NULL;
g_autofree char *app_ld_path = NULL;
g_autofree char *instance_id_host_dir = NULL;
g_autoptr(FlatpakContext) app_context = NULL;
g_autoptr(FlatpakContext) overrides = NULL;
g_autoptr(FlatpakExports) exports = NULL;
g_autofree char *commandline = NULL;
g_autofree char *doc_mount_path = NULL;
g_autofree char *app_extensions = NULL;
g_autofree char *runtime_extensions = NULL;
g_autofree char *runtime_ld_path = NULL;
g_autofree char *checksum = NULL;
glnx_autofd int per_app_dir_lock_fd = -1;
g_autofree char *per_app_dir_lock_path = NULL;
g_autofree char *shared_xdg_runtime_dir = NULL;
int ld_so_fd = -1;
g_autoptr(GFile) runtime_ld_so_conf = NULL;
gboolean generate_ld_so_conf = TRUE;
gboolean use_ld_so_cache = TRUE;
gboolean sandboxed = (flags & FLATPAK_RUN_FLAG_SANDBOX) != 0;
gboolean parent_expose_pids = (flags & FLATPAK_RUN_FLAG_PARENT_EXPOSE_PIDS) != 0;
gboolean parent_share_pids = (flags & FLATPAK_RUN_FLAG_PARENT_SHARE_PIDS) != 0;
const char *app_target_path = "/app";
const char *runtime_target_path = "/usr";
struct stat s;
g_return_val_if_fail (app_ref != NULL, FALSE);
/* This check exists to stop accidental usage of `sudo flatpak run`
and is not to prevent running as root.
*/
if (running_under_sudo ())
return flatpak_fail_error (error, FLATPAK_ERROR,
_("\"flatpak run\" is not intended to be run as `sudo flatpak run`. "
"Use `sudo -i` or `su -l` instead and invoke \"flatpak run\" from "
"inside the new shell."));
app_id = flatpak_decomposed_dup_id (app_ref);
g_return_val_if_fail (app_id != NULL, FALSE);
app_arch = flatpak_decomposed_dup_arch (app_ref);
g_return_val_if_fail (app_arch != NULL, FALSE);
/* Check the user is allowed to run this flatpak. */
if (!check_parental_controls (app_ref, app_deploy, cancellable, error))
return FALSE;
/* Construct the bwrap context. */
bwrap = flatpak_bwrap_new (NULL);
flatpak_bwrap_add_arg (bwrap, flatpak_get_bwrap ());
if (app_deploy == NULL)
{
g_assert (flatpak_decomposed_is_runtime (app_ref));
default_runtime_pref = flatpak_decomposed_dup_pref (app_ref);
}
else
{
const gchar *key;
app_deploy_data = flatpak_deploy_get_deploy_data (app_deploy, FLATPAK_DEPLOY_VERSION_ANY, cancellable, error);
if (app_deploy_data == NULL)
return FALSE;
if ((flags & FLATPAK_RUN_FLAG_DEVEL) != 0)
key = FLATPAK_METADATA_KEY_SDK;
else
key = FLATPAK_METADATA_KEY_RUNTIME;
metakey = flatpak_deploy_get_metadata (app_deploy);
default_runtime_pref = g_key_file_get_string (metakey,
FLATPAK_METADATA_GROUP_APPLICATION,
key, &my_error);
if (my_error)
{
g_propagate_error (error, g_steal_pointer (&my_error));
return FALSE;
}
}
default_runtime = flatpak_decomposed_new_from_pref (FLATPAK_KINDS_RUNTIME, default_runtime_pref, error);
if (default_runtime == NULL)
return FALSE;
if (custom_runtime != NULL || custom_runtime_version != NULL)
{
g_auto(GStrv) custom_runtime_parts = NULL;
const char *custom_runtime_id = NULL;
const char *custom_runtime_arch = NULL;
if (custom_runtime)
{
custom_runtime_parts = g_strsplit (custom_runtime, "/", 0);
for (i = 0; i < 3 && custom_runtime_parts[i] != NULL; i++)
{
if (strlen (custom_runtime_parts[i]) > 0)
{
if (i == 0)
custom_runtime_id = custom_runtime_parts[i];
if (i == 1)
custom_runtime_arch = custom_runtime_parts[i];
if (i == 2 && custom_runtime_version == NULL)
custom_runtime_version = custom_runtime_parts[i];
}
}
}
runtime_ref = flatpak_decomposed_new_from_decomposed (default_runtime,
FLATPAK_KINDS_RUNTIME,
custom_runtime_id,
custom_runtime_arch,
custom_runtime_version,
error);
if (runtime_ref == NULL)
return FALSE;
}
else
runtime_ref = flatpak_decomposed_ref (default_runtime);
runtime_deploy = flatpak_find_deploy_for_ref (flatpak_decomposed_get_ref (runtime_ref), custom_runtime_commit, NULL, cancellable, error);
if (runtime_deploy == NULL)
return FALSE;
runtime_deploy_data = flatpak_deploy_get_deploy_data (runtime_deploy, FLATPAK_DEPLOY_VERSION_ANY, cancellable, error);
if (runtime_deploy_data == NULL)
return FALSE;
runtime_metakey = flatpak_deploy_get_metadata (runtime_deploy);
app_context = flatpak_app_compute_permissions (metakey, runtime_metakey, error);
if (app_context == NULL)
return FALSE;
if (app_deploy != NULL)
{
overrides = flatpak_deploy_get_overrides (app_deploy);
flatpak_context_merge (app_context, overrides);
}
if (sandboxed)
flatpak_context_make_sandboxed (app_context);
if (extra_context)
flatpak_context_merge (app_context, extra_context);
original_runtime_files = flatpak_deploy_get_files (runtime_deploy);
if (custom_usr_path != NULL)
{
runtime_files = g_file_new_for_path (custom_usr_path);
/* Mount the original runtime below here instead of /usr */
runtime_target_path = "/run/parent/usr";
}
else
{
runtime_files = g_object_ref (original_runtime_files);
}
bin_ldconfig = g_file_resolve_relative_path (runtime_files, "bin/ldconfig");
if (!g_file_query_exists (bin_ldconfig, NULL))
use_ld_so_cache = FALSE;
/* We can't use the ld.so cache if we are using a custom /usr or /app,
* because we don't have a unique ID for the /usr or /app, so we can't
* do cache-invalidation correctly. The caller can either build their
* own ld.so.cache before supplying us with the runtime, or supply
* their own LD_LIBRARY_PATH. */
if (custom_usr_path != NULL || custom_app_path != NULL)
use_ld_so_cache = FALSE;
if (app_deploy != NULL)
{
g_autofree const char **previous_ids = NULL;
gsize len = 0;
gboolean do_migrate;
real_app_id_dir = flatpak_get_data_dir (app_id);
original_app_files = flatpak_deploy_get_files (app_deploy);
previous_app_id_dirs = g_ptr_array_new_with_free_func (g_object_unref);
previous_ids = flatpak_deploy_data_get_previous_ids (app_deploy_data, &len);
do_migrate = !g_file_query_exists (real_app_id_dir, cancellable);
/* When migrating, find most recent old existing source and rename that to
* the new name.
*
* We ignore other names than that. For more recent names that don't exist
* we never ran them so nothing will even reference them. For older names
* either they were not used, or they were used but then the more recent
* name was used and a symlink to it was created.
*
* This means we may end up with a chain of symlinks: oldest -> old -> current.
* This is unfortunate but not really a problem, but for robustness reasons we
* don't want to mess with user files unnecessary. For example, the app dir could
* actually be a symlink for other reasons. Imagine for instance that you want to put the
* steam games somewhere else so you leave the app dir as a symlink to /mnt/steam.
*/
for (i = len - 1; i >= 0; i--)
{
g_autoptr(GFile) previous_app_id_dir = NULL;
g_autoptr(GFileInfo) previous_app_id_dir_info = NULL;
g_autoptr(GError) local_error = NULL;
previous_app_id_dir = flatpak_get_data_dir (previous_ids[i]);
previous_app_id_dir_info = g_file_query_info (previous_app_id_dir,
G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK ","
G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable,
&local_error);
/* Warn about the migration failures, but don't make them fatal, then you can never run the app */
if (previous_app_id_dir_info == NULL)
{
if (!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && do_migrate)
{
g_warning (_("Failed to migrate from %s: %s"), flatpak_file_get_path_cached (previous_app_id_dir),
local_error->message);
do_migrate = FALSE; /* Don't migrate older things, they are likely symlinks to the thing that we failed on */
}
g_clear_error (&local_error);
continue;
}
if (do_migrate)
{
do_migrate = FALSE; /* Don't migrate older things, they are likely symlinks to this dir */
if (!flatpak_file_rename (previous_app_id_dir, real_app_id_dir, cancellable, &local_error))
{
g_warning (_("Failed to migrate old app data directory %s to new name %s: %s"),
flatpak_file_get_path_cached (previous_app_id_dir), app_id,
local_error->message);
}
else
{
/* Leave a symlink in place of the old data dir */
if (!g_file_make_symbolic_link (previous_app_id_dir, app_id, cancellable, &local_error))
{
g_warning (_("Failed to create symlink while migrating %s: %s"),
flatpak_file_get_path_cached (previous_app_id_dir),
local_error->message);
}
}
}
/* Give app access to this old dir */
g_ptr_array_add (previous_app_id_dirs, g_steal_pointer (&previous_app_id_dir));
}
if (!flatpak_ensure_data_dir (real_app_id_dir, cancellable, error))
return FALSE;
if (!sandboxed)
app_id_dir = g_object_ref (real_app_id_dir);
}
if (custom_app_path != NULL)
{
if (strcmp (custom_app_path, "") == 0)
app_files = NULL;
else
app_files = g_file_new_for_path (custom_app_path);
/* Mount the original app below here */
app_target_path = "/run/parent/app";
}
else if (original_app_files != NULL)
{
app_files = g_object_ref (original_app_files);
}
flatpak_run_apply_env_default (bwrap, use_ld_so_cache);
flatpak_run_apply_env_vars (bwrap, app_context);
flatpak_run_apply_env_prompt (bwrap, app_id);
if (real_app_id_dir)
{
g_autoptr(GFile) sandbox_dir = g_file_get_child (real_app_id_dir, "sandbox");
flatpak_bwrap_set_env (bwrap, "FLATPAK_SANDBOX_DIR", flatpak_file_get_path_cached (sandbox_dir), TRUE);
}
flatpak_bwrap_add_args (bwrap,
"--ro-bind", flatpak_file_get_path_cached (runtime_files), "/usr",
NULL);
if (runtime_files == original_runtime_files)
{
/* All true Flatpak runtimes have files/.ref */
flatpak_bwrap_add_args (bwrap,
"--lock-file", "/usr/.ref",
NULL);
}
else
{
g_autoptr(GFile) runtime_child = NULL;
runtime_child = g_file_get_child (runtime_files, ".ref");
/* Lock ${usr}/.ref if it exists */
if (g_file_query_exists (runtime_child, NULL))
flatpak_bwrap_add_args (bwrap,
"--lock-file", "/usr/.ref",
NULL);
/* Put the real Flatpak runtime in /run/parent, so that the
* replacement /usr can have symlinks into /run/parent in order
* to use the Flatpak runtime's graphics drivers etc. if desired */
flatpak_bwrap_add_args (bwrap,
"--ro-bind",
flatpak_file_get_path_cached (original_runtime_files),
"/run/parent/usr",
"--lock-file", "/run/parent/usr/.ref",
NULL);
flatpak_run_setup_usr_links (bwrap, original_runtime_files,
"/run/parent");
g_clear_object (&runtime_child);
runtime_child = g_file_get_child (original_runtime_files, "etc");
if (g_file_query_exists (runtime_child, NULL))
flatpak_bwrap_add_args (bwrap,
"--symlink", "usr/etc", "/run/parent/etc",
NULL);
}
if (app_files != NULL)
{
flatpak_bwrap_add_args (bwrap,
"--ro-bind", flatpak_file_get_path_cached (app_files), "/app",
NULL);
if (app_files == original_app_files)
{
/* All true Flatpak apps have files/.ref */
flatpak_bwrap_add_args (bwrap,
"--lock-file", "/app/.ref",
NULL);
}
else
{
g_autoptr(GFile) app_child = NULL;
app_child = g_file_get_child (app_files, ".ref");
/* Lock ${app}/.ref if it exists */
if (g_file_query_exists (app_child, NULL))
flatpak_bwrap_add_args (bwrap,
"--lock-file", "/app/.ref",
NULL);
}
}
else
{
flatpak_bwrap_add_args (bwrap,
"--dir", "/app",
NULL);
}
if (original_app_files != NULL && app_files != original_app_files)
{
/* Put the real Flatpak app in /run/parent/app */
flatpak_bwrap_add_args (bwrap,
"--ro-bind",
flatpak_file_get_path_cached (original_app_files),
"/run/parent/app",
"--lock-file", "/run/parent/app/.ref",
NULL);
}
if (metakey != NULL &&
!flatpak_run_add_extension_args (bwrap, metakey, app_ref,
use_ld_so_cache, app_target_path,
&app_extensions, &app_ld_path,
cancellable, error))
return FALSE;
if (!flatpak_run_add_extension_args (bwrap, runtime_metakey, runtime_ref,
use_ld_so_cache, runtime_target_path,
&runtime_extensions, &runtime_ld_path,
cancellable, error))
return FALSE;
if (custom_usr_path == NULL)
flatpak_run_extend_ld_path (bwrap, NULL, runtime_ld_path);
if (custom_app_path == NULL)
flatpak_run_extend_ld_path (bwrap, app_ld_path, NULL);
runtime_ld_so_conf = g_file_resolve_relative_path (runtime_files, "etc/ld.so.conf");
if (lstat (flatpak_file_get_path_cached (runtime_ld_so_conf), &s) == 0)
generate_ld_so_conf = S_ISREG (s.st_mode) && s.st_size == 0;
/* At this point we have the minimal argv set up, with just the app, runtime and extensions.
We can reuse this to generate the ld.so.cache (if needed) */
if (use_ld_so_cache)
{
checksum = calculate_ld_cache_checksum (app_deploy_data, runtime_deploy_data,
app_extensions, runtime_extensions);
ld_so_fd = regenerate_ld_cache (bwrap->argv,
bwrap->fds,
app_id_dir,
checksum,
runtime_files,
generate_ld_so_conf,
cancellable, error);
if (ld_so_fd == -1)
return FALSE;
flatpak_bwrap_add_fd (bwrap, ld_so_fd);
}
flags |= flatpak_context_get_run_flags (app_context);
if (!flatpak_run_setup_base_argv (bwrap, runtime_files, app_id_dir, app_arch, flags, error))
return FALSE;
if (generate_ld_so_conf)
{
if (!add_ld_so_conf (bwrap, error))
return FALSE;
}
if (ld_so_fd != -1)
{
/* Don't add to fd_array, its already there */
flatpak_bwrap_add_arg (bwrap, "--ro-bind-data");
flatpak_bwrap_add_arg_printf (bwrap, "%d", ld_so_fd);
flatpak_bwrap_add_arg (bwrap, "/etc/ld.so.cache");
}
if (!flatpak_run_add_app_info_args (bwrap,
app_files, original_app_files, app_deploy_data, app_extensions,
runtime_files, original_runtime_files, runtime_deploy_data, runtime_extensions,
app_id, flatpak_decomposed_get_branch (app_ref),
runtime_ref, app_id_dir, app_context, extra_context,
sandboxed, FALSE, flags & FLATPAK_RUN_FLAG_DEVEL,
&app_info_path, instance_id_fd, &instance_id_host_dir,
error))
return FALSE;
if (!sandboxed)
{
if (!flatpak_instance_ensure_per_app_dir (app_id,
&per_app_dir_lock_fd,
&per_app_dir_lock_path,
error))
return FALSE;
if (!flatpak_instance_ensure_per_app_xdg_runtime_dir (app_id,
per_app_dir_lock_fd,
&shared_xdg_runtime_dir,
error))
return FALSE;
flatpak_bwrap_add_arg (bwrap, "--bind");
flatpak_bwrap_add_arg (bwrap, shared_xdg_runtime_dir);
flatpak_bwrap_add_arg_printf (bwrap, "/run/user/%d", getuid ());
}
if (!flatpak_run_add_dconf_args (bwrap, app_id, metakey, error))
return FALSE;
if (!sandboxed && !(flags & FLATPAK_RUN_FLAG_NO_DOCUMENTS_PORTAL))
add_document_portal_args (bwrap, app_id, &doc_mount_path);
if (!flatpak_run_add_environment_args (bwrap, app_info_path, flags,
app_id, app_context, app_id_dir, previous_app_id_dirs,
per_app_dir_lock_fd,
&exports, cancellable, error))
return FALSE;
if (per_app_dir_lock_path != NULL)
{
static const char lock[] = "/run/flatpak/per-app-dirs-ref";
flatpak_bwrap_add_args (bwrap,
"--ro-bind", per_app_dir_lock_path, lock,
"--lock-file", lock,
NULL);
}
if ((app_context->shares & FLATPAK_CONTEXT_SHARED_NETWORK) != 0)
{
flatpak_run_add_gssproxy_args (bwrap);
flatpak_run_add_resolved_args (bwrap);
}
flatpak_run_add_journal_args (bwrap);
add_font_path_args (bwrap);
add_icon_path_args (bwrap);
flatpak_bwrap_add_args (bwrap,
/* Not in base, because we don't want this for flatpak build */
"--symlink", "/app/lib/debug/source", "/run/build",
"--symlink", "/usr/lib/debug/source", "/run/build-runtime",
NULL);
if (cwd)
flatpak_bwrap_add_args (bwrap, "--chdir", cwd, NULL);
if (parent_expose_pids || parent_share_pids)
{
g_autofree char *userns_path = NULL;
g_autofree char *pidns_path = NULL;
g_autofree char *userns2_path = NULL;
int userns_fd, userns2_fd, pidns_fd;
if (parent_pid == 0)
return flatpak_fail (error, "No parent pid specified");
userns_path = g_strdup_printf ("/proc/%d/root/run/.userns", parent_pid);
userns_fd = open_namespace_fd_if_needed (userns_path, "/proc/self/ns/user");
if (userns_fd != -1)
{
flatpak_bwrap_add_args_data_fd (bwrap, "--userns", userns_fd, NULL);
userns2_path = g_strdup_printf ("/proc/%d/ns/user", parent_pid);
userns2_fd = open_namespace_fd_if_needed (userns2_path, userns_path);
if (userns2_fd != -1)
flatpak_bwrap_add_args_data_fd (bwrap, "--userns2", userns2_fd, NULL);
}
pidns_path = g_strdup_printf ("/proc/%d/ns/pid", parent_pid);
pidns_fd = open (pidns_path, O_RDONLY|O_CLOEXEC);
if (pidns_fd != -1)
flatpak_bwrap_add_args_data_fd (bwrap, "--pidns", pidns_fd, NULL);
}
flatpak_bwrap_populate_runtime_dir (bwrap, shared_xdg_runtime_dir);
if (custom_command)
{
command = custom_command;
}
else if (metakey)
{
default_command = g_key_file_get_string (metakey,
FLATPAK_METADATA_GROUP_APPLICATION,
FLATPAK_METADATA_KEY_COMMAND,
&my_error);
if (my_error)
{
g_propagate_error (error, g_steal_pointer (&my_error));
return FALSE;
}
command = default_command;
}
flatpak_bwrap_sort_envp (bwrap);
flatpak_bwrap_envp_to_args (bwrap);
if (!flatpak_bwrap_bundle_args (bwrap, 1, -1, FALSE, error))
return FALSE;
flatpak_bwrap_add_arg (bwrap, command);
if (!add_rest_args (bwrap, app_id,
exports, (flags & FLATPAK_RUN_FLAG_FILE_FORWARDING) != 0,
doc_mount_path,
args, n_args, error))
return FALSE;
/* Hold onto the lock until we execute bwrap */
flatpak_bwrap_add_noinherit_fd (bwrap, glnx_steal_fd (&per_app_dir_lock_fd));
flatpak_bwrap_finish (bwrap);
commandline = flatpak_quote_argv ((const char **) bwrap->argv->pdata, -1);
g_info ("Running '%s'", commandline);
if ((flags & (FLATPAK_RUN_FLAG_BACKGROUND)) != 0 ||
g_getenv ("FLATPAK_TEST_COVERAGE") != NULL)
{
GPid child_pid;
char pid_str[64];
g_autofree char *pid_path = NULL;
GSpawnFlags spawn_flags;
spawn_flags = G_SPAWN_SEARCH_PATH;
if (flags & FLATPAK_RUN_FLAG_DO_NOT_REAP ||
(flags & FLATPAK_RUN_FLAG_BACKGROUND) == 0)
spawn_flags |= G_SPAWN_DO_NOT_REAP_CHILD;
/* We use LEAVE_DESCRIPTORS_OPEN to work around dead-lock, see flatpak_close_fds_workaround */
spawn_flags |= G_SPAWN_LEAVE_DESCRIPTORS_OPEN;
/* flatpak_bwrap_envp_to_args() moved the environment variables to
* be set into --setenv instructions in argv, so the environment
* in which the bwrap command runs must be empty. */
g_assert (bwrap->envp != NULL);
g_assert (bwrap->envp[0] == NULL);
if (!g_spawn_async (NULL,
(char **) bwrap->argv->pdata,
bwrap->envp,
spawn_flags,
flatpak_bwrap_child_setup_cb, bwrap->fds,
&child_pid,
error))
return FALSE;
g_snprintf (pid_str, sizeof (pid_str), "%d", child_pid);
pid_path = g_build_filename (instance_id_host_dir, "pid", NULL);
g_file_set_contents (pid_path, pid_str, -1, NULL);
if ((flags & (FLATPAK_RUN_FLAG_BACKGROUND)) == 0)
{
int wait_status;
if (waitpid (child_pid, &wait_status, 0) != child_pid)
return glnx_throw_errno_prefix (error, "Failed to wait for child process");
if (WIFEXITED (wait_status))
exit (WEXITSTATUS (wait_status));
if (WIFSIGNALED (wait_status))
exit (128 + WTERMSIG (wait_status));
return glnx_throw (error, "Unknown wait status from waitpid(): %d", wait_status);
}
}
else
{
char pid_str[64];
g_autofree char *pid_path = NULL;
g_snprintf (pid_str, sizeof (pid_str), "%d", getpid ());
pid_path = g_build_filename (instance_id_host_dir, "pid", NULL);
g_file_set_contents (pid_path, pid_str, -1, NULL);
/* Ensure we unset O_CLOEXEC for marked fds and rewind fds as needed.
* Note that this does not close fds that are not already marked O_CLOEXEC, because
* we do want to allow inheriting fds into flatpak run. */
flatpak_bwrap_child_setup (bwrap->fds, FALSE);
/* flatpak_bwrap_envp_to_args() moved the environment variables to
* be set into --setenv instructions in argv, so the environment
* in which the bwrap command runs must be empty. */
g_assert (bwrap->envp != NULL);
g_assert (bwrap->envp[0] == NULL);
if (execvpe (flatpak_get_bwrap (), (char **) bwrap->argv->pdata, bwrap->envp) == -1)
{
g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno),
_("Unable to start app"));
return FALSE;
}
/* Not actually reached... */
}
if (instance_dir_out)
*instance_dir_out = g_steal_pointer (&instance_id_host_dir);
return TRUE;
}