From 9cc0c93371141fa34185836d5b54741dae9711b5 Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Wed, 20 May 2020 18:17:58 +0100
Subject: [PATCH] runtime: Replace glibc-related data with symlinks into
 /run/host

Flatpak sub-sandboxing won't let us bind-mount arbitrary things over
arbitrary locations, but if we have a mutable copy of the runtime,
we can delete runtime content and replace it with host system
equivalents.

Signed-off-by: Simon McVittie <smcv@collabora.com>
---
 src/runtime.c | 164 +++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 137 insertions(+), 27 deletions(-)

diff --git a/src/runtime.c b/src/runtime.c
index 50eebe892..f63161c0b 100644
--- a/src/runtime.c
+++ b/src/runtime.c
@@ -34,6 +34,7 @@
 #include "bwrap-lock.h"
 #include "enumtypes.h"
 #include "flatpak-run-private.h"
+#include "resolve-in-sysroot.h"
 #include "utils.h"
 
 /*
@@ -93,6 +94,38 @@ G_DEFINE_TYPE_WITH_CODE (PvRuntime, pv_runtime, G_TYPE_OBJECT,
                          G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
                                                 pv_runtime_initable_iface_init))
 
+/*
+ * Return whether @path is likely to be visible in /run/host.
+ * This needs to be kept approximately in sync with pv_bwrap_bind_usr()
+ * and Flatpak's --filesystem=host-os special keyword.
+ *
+ * This doesn't currently handle /etc: we make the pessimistic assumption
+ * that /etc/ld.so.cache, etc., are not shared.
+ */
+static gboolean
+path_visible_in_run_host (const char *path)
+{
+  while (path[0] == '/')
+    path++;
+
+  if (g_str_has_prefix (path, "usr") &&
+      (path[3] == '\0' || path[3] == '/'))
+    return TRUE;
+
+  if (g_str_has_prefix (path, "lib"))
+    return TRUE;
+
+  if (g_str_has_prefix (path, "bin") &&
+      (path[3] == '\0' || path[3] == '/'))
+    return TRUE;
+
+  if (g_str_has_prefix (path, "sbin") ||
+      (path[4] == '\0' || path[4] == '/'))
+    return TRUE;
+
+  return FALSE;
+}
+
 /*
  * Supported Debian-style multiarch tuples
  */
@@ -1598,6 +1631,86 @@ bind_runtime (PvRuntime *self,
   return TRUE;
 }
 
