-
Simon McVittie authored
Signed-off-by:
Simon McVittie <smcv@collabora.com>
Simon McVittie authoredSigned-off-by:
Simon McVittie <smcv@collabora.com>
flatpak-run.c 13.29 KiB
/*
* Cut-down version of common/flatpak-run.c from Flatpak
* Last updated: Flatpak 1.6.1
*
* Copyright © 2017-2019 Collabora Ltd.
* Copyright © 2014-2019 Red Hat, Inc
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Alexander Larsson <alexl@redhat.com>
*/
#include "config.h"
#include "flatpak-run-private.h"
#include <sys/utsname.h>
#include <X11/Xauth.h>
#include <gio/gio.h>
#include "libglnx/libglnx.h"
#include "flatpak-utils-base-private.h"
#include "flatpak-utils-private.h"
/* In Flatpak this is optional, in pressure-vessel not so much */
#define ENABLE_XAUTH
static char *
extract_unix_path_from_dbus_address (const char *address)
{
const char *path, *path_end;
if (address == NULL)
return NULL;
if (!g_str_has_prefix (address, "unix:"))
return NULL;
path = strstr (address, "path=");
if (path == NULL)
return NULL;
path += strlen ("path=");
path_end = path;
while (*path_end != 0 && *path_end != ',')
path_end++;
return g_strndup (path, path_end - path);
}
#ifdef ENABLE_XAUTH
static gboolean
auth_streq (char *str,
char *au_str,
int au_len)
{
return au_len == strlen (str) && memcmp (str, au_str, au_len) == 0;
}
static gboolean
xauth_entry_should_propagate (Xauth *xa,
char *hostname,
char *number)
{
/* ensure entry isn't for remote access */
if (xa->family != FamilyLocal && xa->family != FamilyWild)
return FALSE;
/* ensure entry is for this machine */
if (xa->family == FamilyLocal && !auth_streq (hostname, xa->address, xa->address_length))
return FALSE;
/* ensure entry is for this session */
if (xa->number != NULL && !auth_streq (number, xa->number, xa->number_length))
return FALSE;
return TRUE;
}
static void
write_xauth (char *number, FILE *output)
{
Xauth *xa, local_xa;
char *filename;
FILE *f;
struct utsname unames;
if (uname (&unames))
{
g_warning ("uname failed");
return;
}
filename = XauFileName ();
f = fopen (filename, "rb");
if (f == NULL)
return;
while (TRUE)
{
xa = XauReadAuth (f);
if (xa == NULL)
break;
if (xauth_entry_should_propagate (xa, unames.nodename, number))
{
local_xa = *xa;
if (local_xa.number)
{
local_xa.number = (char *) "99";
local_xa.number_length = 2;
}
if (!XauWriteAuth (output, &local_xa))
g_warning ("xauth write error");
}
XauDisposeAuth (xa);
}
fclose (f);
}
#endif /* ENABLE_XAUTH */
void
flatpak_run_add_x11_args (FlatpakBwrap *bwrap,
gboolean allowed)
{
g_autofree char *x11_socket = NULL;
const char *display;
/* Always cover /tmp/.X11-unix, that way we never see the host one in case
* we have access to the host /tmp. If you request X access we'll put the right
* thing in this anyway.
*/
flatpak_bwrap_add_args (bwrap,
"--tmpfs", "/tmp/.X11-unix",
NULL);
if (!allowed)
{
flatpak_bwrap_unset_env (bwrap, "DISPLAY");
return;
}
g_debug ("Allowing x11 access");
display = g_getenv ("DISPLAY");
if (display && display[0] == ':' && g_ascii_isdigit (display[1]))
{
const char *display_nr = &display[1];
const char *display_nr_end = display_nr;
g_autofree char *d = NULL;
while (g_ascii_isdigit (*display_nr_end))
display_nr_end++;
d = g_strndup (display_nr, display_nr_end - display_nr);
x11_socket = g_strdup_printf ("/tmp/.X11-unix/X%s", d);
flatpak_bwrap_add_args (bwrap,
"--ro-bind", x11_socket, "/tmp/.X11-unix/X99",
NULL);
flatpak_bwrap_set_env (bwrap, "DISPLAY", ":99.0", TRUE);
#ifdef ENABLE_XAUTH
g_auto(GLnxTmpfile) xauth_tmpf = { 0, };
if (glnx_open_anonymous_tmpfile_full (O_RDWR | O_CLOEXEC, "/tmp", &xauth_tmpf, NULL))
{
FILE *output = fdopen (xauth_tmpf.fd, "wb");
if (output != NULL)
{
/* fd is now owned by output, steal it from the tmpfile */
int tmp_fd = dup (glnx_steal_fd (&xauth_tmpf.fd));
if (tmp_fd != -1)
{
g_autofree char *dest = g_strdup_printf ("/run/user/%d/Xauthority", getuid ());
write_xauth (d, output);
flatpak_bwrap_add_args_data_fd (bwrap, "--ro-bind-data", tmp_fd, dest);
flatpak_bwrap_set_env (bwrap, "XAUTHORITY", dest, TRUE);
}
fclose (output);
if (tmp_fd != -1)
lseek (tmp_fd, 0, SEEK_SET);
}
}
#endif
}
else
{
flatpak_bwrap_unset_env (bwrap, "DISPLAY");
}
}
gboolean
flatpak_run_add_wayland_args (FlatpakBwrap *bwrap)
{
const char *wayland_display;
g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
g_autofree char *wayland_socket = NULL;
g_autofree char *sandbox_wayland_socket = NULL;
gboolean res = FALSE;
struct stat statbuf;
wayland_display = g_getenv ("WAYLAND_DISPLAY");
if (!wayland_display)
wayland_display = "wayland-0";
wayland_socket = g_build_filename (user_runtime_dir, wayland_display, NULL);
sandbox_wayland_socket = g_strdup_printf ("/run/user/%d/%s", getuid (), wayland_display);
if (stat (wayland_socket, &statbuf) == 0 &&
(statbuf.st_mode & S_IFMT) == S_IFSOCK)
{
res = TRUE;
flatpak_bwrap_add_args (bwrap,
"--ro-bind", wayland_socket, sandbox_wayland_socket,
NULL);
}
return res;
}
/* Try to find a default server from a pulseaudio confguration file */
static char *
flatpak_run_get_pulseaudio_server_user_config (const char *path)
{
g_autoptr(GFile) file = g_file_new_for_path (path);
g_autoptr(GError) my_error = NULL;
g_autoptr(GFileInputStream) input_stream = NULL;
g_autoptr(GDataInputStream) data_stream = NULL;
size_t len;
input_stream = g_file_read (file, NULL, &my_error);
if (my_error)
{
g_debug ("Pulseaudio user configuration file '%s': %s", path, my_error->message);
return NULL;
}
data_stream = g_data_input_stream_new (G_INPUT_STREAM (input_stream));
while (TRUE)
{
g_autofree char *line = g_data_input_stream_read_line (data_stream, &len, NULL, NULL);
if (line == NULL)
break;
g_strchug (line);
if ((*line == '\0') || (*line == ';') || (*line == '#'))
continue;
if (g_str_has_prefix (line, ".include "))
{
g_autofree char *rec_path = g_strdup (line + 9);
g_strstrip (rec_path);
char *found = flatpak_run_get_pulseaudio_server_user_config (rec_path);
if (found)
return found;
}
else if (g_str_has_prefix (line, "["))
{
return NULL;
}
else
{
g_auto(GStrv) tokens = g_strsplit (line, "=", 2);
if ((tokens[0] != NULL) && (tokens[1] != NULL))
{
g_strchomp (tokens[0]);
if (strcmp ("default-server", tokens[0]) == 0)
{
g_strstrip (tokens[1]);
g_debug ("Found pulseaudio socket from configuration file '%s': %s", path, tokens[1]);
return g_strdup (tokens[1]);
}
}
}
}
return NULL;
}
static char *
flatpak_run_get_pulseaudio_server (void)
{
const char * pulse_clientconfig;
char *pulse_server;
g_autofree char *pulse_user_config = NULL;
pulse_server = g_strdup (g_getenv ("PULSE_SERVER"));
if (pulse_server)
return pulse_server;
pulse_clientconfig = g_getenv ("PULSE_CLIENTCONFIG");
if (pulse_clientconfig)
return flatpak_run_get_pulseaudio_server_user_config (pulse_clientconfig);
pulse_user_config = g_build_filename (g_get_user_config_dir (), "pulse/client.conf", NULL);
pulse_server = flatpak_run_get_pulseaudio_server_user_config (pulse_user_config);
if (pulse_server)
return pulse_server;
pulse_server = flatpak_run_get_pulseaudio_server_user_config ("/etc/pulse/client.conf");
if (pulse_server)
return pulse_server;
return NULL;
}
static char *
flatpak_run_parse_pulse_server (const char *value)
{
g_auto(GStrv) servers = g_strsplit (value, " ", 0);
gsize i;
for (i = 0; servers[i] != NULL; i++)
{
const char *server = servers[i];
if (g_str_has_prefix (server, "{"))
{
const char * closing = strstr (server, "}");
if (closing == NULL)
continue;
server = closing + 1;
}
if (g_str_has_prefix (server, "unix:"))
return g_strdup (server + 5);
}
return NULL;
}
void
flatpak_run_add_pulseaudio_args (FlatpakBwrap *bwrap)
{
g_autofree char *pulseaudio_server = flatpak_run_get_pulseaudio_server ();
g_autofree char *pulseaudio_socket = NULL;
g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
if (pulseaudio_server)
pulseaudio_socket = flatpak_run_parse_pulse_server (pulseaudio_server);
if (!pulseaudio_socket)
pulseaudio_socket = g_build_filename (user_runtime_dir, "pulse/native", NULL);
flatpak_bwrap_unset_env (bwrap, "PULSE_SERVER");
/* SteamOS system-wide PulseAudio instance */
if (!g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS))
{
g_clear_pointer (&pulseaudio_socket, g_free);
pulseaudio_socket = g_strdup ("/var/run/pulse/native");
}
if (g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS))
{
gboolean share_shm = FALSE; /* TODO: When do we add this? */
g_autofree char *client_config = g_strdup_printf ("enable-shm=%s\n", share_shm ? "yes" : "no");
g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/pulse/native", getuid ());
g_autofree char *pulse_server = g_strdup_printf ("unix:/run/user/%d/pulse/native", getuid ());
g_autofree char *config_path = g_strdup_printf ("/run/user/%d/pulse/config", getuid ());
/* FIXME - error handling */
if (!flatpak_bwrap_add_args_data (bwrap, "pulseaudio", client_config, -1, config_path, NULL))
return;
flatpak_bwrap_add_args (bwrap,
"--ro-bind", pulseaudio_socket, sandbox_socket_path,
NULL);
flatpak_bwrap_set_env (bwrap, "PULSE_SERVER", pulse_server, TRUE);
flatpak_bwrap_set_env (bwrap, "PULSE_CLIENTCONFIG", config_path, TRUE);
}
else
g_debug ("Could not find pulseaudio socket");
}
/* Simplified from Flatpak: we never restrict access to the D-Bus system bus */
gboolean
flatpak_run_add_system_dbus_args (FlatpakBwrap *app_bwrap)
{
const char *dbus_address = g_getenv ("DBUS_SYSTEM_BUS_ADDRESS");
g_autofree char *dbus_system_socket = NULL;
if (dbus_address != NULL)
dbus_system_socket = extract_unix_path_from_dbus_address (dbus_address);
else if (g_file_test ("/var/run/dbus/system_bus_socket", G_FILE_TEST_EXISTS))
dbus_system_socket = g_strdup ("/var/run/dbus/system_bus_socket");
if (dbus_system_socket != NULL)
{
flatpak_bwrap_add_args (app_bwrap,
"--ro-bind", dbus_system_socket, "/run/dbus/system_bus_socket",
NULL);
flatpak_bwrap_set_env (app_bwrap, "DBUS_SYSTEM_BUS_ADDRESS", "unix:path=/run/dbus/system_bus_socket", TRUE);
return TRUE;
}
return FALSE;
}
/* Simplified from Flatpak: we never restrict access to the D-Bus session bus */
gboolean
flatpak_run_add_session_dbus_args (FlatpakBwrap *app_bwrap)
{
const char *dbus_address = g_getenv ("DBUS_SESSION_BUS_ADDRESS");
g_autofree char *dbus_session_socket = NULL;
g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/bus", getuid ());
g_autofree char *sandbox_dbus_address = g_strdup_printf ("unix:path=/run/user/%d/bus", getuid ());
if (dbus_address != NULL)
{
dbus_session_socket = extract_unix_path_from_dbus_address (dbus_address);
}
else
{
g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
struct stat statbuf;
dbus_session_socket = g_build_filename (user_runtime_dir, "bus", NULL);
if (stat (dbus_session_socket, &statbuf) < 0
|| (statbuf.st_mode & S_IFMT) != S_IFSOCK
|| statbuf.st_uid != getuid ())
return FALSE;
}
if (dbus_session_socket != NULL)
{
flatpak_bwrap_add_args (app_bwrap,
"--ro-bind", dbus_session_socket, sandbox_socket_path,
NULL);
flatpak_bwrap_set_env (app_bwrap, "DBUS_SESSION_BUS_ADDRESS", sandbox_dbus_address, TRUE);
return TRUE;
}
return FALSE;
}