/* * Copyright © 2014-2019 Red Hat, Inc * Copyright © 2017-2021 Collabora Ltd. * * 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/>. */ #include "wrap-setup.h" #include "steam-runtime-tools/glib-backports-internal.h" #include "steam-runtime-tools/libdl-internal.h" #include "steam-runtime-tools/utils-internal.h" #include "libglnx/libglnx.h" #include <string.h> #include "bwrap.h" #include "flatpak-run-private.h" #include "flatpak-utils-private.h" /* * Use code borrowed from Flatpak to share various bits of the * execution environment with the host system, in particular Wayland, * X11 and PulseAudio sockets. */ void pv_wrap_share_sockets (FlatpakBwrap *bwrap, PvEnviron *container_env, gboolean using_a_runtime, gboolean is_flatpak_env) { g_autoptr(FlatpakBwrap) sharing_bwrap = flatpak_bwrap_new (flatpak_bwrap_empty_env); g_auto(GStrv) envp = NULL; gsize i; g_return_if_fail (bwrap != NULL); g_return_if_fail (container_env != NULL); /* If these are set by flatpak_run_add_x11_args(), etc., we'll * change them from unset to set later. * Every variable that is unset with flatpak_bwrap_unset_env() in * the functions we borrow from Flatpak (below) should be listed * here. */ pv_environ_setenv (container_env, "DISPLAY", NULL); pv_environ_setenv (container_env, "PULSE_SERVER", NULL); pv_environ_setenv (container_env, "XAUTHORITY", NULL); flatpak_run_add_font_path_args (sharing_bwrap); /* We need to set up IPC rendezvous points relatively late, so that * even if we are sharing /tmp via --filesystem=/tmp, we'll still * mount our own /tmp/.X11-unix over the top of the OS's. */ if (using_a_runtime) { flatpak_run_add_wayland_args (sharing_bwrap); /* When in a Flatpak container the "DISPLAY" env is equal to ":99.0", * but it might be different on the host system. As a workaround we simply * bind the whole "/tmp/.X11-unix" directory and later unset the container * "DISPLAY" env. */ if (is_flatpak_env) { flatpak_bwrap_add_args (sharing_bwrap, "--ro-bind", "/tmp/.X11-unix", "/tmp/.X11-unix", NULL); } else { flatpak_run_add_x11_args (sharing_bwrap, TRUE); } flatpak_run_add_pulseaudio_args (sharing_bwrap); flatpak_run_add_session_dbus_args (sharing_bwrap); flatpak_run_add_system_dbus_args (sharing_bwrap); flatpak_run_add_resolved_args (sharing_bwrap); pv_wrap_add_pipewire_args (sharing_bwrap, container_env); } envp = pv_bwrap_steal_envp (sharing_bwrap); for (i = 0; envp[i] != NULL; i++) { static const char * const known_vars[] = { "DBUS_SESSION_BUS_ADDRESS", "DBUS_SYSTEM_BUS_ADDRESS", "DISPLAY", "PULSE_CLIENTCONFIG", "PULSE_SERVER", "XAUTHORITY", }; char *equals = strchr (envp[i], '='); const char *var = envp[i]; const char *val = NULL; gsize j; if (equals != NULL) { *equals = '\0'; val = equals + 1; } for (j = 0; j < G_N_ELEMENTS (known_vars); j++) { if (strcmp (var, known_vars[j]) == 0) break; } /* If this warning is reached, we might need to add this * variable to the block of * pv_environ_setenv (container_env, ., NULL) calls above */ if (j >= G_N_ELEMENTS (known_vars)) g_warning ("Extra environment variable %s set during container " "setup but not in known_vars; check logic", var); pv_environ_setenv (container_env, var, val); } g_warn_if_fail (g_strv_length (sharing_bwrap->envp) == 0); flatpak_bwrap_append_bwrap (bwrap, sharing_bwrap); } /* * Export most root directories, but not the ones that * "flatpak run --filesystem=host" would skip. * (See flatpak_context_export(), which might replace this function * later on.) * * If we are running inside Flatpak, we assume that any directory * that is made available in the root, and is not in dont_mount_in_root, * came in via --filesystem=host or similar and matches its equivalent * on the real root filesystem. */ static gboolean export_root_dirs_like_filesystem_host (FlatpakExports *exports, FlatpakFilesystemMode mode, GError **error) { g_autoptr(GDir) dir = NULL; const char *member = NULL; g_return_val_if_fail (exports != NULL, FALSE); g_return_val_if_fail ((unsigned) mode <= FLATPAK_FILESYSTEM_MODE_LAST, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); dir = g_dir_open ("/", 0, error); if (dir == NULL) return FALSE; for (member = g_dir_read_name (dir); member != NULL; member = g_dir_read_name (dir)) { g_autofree gchar *path = NULL; if (g_strv_contains (dont_mount_in_root, member)) continue; path = g_build_filename ("/", member, NULL); flatpak_exports_add_path_expose (exports, mode, path); } /* For parity with Flatpak's handling of --filesystem=host */ flatpak_exports_add_path_expose (exports, mode, "/run/media"); return TRUE; } /* * This function assumes that /run on the host is the same as in the * current namespace, so it won't work in Flatpak. */ static gboolean export_contents_of_run (FlatpakBwrap *bwrap, GError **error) { static const char *ignore[] = { "gfx", /* can be created by pressure-vessel */ "host", /* created by pressure-vessel */ "media", /* see export_root_dirs_like_filesystem_host() */ "pressure-vessel", /* created by pressure-vessel */ NULL }; g_autoptr(GDir) dir = NULL; const char *member = NULL; g_return_val_if_fail (bwrap != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); g_return_val_if_fail (!g_file_test ("/.flatpak-info", G_FILE_TEST_IS_REGULAR), FALSE); dir = g_dir_open ("/run", 0, error); if (dir == NULL) return FALSE; for (member = g_dir_read_name (dir); member != NULL; member = g_dir_read_name (dir)) { g_autofree gchar *path = NULL; if (g_strv_contains (ignore, member)) continue; path = g_build_filename ("/run", member, NULL); flatpak_bwrap_add_args (bwrap, "--bind", path, path, NULL); } return TRUE; } /* * Configure @exports and @bwrap to use the host operating system to * provide basically all directories. * * /app and /boot are excluded, but are assumed to be unnecessary. * * /dev, /proc and /sys are assumed to have been handled by * pv_bwrap_add_api_filesystems() already. */ gboolean pv_wrap_use_host_os (FlatpakExports *exports, FlatpakBwrap *bwrap, GError **error) { static const char * const export_os_mutable[] = { "/etc", "/tmp", "/var" }; gsize i; g_return_val_if_fail (exports != NULL, FALSE); g_return_val_if_fail (bwrap != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (!pv_bwrap_bind_usr (bwrap, "/", "/", "/", error)) return FALSE; for (i = 0; i < G_N_ELEMENTS (export_os_mutable); i++) { const char *dir = export_os_mutable[i]; if (g_file_test (dir, G_FILE_TEST_EXISTS)) flatpak_bwrap_add_args (bwrap, "--bind", dir, dir, NULL); } /* We do each subdirectory of /run separately, so that we can * always create /run/host and /run/pressure-vessel. */ if (!export_contents_of_run (bwrap, error)) return FALSE; /* This handles everything except: * * /app (should be unnecessary) * /boot (should be unnecessary) * /dev (handled by pv_bwrap_add_api_filesystems()) * /etc (handled by export_os_mutable above) * /proc (handled by pv_bwrap_add_api_filesystems()) * /root (should be unnecessary) * /run (handled by export_contents_of_run() above) * /sys (handled by pv_bwrap_add_api_filesystems()) * /tmp (handled by export_os_mutable above) * /usr, /lib, /lib32, /lib64, /bin, /sbin * (all handled by pv_bwrap_bind_usr() above) * /var (handled by export_os_mutable above) */ if (!export_root_dirs_like_filesystem_host (exports, FLATPAK_FILESYSTEM_MODE_READ_WRITE, error)) return FALSE; return TRUE; } const char * pv_wrap_get_steam_app_id (const char *from_command_line) { const char *value; if (from_command_line != NULL) return from_command_line; if ((value = g_getenv ("STEAM_COMPAT_APP_ID")) != NULL) return value; if ((value = g_getenv ("SteamAppId")) != NULL) return value; return NULL; } /* * Try to move the current process into a scope defined by the given * Steam app ID. If that's not possible, ignore. */ void pv_wrap_move_into_scope (const char *steam_app_id) { g_autoptr(GError) local_error = NULL; if (steam_app_id != NULL) { if (steam_app_id[0] == '\0') steam_app_id = NULL; else if (strcmp (steam_app_id, "0") == 0) steam_app_id = NULL; } if (steam_app_id != NULL) flatpak_run_in_transient_unit ("steam", "app", steam_app_id, &local_error); else flatpak_run_in_transient_unit ("steam", "", "unknown", &local_error); if (local_error != NULL) g_debug ("Cannot move into a systemd scope: %s", local_error->message); } static void append_preload_internal (GPtrArray *argv, const char *option, const char *original_path, GStrv env, PvAppendPreloadFlags flags, PvRuntime *runtime, FlatpakExports *exports) { gboolean flatpak_subsandbox = ((flags & PV_APPEND_PRELOAD_FLAGS_FLATPAK_SUBSANDBOX) != 0); if (runtime != NULL && (g_str_has_prefix (original_path, "/usr/") || g_str_has_prefix (original_path, "/lib") || (flatpak_subsandbox && g_str_has_prefix (original_path, "/app/")))) { g_autofree gchar *adjusted_path = NULL; const char *target = flatpak_subsandbox ? "/run/parent" : "/run/host"; adjusted_path = g_build_filename (target, original_path, NULL); g_debug ("%s -> %s", original_path, adjusted_path); g_ptr_array_add (argv, g_strdup_printf ("%s=%s", option, adjusted_path)); } else { g_debug ("%s -> unmodified", original_path); g_ptr_array_add (argv, g_strdup_printf ("%s=%s", option, original_path)); if (exports != NULL && original_path[0] == '/') { const gchar *steam_path = g_environ_getenv (env, "STEAM_COMPAT_CLIENT_INSTALL_PATH"); if (steam_path != NULL && flatpak_has_path_prefix (original_path, steam_path)) { g_debug ("Skipping exposing \"%s\" because it is located " "under the Steam client install path that we " "bind by default", original_path); } else { g_debug ("%s needs adding to exports", original_path); flatpak_exports_add_path_expose (exports, FLATPAK_FILESYSTEM_MODE_READ_ONLY, original_path); } } } } /** * pv_wrap_append_preload: * @argv: (element-type filename): Array of command-line options to populate * @variable: (type filename): Environment variable from which this * preload module was taken, either `LD_AUDIT` or `LD_PRELOAD` * @option: (type filename): Command-line option to add to @argv, * either `--ld-audit` or `--ld-preload` * @preload: (type filename): Path of preloadable module in current * namespace, possibly including special ld.so tokens such as `$LIB`, * or basename of a preloadable module to be found in the standard * library search path * @env: (array zero-terminated=1) (element-type filename): Environment * variables to be used instead of `environ` * @flags: Flags to adjust behaviour * @runtime: (nullable): Runtime to be used in container * @exports: (nullable): Used to configure extra paths that need to be * exported into the container * * Adjust @preload to be valid for the container and append it * to @argv. */ void pv_wrap_append_preload (GPtrArray *argv, const char *variable, const char *option, const char *preload, GStrv env, PvAppendPreloadFlags flags, PvRuntime *runtime, FlatpakExports *exports) { SrtLoadableKind kind; SrtLoadableFlags loadable_flags; g_return_if_fail (argv != NULL); g_return_if_fail (option != NULL); g_return_if_fail (preload != NULL); g_return_if_fail (runtime == NULL || PV_IS_RUNTIME (runtime)); if (strstr (preload, "gtk3-nocsd") != NULL) { g_warning ("Disabling gtk3-nocsd %s: it is known to cause crashes.", variable); return; } if ((flags & PV_APPEND_PRELOAD_FLAGS_REMOVE_GAME_OVERLAY) && g_str_has_suffix (preload, "/gameoverlayrenderer.so")) { g_info ("Disabling Steam Overlay: %s", preload); return; } kind = _srt_loadable_classify (preload, &loadable_flags); switch (kind) { case SRT_LOADABLE_KIND_BASENAME: /* Basenames can't have dynamic string tokens. */ g_warn_if_fail ((loadable_flags & SRT_LOADABLE_FLAGS_DYNAMIC_TOKENS) == 0); /* Ideally we would handle this more cleverly, but for now, * pass preload through to the container without adjustment. * It isn't useful to add it to the FlatpakExports, because it * isn't an absolute filename. */ append_preload_internal (argv, option, preload, env, flags, runtime, NULL); break; case SRT_LOADABLE_KIND_PATH: /* Ideally we would handle this more cleverly, but for now, * assume preload is a filename: adjust the beginning if it's * in /usr/ etc., and add it to the exports if absolute. */ append_preload_internal (argv, option, preload, env, flags, runtime, exports); break; case SRT_LOADABLE_KIND_ERROR: default: /* Empty string or similar syntactically invalid token: * ignore with a warning. Since steam-runtime-tools!352 and * steamlinuxruntime!64, the wrapper scripts don't give us * an empty argument any more. */ g_warning ("Ignoring invalid loadable module \"%s\"", preload); break; } }