From 39a0cacd241814a14fc968e05ff4d1e05a606254 Mon Sep 17 00:00:00 2001
From: Ludovico de Nittis <ludovico.denittis@collabora.com>
Date: Tue, 16 Mar 2021 16:21:22 +0100
Subject: [PATCH] runtime: Create library aliases in overrides

If a library has different SONAMEs, called aliases in
"steam-runtime-abi.json", it might lead to errors when a game tries to
load a SONAME that is not the "real" library SONAME (the DT_SONAME that
objdump shows). Because `ld.so.cache` will not consider the aliases when
searching for the right match.

To workaround this problem we create a directory called "aliases" that
contains symlinks of the libraries aliases that we know about.

By adding these directories, one for every multiarch, in the
`LD_LIBRARY_PATH`, we ensure that even games that were linked to an
alias will be able to find the library they were looking for.

Helps: https://github.com/ValveSoftware/steam-runtime/issues/246
Fixes: T26816

Signed-off-by: Ludovico de Nittis <ludovico.denittis@collabora.com>
---
 pressure-vessel/runtime.c | 129 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 129 insertions(+)

diff --git a/pressure-vessel/runtime.c b/pressure-vessel/runtime.c
index a03e5921e..c3df151c7 100644
--- a/pressure-vessel/runtime.c
+++ b/pressure-vessel/runtime.c
@@ -26,6 +26,7 @@
 /* Include these before steam-runtime-tools.h so that their backport of
  * G_DEFINE_AUTOPTR_CLEANUP_FUNC will be visible to it */
 #include "steam-runtime-tools/glib-backports-internal.h"
+#include "steam-runtime-tools/json-glib-backports-internal.h"
 #include "libglnx/libglnx.h"
 
 #include <steam-runtime-tools/steam-runtime-tools.h>
@@ -63,6 +64,7 @@ struct _PvRuntime
   GStrv original_environ;
 
   gchar *libcapsule_knowledge;
+  gchar *runtime_abi_json;
   gchar *variable_dir;
   gchar *mutable_sysroot;
   gchar *tmpdir;
