From 8196049befc72183dd29b189edb6bf4b8170c302 Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Wed, 4 Aug 2021 19:44:34 +0100
Subject: [PATCH] pv_wrap_append_preload: Handle libraries specified as a
 basename

This is a bit complicated, because there are two reasonable things that
people might use LD_PRELOAD for, and in this mode it's particularly
important to distinguish between them.

One is to inject arbitrary code, like MangoHud or fakeroot. In this
case, we want to take the loadable module from the namespace in which
the user initiated pv-wrap. We can do this the same way we deal with
the ${LIB} and ${PLATFORM} dynamic string tokens: load it once per ABI,
pass a separate option to pv-adverb for each one, and let pv-adverb
recombine them.

The other is to work around libraries not being loaded soon enough,
like the way people sometimes use LD_PRELOAD="libpthread.so.0 libGL.so.1"
to force an optirun library to be loaded. In this case, we absolutely
do not want to import the host library of that name into the container
unconditionally, because if we do, it will sabotage our careful efforts
to get the correct instance of libpthread.so.0 to be chosen. In this
case, assume that the user meant "take whatever libpthread.so.0 you
would naturally load, and preload it into each executable".

Resolves: https://github.com/ValveSoftware/steam-runtime/issues/435
Signed-off-by: Simon McVittie <smcv@collabora.com>
---
 pressure-vessel/runtime.c    | 100 +++++++++++++++++++++++++++++++++++
 pressure-vessel/runtime.h    |   3 ++
 pressure-vessel/wrap-setup.c |  99 ++++++++++++++++++++++++++++------
 3 files changed, 187 insertions(+), 15 deletions(-)

diff --git a/pressure-vessel/runtime.c b/pressure-vessel/runtime.c
index 62fb34ef2..443bd05f5 100644
--- a/pressure-vessel/runtime.c
+++ b/pressure-vessel/runtime.c
@@ -5751,3 +5751,103 @@ pv_runtime_get_modified_app (PvRuntime *self)
   else
     return NULL;
 }
+
+/*
+ * Return %TRUE if the runtime provides @library, either directly or
+ * via the graphics-stack provider.
+ */
+gboolean
+pv_runtime_has_library (PvRuntime *self,
+                        const char *library)
+{
+  glnx_autofd int source_files_fd = -1;
+  gsize i;
+
+  g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
+  g_return_val_if_fail (library != NULL, FALSE);
+
+  g_debug ("Checking whether runtime has library: %s", library);
+
+  for (i = 0; i < PV_N_SUPPORTED_ARCHITECTURES; i++)
+    {
+      const PvMultiarchDetails *details = &pv_multiarch_details[i];
+      g_autoptr(GPtrArray) dirs = NULL;
+      gsize j;
+
+      dirs = pv_multiarch_details_get_libdirs (details,
+                                               PV_MULTIARCH_LIBDIRS_FLAGS_NONE);
+
+      for (j = 0; j < dirs->len; j++)
+        {
+          const char *libdir = g_ptr_array_index (dirs, j);
+          g_autofree gchar *path = g_build_filename (libdir, library, NULL);
+
+          if (self->mutable_sysroot_fd >= 0)
+            {
+              glnx_autofd int fd = -1;
+
+              fd = _srt_resolve_in_sysroot (self->mutable_sysroot_fd, path,
+                                            SRT_RESOLVE_FLAGS_NONE,
+                                            NULL, NULL);
+
+              if (fd >= 0)
+                {
+                  g_debug ("-> yes, ${mutable_sysroot}/%s", path);
+                  return TRUE;
+                }
+            }
+          else
+            {
+              glnx_autofd int fd = -1;
+
+              /* The runtime isn't necessarily a sysroot (it might just be a
+               * merged /usr) but in practice it'll be close enough: we look
+               * up each library in /usr/foo and /foo anyway. */
+              if (source_files_fd < 0)
+                {
+                  if (!glnx_opendirat (AT_FDCWD, self->source_files, TRUE,
+                                       &source_files_fd, NULL))
+                    continue;
+                }
+
+              fd = _srt_resolve_in_sysroot (source_files_fd, path,
+                                            SRT_RESOLVE_FLAGS_NONE,
+                                            NULL, NULL);
+
+              if (fd >= 0)
+                {
+                  g_debug ("-> yes, ${source_files}/%s", path);
+                  return TRUE;
+                }
+            }
+
+          /* If the graphics stack provider is not the same as the current
+           * namespace (in practice this rarely/never happens), we also
+           * want to steer clear of libraries that only exist in the
+           * graphics stack provider.
+           *
+           * If the graphics stack provider *is* the current namespace,
+           * and the library doesn't exist in the container runtime, then
+           * it's OK to use libraries from it in LD_PRELOAD, because there
+           * is no other version that might have been meant. */
+          if (self->provider != NULL
+              && g_strcmp0 (self->provider->path_in_current_ns, "/") != 0)
+            {
+              glnx_autofd int fd = -1;
+
+              fd = _srt_resolve_in_sysroot (self->provider->fd, path,
+                                            SRT_RESOLVE_FLAGS_NONE,
+                                            NULL, NULL);
+
+              if (fd >= 0)
+                {
+                  g_debug ("-> yes, ${provider}/%s", path);
+                  return TRUE;
+                }
+            }
+        }
+    }
+
+  g_debug ("-> no");
+  return FALSE;
+}
diff --git a/pressure-vessel/runtime.h b/pressure-vessel/runtime.h
index dfb9f19f4..6370deea3 100644
--- a/pressure-vessel/runtime.h
+++ b/pressure-vessel/runtime.h
@@ -113,4 +113,7 @@ gboolean pv_runtime_use_shared_sockets (PvRuntime *self,
                                         PvEnviron *container_env,
                                         GError **error);
 
