diff --git a/pressure-vessel/adverb.1.md b/pressure-vessel/adverb.1.md index 93f791b80769c6708cbff886c10e413da3d65c99..c932d9f52ac417ea02fd78a733320b852cbfc2ab 100644 --- a/pressure-vessel/adverb.1.md +++ b/pressure-vessel/adverb.1.md @@ -18,6 +18,8 @@ pressure-vessel-adverb - wrap processes in various ways [**--[no-]exit-with-parent**] [**--fd** *FD*...] [**--[no-]generate-locales**] +[**--ld-audit** *MODULE*...] +[**--ld-preload** *MODULE*...] [**--pass-fd** *FD*...] [**--shell** **none**|**after**|**fail**|**instead**] [**--subreaper**] @@ -27,6 +29,7 @@ pressure-vessel-adverb - wrap processes in various ways [**--[no-]wait**] [**--[no-]write**] **--lock-file** *FILENAME*...] + [**--verbose**] [**--**] *COMMAND* [*ARGUMENTS...*] @@ -68,6 +71,16 @@ exit status. **LOCPATH** environment variable. **--no-generate-locales** disables this behaviour, and is the default. +**--ld-audit** *MODULE* +: Add *MODULE* to **LD_AUDIT** before executing *COMMAND*. + +**--ld-preload** *MODULE* +: Add *MODULE* to **LD_PRELOAD** before executing *COMMAND*. + Some adjustments may be performed to the provided *MODULE*, e.g. + multiple preloads of gameoverlayrenderer.so for different ABIs may be + joined together into a single path by leveraging the dynamic linker + token expansion feature. + **--lock-file** *FILENAME* : Lock the file *FILENAME* according to the most recently seen **--[no-]create**, **--[no-]wait** and **--[no-]write** options, diff --git a/pressure-vessel/adverb.c b/pressure-vessel/adverb.c index 4e9ab7696e5d6f70adbfca46542ec2a312ce3344..b8b6325ac5ec3453ddd855ca07d5f50569c31cac 100644 --- a/pressure-vessel/adverb.c +++ b/pressure-vessel/adverb.c @@ -3,7 +3,7 @@ * The lock is basically flock(1), but using fcntl locks compatible with * those used by bubblewrap and Flatpak. * - * Copyright © 2019-2020 Collabora Ltd. + * Copyright © 2019-2021 Collabora Ltd. * * SPDX-License-Identifier: LGPL-2.1-or-later * @@ -37,6 +37,7 @@ #include "steam-runtime-tools/glib-backports-internal.h" #include "steam-runtime-tools/profiling-internal.h" +#include "steam-runtime-tools/steam-runtime-tools.h" #include "steam-runtime-tools/utils-internal.h" #include "libglnx/libglnx.h" @@ -64,12 +65,52 @@ static gboolean opt_wait = FALSE; static gboolean opt_write = FALSE; static GPid child_pid; +typedef struct +{ + const gchar *abi; + const gchar *directory_name; +} AbiTuplesDetails; + +static AbiTuplesDetails abi_tuples_details[] = +{ + { SRT_ABI_I386, "ubuntu12_32" }, + { SRT_ABI_X86_64, "ubuntu12_64" }, +}; + typedef struct { int original_stdout_fd; int *pass_fds; } ChildSetupData; +typedef struct +{ + gchar *root_path; + gchar *platform_token_path; + GHashTable *abi_paths; +} LibTempDirs; + +static PreloadModule opt_preload_modules[] = +{ + { "LD_AUDIT", NULL, NULL }, + { "LD_PRELOAD", NULL, NULL }, +}; + +static void +lib_temp_dirs_free (LibTempDirs *lib_temp_dirs) +{ + if (lib_temp_dirs->root_path != NULL) + _srt_rm_rf (lib_temp_dirs->root_path); + + g_clear_pointer (&lib_temp_dirs->root_path, g_free); + g_clear_pointer (&lib_temp_dirs->platform_token_path, g_free); + g_clear_pointer (&lib_temp_dirs->abi_paths, g_hash_table_unref); + + g_free (lib_temp_dirs); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (LibTempDirs, lib_temp_dirs_free) + static void child_setup_cb (gpointer user_data) { @@ -175,6 +216,28 @@ opt_fd_cb (const char *name, return TRUE; } +static gboolean +opt_ld_audit_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + pv_append_preload_module (opt_preload_modules, G_N_ELEMENTS (opt_preload_modules), + "LD_AUDIT", value, FALSE); + return TRUE; +} + +static gboolean +opt_ld_preload_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + pv_append_preload_module (opt_preload_modules, G_N_ELEMENTS (opt_preload_modules), + "LD_PRELOAD", value, FALSE); + return TRUE; +} + static gboolean opt_pass_fd_cb (const char *name, const char *value, @@ -402,6 +465,52 @@ run_helper_sync (const char *cwd, return ret; } +static gboolean +generate_lib_temp_dirs (LibTempDirs *lib_temp_dirs, + GError **error) +{ + g_autoptr(SrtSystemInfo) info = NULL; + gsize i; + + g_return_val_if_fail (lib_temp_dirs != NULL, FALSE); + g_return_val_if_fail (lib_temp_dirs->abi_paths == NULL, FALSE); + + info = srt_system_info_new (NULL); + + lib_temp_dirs->root_path = g_dir_make_tmp ("pressure-vessel-libs-XXXXXX", error); + if (lib_temp_dirs->root_path == NULL) + return glnx_prefix_error (error, + "Cannot create temporary directory for platform specific libraries"); + + lib_temp_dirs->platform_token_path = g_build_filename (lib_temp_dirs->root_path, + "${PLATFORM}", NULL); + + lib_temp_dirs->abi_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + for (i = 0; i < G_N_ELEMENTS (abi_tuples_details); i++) + { + g_autofree gchar *libdl_platform = NULL; + g_autofree gchar *abi_path = NULL; + libdl_platform = srt_system_info_dup_libdl_platform (info, + abi_tuples_details[i].abi, + error); + if (!libdl_platform) + return glnx_prefix_error (error, + "Unknown expansion of the dl string token $PLATFORM"); + + abi_path = g_build_filename (lib_temp_dirs->root_path, libdl_platform, NULL); + + if (g_mkdir (abi_path, 0700) != 0) + return glnx_throw_errno_prefix (error, "Unable to create \"%s\"", abi_path); + + g_hash_table_insert (lib_temp_dirs->abi_paths, + g_strdup (abi_tuples_details[i].abi), + g_steal_pointer (&abi_path)); + } + + return TRUE; +} + static gboolean generate_locales (gchar **locpath_out, GError **error) @@ -510,6 +619,12 @@ terminate_child_cb (int signum) } } +static void +append_to_search_path (const gchar *item, GString *search_path) +{ + pv_search_path_append (search_path, item); +} + static GOptionEntry options[] = { { "batch", '\0', @@ -567,6 +682,17 @@ static GOptionEntry options[] = "Exit unsuccessfully if a lock-file is busy [default].", NULL }, + { "ld-audit", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, &opt_ld_audit_cb, + "Add MODULE to LD_AUDIT before executing COMMAND.", + "MODULE" }, + { "ld-preload", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, &opt_ld_preload_cb, + "Add MODULE to LD_PRELOAD before executing COMMAND. Some adjustments " + "may be performed, e.g. joining together multiple gameoverlayrenderer.so " + "preloads into a single path by leveraging the dynamic linker token expansion", + "MODULE" }, + { "lock-file", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, opt_lock_file_cb, "Open the given file and lock it, affected by options appearing " @@ -644,6 +770,9 @@ main (int argc, sigset_t mask; struct sigaction terminate_child_action = {}; g_autoptr(FlatpakBwrap) wrapped_command = NULL; + g_autoptr(LibTempDirs) lib_temp_dirs = g_new0 (LibTempDirs, 1); + gboolean all_abi_paths_created = TRUE; + gsize i; sigemptyset (&mask); sigaddset (&mask, SIGCHLD); @@ -807,6 +936,113 @@ main (int argc, flatpak_bwrap_append_argsv (wrapped_command, &argv[1], argc - 1); flatpak_bwrap_finish (wrapped_command); + if (!generate_lib_temp_dirs (lib_temp_dirs, error)) + { + all_abi_paths_created = FALSE; + g_warning ("%s", local_error->message); + g_clear_error (error); + } + + for (i = 0; i < G_N_ELEMENTS (opt_preload_modules); i++) + { + const char *variable = opt_preload_modules[i].variable; + const GPtrArray *values = opt_preload_modules[i].original_values; + g_autofree gchar *platform_overlay_path = NULL; + g_autoptr(GPtrArray) search_path = NULL; + g_autoptr(GString) buffer = NULL; + gsize j; + + if (values == NULL) + continue; + + search_path = g_ptr_array_new_with_free_func (g_free); + buffer = g_string_new (""); + + if (g_strcmp0 (variable, "LD_PRELOAD") != 0 || !all_abi_paths_created) + { + /* Currently we perform adjustments only for LD_PRELOAD. + * Also if we were not able to create the temporary libraries + * directories, we simply avoid any adjustment and try to continue */ + for (j = 0; j < values->len; j++) + { + const char *preload = g_ptr_array_index (values, j); + g_assert (preload != NULL); + if (*preload == '\0') + continue; + + pv_search_path_append (buffer, preload); + } + + if (buffer->len != 0) + flatpak_bwrap_set_env (wrapped_command, variable, buffer->str, TRUE); + + /* No adjustment needed, continue to the next preload module */ + continue; + } + + g_debug ("Adjusting %s...", variable); + platform_overlay_path = g_build_filename (lib_temp_dirs->platform_token_path, + "gameoverlayrenderer.so", NULL); + for (j = 0; j < values->len; j++) + { + const char *preload = g_ptr_array_index (values, j); + g_assert (preload != NULL); + if (*preload == '\0') + continue; + + if (g_str_has_suffix (preload, "/gameoverlayrenderer.so")) + { + g_autofree gchar *link = NULL; + + for (gsize jj = 0; jj < G_N_ELEMENTS (abi_tuples_details); jj++) + { + g_autofree gchar *expected_suffix = g_strdup_printf ("/%s/gameoverlayrenderer.so", + abi_tuples_details[jj].directory_name); + if (g_str_has_suffix (preload, expected_suffix)) + { + link = g_build_filename (g_hash_table_lookup (lib_temp_dirs->abi_paths, + abi_tuples_details[jj].abi), + "gameoverlayrenderer.so", NULL); + break; + } + } + + if (link == NULL) + { + g_ptr_array_add (search_path, g_strdup (preload)); + g_debug ("Preloading gameoverlayrenderer.so from an unexpected path \"%s\", " + "just leave it as is without adjusting", preload); + continue; + } + + if (symlink (preload, link) != 0) + { + /* This might also happen if the same gameoverlayrenderer.so + * was given multiple times. We don't expect this under normal + * circumstances, so we bail out. */ + glnx_throw_errno_prefix (error, + "Unable to create symlink %s -> %s", + link, preload); + goto out; + } + g_debug ("created symlink %s -> %s", link, preload); + + if (!g_ptr_array_find_with_equal_func (search_path, platform_overlay_path, + g_str_equal, NULL)) + g_ptr_array_add (search_path, g_strdup (platform_overlay_path)); + } + else + { + g_ptr_array_add (search_path, g_strdup (preload)); + } + } + + g_ptr_array_foreach (search_path, (GFunc) append_to_search_path, buffer); + + if (buffer->len != 0) + flatpak_bwrap_set_env (wrapped_command, variable, buffer->str, TRUE); + } + if (opt_generate_locales) { G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) profiling = @@ -856,8 +1092,6 @@ main (int argc, if (opt_verbose) { - gsize i; - g_message ("Command-line:"); for (i = 0; i < wrapped_command->argv->len - 1; i++) @@ -904,8 +1138,6 @@ main (int argc, * our stdin and stderr open. */ if (child_setup_data.pass_fds != NULL) { - gsize i; - for (i = 0; child_setup_data.pass_fds[i] > -1; i++) { if (child_setup_data.pass_fds[i] > 2) @@ -968,6 +1200,8 @@ out: if (locales_temp_dir != NULL) _srt_rm_rf (locales_temp_dir); + pv_preload_modules_free (opt_preload_modules, G_N_ELEMENTS (opt_preload_modules)); + if (local_error != NULL) pv_log_failure ("%s", local_error->message); diff --git a/pressure-vessel/utils.c b/pressure-vessel/utils.c index 322ca0ff62632909dece050f3323f2a4225455f9..d94c2819d09b9b2c69f125cdffc081350a7bc306 100644 --- a/pressure-vessel/utils.c +++ b/pressure-vessel/utils.c @@ -994,3 +994,73 @@ pv_delete_dangling_symlink (int dirfd, debug_path, name, g_strerror (saved_errno)); } } + +/** + * pv_append_preload_module: + * @preload_modules: #PreloadModule objects array + * @n_preload_modules: Size of @preload_modules + * @variable: Variable where @value will be appended to + * @value: Value that needs to be appended + * @adjusted_value: If #TRUE, @value will be appended to the adjusted values + * array, otherwise it will be appended to the original values array + * + * Append @value to the #PreloadModule whose "variable" is the same as the + * provided @variable. + */ +void +pv_append_preload_module (PreloadModule preload_modules[], + gsize n_preload_modules, + const char *variable, + const char *value, + gboolean adjusted_value) +{ + gsize i; + + g_return_if_fail (preload_modules != NULL); + g_return_if_fail (n_preload_modules != 0); + g_return_if_fail (variable != NULL); + + for (i = 0; i < n_preload_modules; i++) + { + if (strcmp (variable, preload_modules[i].variable) == 0) + break; + } + + if (i == n_preload_modules) + g_return_if_reached (); + + if (adjusted_value) + { + if (preload_modules[i].adjusted_values == NULL) + preload_modules[i].adjusted_values = g_ptr_array_new_with_free_func (g_free); + + g_ptr_array_add (preload_modules[i].adjusted_values, g_strdup (value)); + } + else + { + if (preload_modules[i].original_values == NULL) + preload_modules[i].original_values = g_ptr_array_new_with_free_func (g_free); + + g_ptr_array_add (preload_modules[i].original_values, g_strdup (value)); + } +} + +/** + * pv_preload_modules_free: + * @preload_modules: #PreloadModule objects array + * @n_preload_modules: Size of @preload_modules + * + * Free the values stored in @preload_modules. + */ +void +pv_preload_modules_free (PreloadModule preload_modules[], + gsize n_preload_modules) +{ + gsize i; + + for (i = 0; i < n_preload_modules; i++) + { + g_clear_pointer (&preload_modules[i].original_values, g_ptr_array_unref); + g_clear_pointer (&preload_modules[i].adjusted_values, g_ptr_array_unref); + } +} diff --git a/pressure-vessel/utils.h b/pressure-vessel/utils.h index 97e1a0a8d3faafa034df6ee0d625af869f5b8875..3693956d7a6a25c7268ace49c5f72326adf6b9d1 100644 --- a/pressure-vessel/utils.h +++ b/pressure-vessel/utils.h @@ -44,6 +44,13 @@ #define pv_log_failure(...) \ g_log (G_LOG_DOMAIN, PV_LOG_LEVEL_FAILURE, __VA_ARGS__) +typedef struct +{ + const char *variable; + GPtrArray *original_values; + GPtrArray *adjusted_values; +} PreloadModule; + void pv_get_current_dirs (gchar **cwd_p, gchar **cwd_l); @@ -81,3 +88,13 @@ void pv_set_up_logging (gboolean opt_verbose); void pv_delete_dangling_symlink (int dirfd, const char *debug_path, const char *name); + +void pv_append_preload_module (PreloadModule preload_modules[], + gsize n_preload_modules, + const char *variable, + const char *value, + gboolean adjusted_value); + +void +pv_preload_modules_free (PreloadModule preload_modules[], + gsize n_preload_modules); diff --git a/pressure-vessel/wrap.c b/pressure-vessel/wrap.c index 2bcb3d5df2f3bad20ea463d2358241e7c0864acc..e675f2765479b2d04d7c4c8661c59d4bd9d62031 100644 --- a/pressure-vessel/wrap.c +++ b/pressure-vessel/wrap.c @@ -713,40 +713,12 @@ static gboolean opt_test = FALSE; static PvTerminal opt_terminal = PV_TERMINAL_AUTO; static char *opt_write_final_argv = NULL; -typedef struct -{ - const char *variable; - GPtrArray *values; -} PreloadModule; - static PreloadModule opt_preload_modules[] = { - { "LD_AUDIT", NULL }, - { "LD_PRELOAD", NULL }, + { "LD_AUDIT", NULL, NULL }, + { "LD_PRELOAD", NULL, NULL }, }; -static gboolean -append_preload_module (const char *variable, - const char *value, - GError **error) -{ - gsize i; - - for (i = 0; i < G_N_ELEMENTS (opt_preload_modules); i++) - { - if (strcmp (variable, opt_preload_modules[i].variable) == 0) - break; - } - - g_return_val_if_fail (i < G_N_ELEMENTS (opt_preload_modules), FALSE); - - if (opt_preload_modules[i].values == NULL) - opt_preload_modules[i].values = g_ptr_array_new_with_free_func (g_free); - - g_ptr_array_add (opt_preload_modules[i].values, g_strdup (value)); - return TRUE; -} - /* * check_main_program: * @@ -767,7 +739,9 @@ opt_host_ld_preload_cb (const gchar *option_name, { g_warning ("%s is deprecated, use --ld-preload=%s instead", option_name, value); - return append_preload_module ("LD_PRELOAD", value, error); + pv_append_preload_module (opt_preload_modules, G_N_ELEMENTS (opt_preload_modules), + "LD_PRELOAD", value, FALSE); + return TRUE; } static gboolean @@ -776,7 +750,9 @@ opt_ld_audit_cb (const gchar *option_name, gpointer data, GError **error) { - return append_preload_module ("LD_AUDIT", value, error); + pv_append_preload_module (opt_preload_modules, G_N_ELEMENTS (opt_preload_modules), + "LD_AUDIT", value, FALSE); + return TRUE; } static gboolean @@ -785,7 +761,9 @@ opt_ld_preload_cb (const gchar *option_name, gpointer data, GError **error) { - return append_preload_module ("LD_PRELOAD", value, error); + pv_append_preload_module (opt_preload_modules, G_N_ELEMENTS (opt_preload_modules), + "LD_PRELOAD", value, FALSE); + return TRUE; } static gboolean @@ -2117,13 +2095,10 @@ main (int argc, for (i = 0; i < G_N_ELEMENTS (opt_preload_modules); i++) { const char *variable = opt_preload_modules[i].variable; - const GPtrArray *values = opt_preload_modules[i].values; - g_autoptr(GString) adjusted = NULL; + const GPtrArray *values = opt_preload_modules[i].original_values; gsize len; gsize j; - adjusted = g_string_new (""); - g_debug ("Adjusting %s...", variable); if (values == NULL) @@ -2168,7 +2143,9 @@ main (int argc, adjusted_path = g_build_filename ("/run/parent", preload, NULL); g_debug ("%s -> %s", preload, adjusted_path); - pv_search_path_append (adjusted, adjusted_path); + pv_append_preload_module (opt_preload_modules, + G_N_ELEMENTS (opt_preload_modules), + variable, adjusted_path, TRUE); if (g_str_has_suffix (preload, "/libshared-library-guard.so")) { @@ -2189,7 +2166,9 @@ main (int argc, else { g_debug ("%s -> unmodified", preload); - pv_search_path_append (adjusted, preload); + pv_append_preload_module (opt_preload_modules, + G_N_ELEMENTS (opt_preload_modules), + variable, preload, TRUE); } /* No FlatpakExports here: any file not in /usr or /app that @@ -2211,7 +2190,9 @@ main (int argc, /* When using a runtime we can't write to /usr/ or /libQUAL/, * so redirect this preloaded module to the corresponding * location in /run/host. */ - pv_search_path_append (adjusted, adjusted_path); + pv_append_preload_module (opt_preload_modules, + G_N_ELEMENTS (opt_preload_modules), + variable, adjusted_path, TRUE); } else { @@ -2232,7 +2213,9 @@ main (int argc, preload); } - pv_search_path_append (adjusted, preload); + pv_append_preload_module (opt_preload_modules, + G_N_ELEMENTS (opt_preload_modules), + variable, preload, TRUE); } } else @@ -2240,26 +2223,6 @@ main (int argc, g_info ("%s module '%s' does not exist", variable, preload); } } - - if (bwrap != NULL) - { - /* If we adjusted the module paths from the one provided by the host - * to something that is valid in the container, we shouldn't add them to - * the bwrap envp. Otherwise when we call "pv_bwrap_execve()" we will - * create an environment that tries to preload libraries that are not - * available, until it actually executes "bwrap". - * This can be avoided by using the bwrap option "--setenv" instead. */ - if (adjusted->len != 0) - flatpak_bwrap_add_args (bwrap, "--setenv", variable, - adjusted->str, NULL); - } - else - { - if (adjusted->len != 0) - pv_environ_setenv (container_env, variable, adjusted->str); - else - pv_environ_setenv (container_env, variable, NULL); - } } if (flatpak_subsandbox == NULL) @@ -2662,6 +2625,37 @@ main (int argc, break; } + for (i = 0; i < G_N_ELEMENTS (opt_preload_modules); i++) + { + const gchar *variable = opt_preload_modules[i].variable; + const GPtrArray *values = opt_preload_modules[i].adjusted_values; + const gchar *option = NULL; + gsize j; + + if (g_strcmp0 (variable, "LD_AUDIT") == 0) + { + option = "--ld-audit"; + } + else if (g_strcmp0 (variable, "LD_PRELOAD") == 0) + { + option = "--ld-preload"; + } + else + { + /* Programming error: this logic needs to be updated + * to handle this new preload module */ + g_critical ("Unhandled preload module %s", variable); + continue; + } + + if (values == NULL) + continue; + + for (j = 0; j < values->len; j++) + flatpak_bwrap_add_arg_printf (adverb_argv, "%s=%s", option, + (gchar *)g_ptr_array_index (values, j)); + } + if (opt_verbose) flatpak_bwrap_add_arg (adverb_argv, "--verbose"); @@ -2795,8 +2789,7 @@ out: if (local_error != NULL) pv_log_failure ("%s", local_error->message); - for (i = 0; i < G_N_ELEMENTS (opt_preload_modules); i++) - g_clear_pointer (&opt_preload_modules[i].values, g_ptr_array_unref); + pv_preload_modules_free (opt_preload_modules, G_N_ELEMENTS (opt_preload_modules)); g_clear_pointer (&opt_env_if_host, g_strfreev); g_clear_pointer (&opt_freedesktop_app_id, g_free);