@@ -291,6 +293,7 @@ typedef struct
 {
   gsize multiarch_index;
   const MultiarchDetails *details;
+  gchar *aliases_in_current_namespace;
   gchar *capsule_capture_libs_basename;
   gchar *capsule_capture_libs;
   gchar *libdir_in_current_namespace;
@@ -326,6 +329,9 @@ runtime_architecture_init (RuntimeArchitecture *self,
   self->libdir_in_container = g_build_filename (runtime->overrides_in_container,
                                                 "lib", self->details->tuple, NULL);
 
+  self->aliases_in_current_namespace = g_build_filename (self->libdir_in_current_namespace,
+                                                         "aliases", NULL);
+
   /* This has the side-effect of testing whether we can run binaries
    * for this architecture on the current environment. We
    * assume that this is the same as whether we can run them
@@ -351,6 +357,7 @@ runtime_architecture_check_valid (RuntimeArchitecture *self)
   g_return_val_if_fail (self->capsule_capture_libs != NULL, FALSE);
   g_return_val_if_fail (self->libdir_in_current_namespace != NULL, FALSE);
   g_return_val_if_fail (self->libdir_in_container != NULL, FALSE);
+  g_return_val_if_fail (self->aliases_in_current_namespace != NULL, FALSE);
   g_return_val_if_fail (self->ld_so != NULL, FALSE);
   return TRUE;
 }
@@ -364,6 +371,7 @@ runtime_architecture_clear (RuntimeArchitecture *self)
   g_clear_pointer (&self->capsule_capture_libs, g_free);
   g_clear_pointer (&self->libdir_in_current_namespace, g_free);
   g_clear_pointer (&self->libdir_in_container, g_free);
+  g_clear_pointer (&self->aliases_in_current_namespace, g_free);
   g_clear_pointer (&self->ld_so, g_free);
 }
 
@@ -1400,6 +1408,12 @@ pv_runtime_initable_init (GInitable *initable,
   if (!g_file_test (self->libcapsule_knowledge, G_FILE_TEST_EXISTS))
     g_clear_pointer (&self->libcapsule_knowledge, g_free);
 
+  self->runtime_abi_json = g_build_filename (self->runtime_usr, "lib", "steamrt",
+                                             "steam-runtime-abi.json", NULL);
+
+  if (!g_file_test (self->runtime_abi_json, G_FILE_TEST_EXISTS))
+    g_clear_pointer (&self->runtime_abi_json, g_free);
+
   os_release = g_build_filename (self->runtime_usr, "lib", "os-release", NULL);
 
   /* TODO: Teach SrtSystemInfo to be able to load lib/os-release from
@@ -1488,6 +1502,7 @@ pv_runtime_finalize (GObject *object)
   g_free (self->bubblewrap);
   g_strfreev (self->original_environ);
   g_free (self->libcapsule_knowledge);
+  g_free (self->runtime_abi_json);
   glnx_close_fd (&self->variable_dir_fd);
   g_free (self->variable_dir);
   glnx_close_fd (&self->mutable_sysroot_fd);
@@ -3882,6 +3897,104 @@ pv_runtime_finish_libc_family (PvRuntime *self,
   return TRUE;
 }
 
+static gboolean
+pv_runtime_create_aliases (PvRuntime *self,
+                           RuntimeArchitecture *arch,
+                           GError **error)
+{
+  g_autoptr(JsonParser) parser = NULL;
+  JsonNode *node = NULL;
+  JsonArray *libraries_array = NULL;
+  JsonArray *aliases_array = NULL;
+  JsonObject *object;
+
+  if (self->runtime_abi_json == NULL)
+    {
+      g_info ("Runtime ABI JSON not present, not creating library aliases");
+      return TRUE;
+    }
+
+  parser = json_parser_new ();
+  if (!json_parser_load_from_file (parser, self->runtime_abi_json, error))
+    return glnx_prefix_error (error, "Error parsing the expected JSON object in \"%s\"",
+                              self->runtime_abi_json);
+
+  node = json_parser_get_root (parser);
+  object = json_node_get_object (node);
+
+  if (!json_object_has_member (object, "shared_libraries"))
+    return glnx_throw (error, "No \"shared_libraries\" in the JSON object \"%s\"",
+                       self->runtime_abi_json);
+
+  libraries_array = json_object_get_array_member (object, "shared_libraries");
+  if (libraries_array == NULL || json_array_get_length (libraries_array) == 0)
+    return TRUE;
+
+  for (guint i = 0; i < json_array_get_length (libraries_array); i++)
+    {
+      const gchar *soname = NULL;
+      g_autofree gchar *soname_in_runtime = NULL;
+      g_autofree gchar *soname_in_runtime_usr = NULL;
+      g_autofree gchar *soname_in_overrides = NULL;
+      g_autofree gchar *target = NULL;
+      g_autoptr(GList) members = NULL;
+
+      node = json_array_get_element (libraries_array, i);
+      if (!JSON_NODE_HOLDS_OBJECT (node))
+        continue;
+
+      object = json_node_get_object (node);
+
+      members = json_object_get_members (object);
+      if (members == NULL)
+        continue;
+
+      soname = members->data;
+
+      object = json_object_get_object_member (object, soname);
+      if (!json_object_has_member (object, "aliases"))
+        continue;
+
+      aliases_array = json_object_get_array_member (object, "aliases");
+      if (aliases_array == NULL || json_array_get_length (aliases_array) == 0)
+        continue;
+
+      soname_in_overrides = g_build_filename (arch->libdir_in_current_namespace,
+                                              soname, NULL);
+      soname_in_runtime_usr = g_build_filename (self->runtime_usr, "lib",
+                                                arch->details->tuple, soname, NULL);
+      /* We are not always in a merged-/usr runtime, e.g. if we are using a
+       * "sysroot" runtime. */
+      soname_in_runtime = g_build_filename (self->runtime_files, "lib",
+                                            arch->details->tuple, soname, NULL);
+
+      if (g_file_test (soname_in_overrides,
+                       (G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_SYMLINK)))
+        target = g_build_filename (arch->libdir_in_container, soname, NULL);
+      else if (g_file_test (soname_in_runtime_usr,
+                            (G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_SYMLINK)))
+        target = g_build_filename ("/usr/lib", arch->details->tuple, soname, NULL);
+      else if (g_file_test (soname_in_runtime,
+                            (G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_SYMLINK)))
+        target = g_build_filename ("/lib", arch->details->tuple, soname, NULL);
+      else
+        return glnx_throw (error, "The expected library %s is missing from both the runtime "
+                           "and the \"overrides\" directory", soname);
+
+      for (guint j = 0; j < json_array_get_length (aliases_array); j++)
+        {
+          g_autofree gchar *dest = g_build_filename (arch->aliases_in_current_namespace,
+                                                     json_array_get_string_element (aliases_array, j),
+                                                     NULL);
+          if (symlink (target, dest) != 0)
+            return glnx_throw_errno_prefix (error,
+                                            "Unable to create symlink %s -> %s",
+                                            dest, target);
+        }
+    }
+  return TRUE;
+}
+
 static gboolean
 pv_runtime_use_provider_graphics_stack (PvRuntime *self,
                                         FlatpakBwrap *bwrap,
@@ -4069,6 +4182,7 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self,
 
   for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++)
     {
+      g_autoptr(GError) local_error = NULL;
       g_auto (RuntimeArchitecture) arch_on_stack = { i };
       RuntimeArchitecture *arch = &arch_on_stack;
 
@@ -4114,6 +4228,7 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self,
           pv_search_path_append (va_api_path, this_dri_path_in_container);
 
           g_mkdir_with_parents (arch->libdir_in_current_namespace, 0755);
+          g_mkdir_with_parents (arch->aliases_in_current_namespace, 0755);
 
           g_debug ("Collecting graphics drivers from provider system...");
 
@@ -4359,6 +4474,15 @@ pv_runtime_use_provider_graphics_stack (PvRuntime *self,
                                                 platform_link, arch->details->tuple);
             }
 
+          if (!pv_runtime_create_aliases (self, arch, &local_error))
+            {
+              /* This is not a critical error, try to continue */
+              g_warning ("Unable to create library aliases: %s",
+                         local_error->message);
+              g_clear_error (&local_error);
+              continue;
+            }
+
           /* Make sure we do this last, so that we have really copied
            * everything from the host that we are going to */
           if (self->mutable_sysroot != NULL &&
@@ -4659,11 +4783,16 @@ pv_runtime_set_search_paths (PvRuntime *self,
   for (i = 0; i < G_N_ELEMENTS (multiarch_tuples) - 1; i++)
     {
       g_autofree gchar *ld_path = NULL;
+      g_autofree gchar *aliases = NULL;
 
       ld_path = g_build_filename (self->overrides_in_container, "lib",
                                   multiarch_tuples[i], NULL);
 
+      aliases = g_build_filename (self->overrides_in_container, "lib",
+                                  multiarch_tuples[i], "aliases", NULL);
+
       pv_search_path_append (ld_library_path, ld_path);
+      pv_search_path_append (ld_library_path, aliases);
     }
 
   if (self->flags & PV_RUNTIME_FLAGS_SEARCH_CWD)
-- 
GitLab