+gboolean pv_runtime_has_library (PvRuntime *self,
+                                 const char *library);
+
 G_DEFINE_AUTOPTR_CLEANUP_FUNC (PvRuntime, g_object_unref)
diff --git a/pressure-vessel/wrap-setup.c b/pressure-vessel/wrap-setup.c
index fdcaef856..c9982426e 100644
--- a/pressure-vessel/wrap-setup.c
+++ b/pressure-vessel/wrap-setup.c
@@ -502,9 +502,6 @@ append_preload_per_architecture (GPtrArray *argv,
   g_autoptr(SrtSystemInfo) system_info = srt_system_info_new (NULL);
   gsize i;
 
-  g_debug ("Found $LIB or $PLATFORM in \"%s\", splitting architectures",
-           preload);
-
   if (system_info == NULL)
     system_info = srt_system_info_new (NULL);
 
@@ -537,10 +534,19 @@ append_preload_per_architecture (GPtrArray *argv,
           platform = pv_multiarch_details[i].platforms[0];
 
           mock_path = g_string_new (preload);
-          g_string_replace (mock_path, "$LIB", lib, 0);
-          g_string_replace (mock_path, "${LIB}", lib, 0);
-          g_string_replace (mock_path, "$PLATFORM", platform, 0);
-          g_string_replace (mock_path, "${PLATFORM}", platform, 0);
+
+          if (strchr (preload, '/') == NULL)
+            {
+              g_string_printf (mock_path, "/path/to/%s/%s", lib, preload);
+            }
+          else
+            {
+              g_string_replace (mock_path, "$LIB", lib, 0);
+              g_string_replace (mock_path, "${LIB}", lib, 0);
+              g_string_replace (mock_path, "$PLATFORM", platform, 0);
+              g_string_replace (mock_path, "${PLATFORM}", platform, 0);
+            }
+
           path = mock_path->str;
 
           /* As a special case, pretend one 64-bit library failed to load,
@@ -575,6 +581,73 @@ append_preload_per_architecture (GPtrArray *argv,
     }
 }
 
+static void
+append_preload_basename (GPtrArray *argv,
+                         const char *option,
+                         const char *preload,
+                         GStrv env,
+                         PvAppendPreloadFlags flags,
+                         PvRuntime *runtime,
+                         FlatpakExports *exports)
+{
+  gboolean runtime_has_library = FALSE;
+
+  if (runtime != NULL)
+    runtime_has_library = pv_runtime_has_library (runtime, preload);
+
+  if (flags & PV_APPEND_PRELOAD_FLAGS_IN_UNIT_TESTS)
+    {
+      /* Mock implementation for unit tests: behave as though the
+       * container has everything except libfakeroot/libfakechroot. */
+      if (g_str_has_prefix (preload, "libfake"))
+        runtime_has_library = FALSE;
+      else
+        runtime_has_library = TRUE;
+    }
+
+  if (runtime_has_library)
+    {
+      /* If the library exists in the container runtime or in the
+       * stack we imported from the graphics provider, e.g.
+       * LD_PRELOAD=libpthread.so.0, then we certainly don't want
+       * to be loading it from the current namespace: that would
+       * bypass our logic for comparing library versions and picking
+       * the newest. Just pass through the LD_PRELOAD item into the
+       * container, and let the dynamic linker in the container choose
+       * what it means (container runtime or graphics provider as
+       * appropriate). */
+      g_debug ("Found \"%s\" in runtime or graphics stack provider, "
+               "passing %s through as-is",
+               preload, option);
+      append_preload_internal (argv,
+                               option,
+                               NULL,
+                               NULL,
+                               preload,
+                               env,
+                               flags,
+                               runtime,
+                               NULL);
+    }
+  else
+    {
+      /* There's no such library in the container runtime or in the
+       * graphics provider, so it's OK to inject the version from the
+       * current namespace. Use the same trick as for ${PLATFORM} to
+       * turn it into (up to) one absolute path per ABI. */
+      g_debug ("Did not find \"%s\" in runtime or graphics stack provider, "
+               "splitting architectures",
+               preload);
+      append_preload_per_architecture (argv,
+                                       option,
+                                       preload,
+                                       env,
+                                       flags,
+                                       runtime,
+                                       exports);
+    }
+}
+
 /**
  * pv_wrap_append_preload:
  * @argv: (element-type filename): Array of command-line options to populate
@@ -635,19 +708,13 @@ pv_wrap_append_preload (GPtrArray *argv,
       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,
+        append_preload_basename (argv,
                                  option,
-                                 NULL,
-                                 NULL,
                                  preload,
                                  env,
                                  flags,
                                  runtime,
-                                 NULL);
+                                 exports);
         break;
 
       case SRT_LOADABLE_KIND_PATH:
@@ -665,6 +732,8 @@ pv_wrap_append_preload (GPtrArray *argv,
           }
         else if (loadable_flags & SRT_LOADABLE_FLAGS_ABI_DEPENDENT)
           {
+            g_debug ("Found $LIB or $PLATFORM in \"%s\", splitting architectures",
+                     preload);
             append_preload_per_architecture (argv,
                                              option,
                                              preload,
-- 
GitLab