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