+typedef enum
+{
+  TAKE_FROM_HOST_FLAGS_IF_DIR = (1 << 0),
+  TAKE_FROM_HOST_FLAGS_IF_EXISTS = (1 << 1),
+  TAKE_FROM_HOST_FLAGS_NONE = 0
+} TakeFromHostFlags;
+
+static gboolean
+pv_runtime_take_from_host (PvRuntime *self,
+                           FlatpakBwrap *bwrap,
+                           const char *source_in_host,
+                           const char *dest_in_container,
+                           TakeFromHostFlags flags,
+                           GError **error)
+{
+  g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
+  g_return_val_if_fail (!pv_bwrap_was_finished (bwrap), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  if (flags & TAKE_FROM_HOST_FLAGS_IF_DIR)
+    {
+      if (!g_file_test (source_in_host, G_FILE_TEST_IS_DIR))
+        return TRUE;
+    }
+
+  if (flags & TAKE_FROM_HOST_FLAGS_IF_EXISTS)
+    {
+      if (!g_file_test (source_in_host, G_FILE_TEST_EXISTS))
+        return TRUE;
+    }
+
+  if (self->mutable_sysroot != NULL)
+    {
+      /* Replace ${mutable_sysroot}/usr/lib/locale with a symlink to
+       * /run/host/usr/lib/locale, or similar */
+      g_autofree gchar *parent_in_container = NULL;
+      g_autofree gchar *target = NULL;
+      const char *base;
+      glnx_autofd int parent_dirfd = -1;
+
+      /* If it isn't in /usr, /lib, etc., then the symlink will be
+       * dangling and this probably isn't going to work. */
+      if (!path_visible_in_run_host (source_in_host))
+        g_warning ("\"%s\" is unlikely to appear in /run/host",
+                   source_in_host);
+
+      parent_in_container = g_path_get_dirname (dest_in_container);
+      parent_dirfd = pv_resolve_in_sysroot (self->mutable_sysroot_fd,
+                                            parent_in_container,
+                                            PV_RESOLVE_FLAGS_MKDIR_P,
+                                            NULL, error);
+
+      if (parent_dirfd < 0)
+        return FALSE;
+
+      base = glnx_basename (dest_in_container);
+
+      if (!glnx_shutil_rm_rf_at (parent_dirfd, base, NULL, error))
+        return FALSE;
+
+      target = g_build_filename ("/run/host", source_in_host, NULL);
+
+      if (TEMP_FAILURE_RETRY (symlinkat (target, parent_dirfd, base)) != 0)
+        return glnx_throw_errno_prefix (error,
+                                        "Unable to create symlink \"%s/%s\" -> \"%s\"",
+                                        self->mutable_sysroot,
+                                        dest_in_container, target);
+    }
+  else
+    {
+      /* We can't edit the runtime in-place, so tell bubblewrap to mount
+       * a new version over the top */
+      flatpak_bwrap_add_args (bwrap,
+                              "--ro-bind", source_in_host, dest_in_container,
+                              NULL);
+    }
+
+  return TRUE;
+}
+
 static gboolean
 pv_runtime_use_host_graphics_stack (PvRuntime *self,
                                     FlatpakBwrap *bwrap,
@@ -2124,17 +2237,19 @@ pv_runtime_use_host_graphics_stack (PvRuntime *self,
     {
       g_debug ("Making host locale data visible in container");
 
-      if (g_file_test ("/usr/lib/locale", G_FILE_TEST_EXISTS))
-        flatpak_bwrap_add_args (bwrap,
-                                "--ro-bind", "/usr/lib/locale",
-                                "/usr/lib/locale",
-                                NULL);
+      if (!pv_runtime_take_from_host (self, bwrap,
+                                      "/usr/lib/locale",
+                                      "/usr/lib/locale",
+                                      TAKE_FROM_HOST_FLAGS_IF_EXISTS,
+                                      error))
+        return FALSE;
 
-      if (g_file_test ("/usr/share/i18n", G_FILE_TEST_EXISTS))
-        flatpak_bwrap_add_args (bwrap,
-                                "--ro-bind", "/usr/share/i18n",
-                                "/usr/share/i18n",
-                                NULL);
+      if (!pv_runtime_take_from_host (self, bwrap,
+                                      "/usr/share/i18n",
+                                      "/usr/share/i18n",
+                                      TAKE_FROM_HOST_FLAGS_IF_EXISTS,
+                                      error))
+        return FALSE;
 
       localedef = g_find_program_in_path ("localedef");
 
@@ -2184,11 +2299,13 @@ pv_runtime_use_host_graphics_stack (PvRuntime *self,
         {
           g_warning ("Cannot find ldconfig in PATH, /sbin or /usr/sbin");
         }
-      else
+      else if (!pv_runtime_take_from_host (self, bwrap,
+                                           ldconfig,
+                                           "/sbin/ldconfig",
+                                           TAKE_FROM_HOST_FLAGS_NONE,
+                                           error))
         {
-          flatpak_bwrap_add_args (bwrap,
-                                  "--ro-bind", ldconfig, "/sbin/ldconfig",
-                                  NULL);
+          return FALSE;
         }
 
       g_debug ("Making host gconv modules visible in container");
@@ -2196,19 +2313,12 @@ pv_runtime_use_host_graphics_stack (PvRuntime *self,
       g_hash_table_iter_init (&iter, gconv_from_host);
       while (g_hash_table_iter_next (&iter, (gpointer *)&gconv_path, NULL))
         {
-          g_autofree gchar *gconv_in_runtime = NULL;
-
-          if (g_str_has_prefix (gconv_path, "/usr/"))
-            gconv_in_runtime = g_build_filename (self->runtime_usr, gconv_path + strlen ("/usr"), NULL);
-          else
-            gconv_in_runtime = g_build_filename (self->runtime_usr, gconv_path, NULL);
-
-          if (g_file_test (gconv_in_runtime, G_FILE_TEST_IS_DIR))
-            {
-              flatpak_bwrap_add_args (bwrap,
-                                      "--ro-bind", gconv_path, gconv_path,
-                                      NULL);
-            }
+          if (!pv_runtime_take_from_host (self, bwrap,
+                                          gconv_path,
+                                          gconv_path,
+                                          TAKE_FROM_HOST_FLAGS_IF_DIR,
+                                          error))
+            return FALSE;
         }
     }
   else
-- 
GitLab