/* * Cut-down version of common/flatpak-run.c from Flatpak * * Copyright © 2017-2019 Collabora Ltd. * Copyright © 2014-2019 Red Hat, Inc * * SPDX-License-Identifier: LGPL-2.1-or-later * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, see <http://www.gnu.org/licenses/>. */ #include "flatpak-run-private.h" #include <sys/utsname.h> #include <X11/Xauth.h> #include <gio/gio.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 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 (O_RDWR | O_CLOEXEC, &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"); 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 ()); g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir (); /* FIXME: upstream the use of user_runtime_dir to Flatpak */ if (dbus_address != NULL) { dbus_session_socket = extract_unix_path_from_dbus_address (dbus_address); } else { 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; }