From 744fecdad46b4fc1c4a472f698a2f8f2dae2e844 Mon Sep 17 00:00:00 2001 From: Simon McVittie <smcv@collabora.com> Date: Thu, 18 Jul 2024 15:29:54 +0100 Subject: [PATCH] pv-runtime: Try to mount /usr/${LIB}/gconv into container Previously we assumed that the hard-coded directory used to dlopen gconv modules matched the realpath() of the directory containing libc.so.6, disregarding any compatibility symlinks: for example, on Red Hat, 64-bit gconv modules are in /usr/lib64/gconv, and on Debian they're in /usr/lib/x86_64-linux-gnu/gconv. However, this turns out not to be true on Void Linux, where /usr/lib64 -> lib is a non-canonical path, but the hard-coded path that will be used to load iconv modules is /usr/lib64/gconv anyway. We cannot simply set GCONV_PATH, because apparently setting `GCONV_PATH=/usr/lib/gconv:/usr/lib32/gconv` will break 32-bit applications: unlike the equivalent with LD_LIBRARY_PATH, a 32-bit application would try and fail to load the 64-bit module from /usr/lib/gconv (it will fail to load because its word size does not match the word size of the process), and then will not fall back to /usr/lib32/gconv as one might expect. Luckily, on Void Linux, the libdl dynamic string token ${LIB} has been chosen such that the correct path for each architecture's gconv modules is /usr/${LIB}/gconv. We can use this as an additional guess at the necessary mount point for the gconv modules, mounting /usr/lib/gconv onto both /usr/lib/gconv and /usr/lib64/gconv - that way, it doesn't matter whether the path hard-coded in glibc matches our old heuristic or our new heuristic, because either one will work. steamrt/tasks#477, ValveSoftware/steam-runtime#680 Signed-off-by: Simon McVittie <smcv@collabora.com> --- pressure-vessel/runtime.c | 91 +++++++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/pressure-vessel/runtime.c b/pressure-vessel/runtime.c index bd119e12c..d62123304 100644 --- a/pressure-vessel/runtime.c +++ b/pressure-vessel/runtime.c @@ -5430,10 +5430,46 @@ collect_core_libraries_patterns (GPtrArray *patterns) exact_sonames[i])); } +/* + * @self: The runtime + * @pathp: (inout): A candidate path for /usr/lib/gconv + * or similar in the graphics stack provider, which may be "stolen" + * on success + * @gconv_in_provider: Map { owned string => itself } representing a set + * of paths to /usr/lib/gconv or equivalent in the graphics stack provider + * + * Returns: %TRUE if the path `*pathp` was suitable + */ +static gboolean +pv_runtime_try_gconv_dir (PvRuntime *self, + gchar **pathp, + GHashTable *gconv_in_provider) +{ + /* Quietly short-circuit if already added, for example in the common case + * where the path we derived from the realpath() of libc.so.6 matches + * /usr/${LIB}/gconv */ + if (g_hash_table_contains (gconv_in_provider, *pathp)) + return TRUE; + + g_debug ("Checking for gconv in %s", *pathp); + + if (_srt_sysroot_test (self->provider->in_current_ns, *pathp, + SRT_RESOLVE_FLAGS_MUST_BE_DIRECTORY, NULL)) + { + g_debug ("... yes"); + g_hash_table_add (gconv_in_provider, g_steal_pointer (pathp)); + return TRUE; + } + + g_debug ("... no"); + return FALSE; +} + /* * pv_runtime_collect_libc_family: * @self: The runtime * @arch: Architecture of @libc_symlink + * @system_info: A #SrtSystemInfo * @bwrap: * @libc_symlink: The symlink created by capsule-capture-libs, * relative to /overrides. @@ -5448,6 +5484,7 @@ collect_core_libraries_patterns (GPtrArray *patterns) static gboolean pv_runtime_collect_libc_family (PvRuntime *self, RuntimeArchitecture *arch, + SrtSystemInfo *system_info, FlatpakBwrap *bwrap, const char *libc_symlink, const char *ld_so_in_runtime, @@ -5468,7 +5505,9 @@ pv_runtime_collect_libc_family (PvRuntime *self, }; G_GNUC_UNUSED g_autoptr(SrtProfilingTimer) libc_timer = _srt_profiling_start ("glibc"); + g_autoptr(GError) local_error = NULL; g_autofree char *libc_target = NULL; + g_autofree char *libdl_lib = NULL; g_return_val_if_fail (self->provider != NULL, FALSE); g_return_val_if_fail (bwrap != NULL || self->mutable_sysroot != NULL, FALSE); @@ -5483,6 +5522,31 @@ pv_runtime_collect_libc_family (PvRuntime *self, NULL, patterns, G_N_ELEMENTS (patterns), error)) return FALSE; + libdl_lib = srt_system_info_dup_libdl_lib (system_info, + arch->details->tuple, + &local_error); + + if (libdl_lib == NULL) + { + g_debug ("Unable to determine libdl ${LIB}: %s", local_error->message); + g_clear_error (&local_error); + } + else + { + g_autofree gchar *dir = g_build_filename ("/usr", libdl_lib, "gconv", NULL); + + /* On some host OSs, the hard-coded path used to dlopen gconv modules + * does not actually match the realpath() of the directory containing + * libc.so.6 (for example on Void Linux, /usr/lib64 -> lib is a symlink, + * but 64-bit gconv modules are loaded via /usr/lib64 and not /usr/lib). + * Use /usr/${LIB}/gconv as a better guess at what the hard-coded path + * might be. For example, this resolves to /usr/lib64/gconv on + * Void Linux, which would mean we mount both /usr/lib64/gconv (here) + * and /usr/lib/gconv (below), ensuring that whichever one glibc + * actually wants to load, it'll work. */ + pv_runtime_try_gconv_dir (self, &dir, gconv_in_provider); + } + libc_target = glnx_readlinkat_malloc (self->overrides_fd, libc_symlink, NULL, NULL); if (libc_target != NULL) @@ -5532,15 +5596,10 @@ pv_runtime_collect_libc_family (PvRuntime *self, gconv_prefix = "/"; gconv_dir_in_provider = g_build_filename (gconv_prefix, dir, "gconv", NULL); - g_debug ("Checking for gconv in %s", gconv_dir_in_provider); - if (_srt_sysroot_test (self->provider->in_current_ns, - gconv_dir_in_provider, - SRT_RESOLVE_FLAGS_MUST_BE_DIRECTORY, NULL)) - { - g_hash_table_add (gconv_in_provider, g_steal_pointer (&gconv_dir_in_provider)); - found = TRUE; - } + if (pv_runtime_try_gconv_dir (self, &gconv_dir_in_provider, + gconv_in_provider)) + found = TRUE; if (!found) { @@ -5564,16 +5623,9 @@ pv_runtime_collect_libc_family (PvRuntime *self, g_clear_pointer (&gconv_dir_in_provider, g_free); gconv_dir_in_provider = g_build_filename (gconv_prefix, dir, "gconv", NULL); - g_debug ("Checking for gconv (after removing hwcaps subdirectories) in %s", - gconv_dir_in_provider); - - if (_srt_sysroot_test (self->provider->in_current_ns, - gconv_dir_in_provider, - SRT_RESOLVE_FLAGS_MUST_BE_DIRECTORY, NULL)) - { - g_hash_table_add (gconv_in_provider, g_steal_pointer (&gconv_dir_in_provider)); - found = TRUE; - } + if (pv_runtime_try_gconv_dir (self, &gconv_dir_in_provider, + gconv_in_provider)) + found = TRUE; } if (!found) @@ -7378,7 +7430,8 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self, if (fstatat (self->overrides_fd, libc_symlink, &stat_buf, AT_SYMLINK_NOFOLLOW) == 0 && S_ISLNK (stat_buf.st_mode)) { - if (!pv_runtime_collect_libc_family (self, arch, bwrap, + if (!pv_runtime_collect_libc_family (self, arch, + arch_system_info, bwrap, libc_symlink, ld_so_in_runtime, gconv_in_provider, error)) -- GitLab