[gnome-desktop] thumbnail: Sandbox thumbnailers on Linux



commit 8b1db18aa75c2684b513481088b4e289b5c8ed92
Author: Bastien Nocera <hadess hadess net>
Date:   Fri Jul 21 13:08:43 2017 +0200

    thumbnail: Sandbox thumbnailers on Linux
    
    On Linux systems, bubblewrap is now required to launch thumbnailers in a
    restricted environment.
    
    - Only /usr and the compilation ${prefix} of the gnome-desktop library
      will be available to the thumbnailer as read-only
    - The network is disabled
    - The filename of the file to thumbnail is hidden
    - Bubblewrap is not used if the application is already sandboxed in
      Flatpak as all privileges to create a new namespace are dropped when
      the initial one is created.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=774497

 configure.ac                                      |    8 +
 libgnome-desktop/gnome-desktop-thumbnail-script.c |  201 +++++++++++++++++++--
 2 files changed, 194 insertions(+), 15 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index fe4f166..f277dcc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -159,6 +159,14 @@ else
        have_udev=no
 fi
 
+dnl Check for bubblewrap compatible platform
+case $host_os in
+  linux*)
+    AC_DEFINE_UNQUOTED(HAVE_BWRAP, 1, [Define to 1 if Bubblewrap support is available])
+    AC_DEFINE_UNQUOTED(INSTALL_PREFIX, "$prefix", [Path to library install prefix])
+    ;;
+esac
+
 dnl pkg-config dependency checks
 
 PKG_CHECK_MODULES(GNOME_DESKTOP, gdk-pixbuf-2.0 >= $GDK_PIXBUF_REQUIRED
diff --git a/libgnome-desktop/gnome-desktop-thumbnail-script.c 
b/libgnome-desktop/gnome-desktop-thumbnail-script.c
index 87319bc..6a5a77c 100644
--- a/libgnome-desktop/gnome-desktop-thumbnail-script.c
+++ b/libgnome-desktop/gnome-desktop-thumbnail-script.c
@@ -36,8 +36,15 @@
 #include "gnome-desktop-thumbnail-script.h"
 
 typedef struct {
+  gboolean sandbox;
+  GArray *fd_array;
+  /* Input/output file paths outside the sandbox */
   char *infile;
   char *outfile;
+  char *outdir; /* outdir is outfile's parent dir, if it needs to be deleted */
+  /* I/O file paths inside the sandbox */
+  char *s_infile;
+  char *s_outfile;
 } ScriptExec;
 
 static char *
@@ -100,11 +107,99 @@ expand_thumbnailing_elem (const char *elem,
   return g_string_free (str, FALSE);
 }
 
+/* From https://github.com/flatpak/flatpak/blob/master/common/flatpak-run.c */
+G_GNUC_NULL_TERMINATED
+static void
+add_args (GPtrArray *argv_array, ...)
+{
+  va_list args;
+  const gchar *arg;
+
+  va_start (args, argv_array);
+  while ((arg = va_arg (args, const gchar *)))
+    g_ptr_array_add (argv_array, g_strdup (arg));
+  va_end (args);
+}
+
+static char *
+get_extension (const char *path)
+{
+  g_autofree char *basename = NULL;
+  char *p;
+
+  basename = g_path_get_basename (path);
+  p = strrchr (basename, '.');
+  if (p == NULL)
+    return NULL;
+  return g_strdup (p + 1);
+}
+
+#ifdef HAVE_BWRAP
+static gboolean
+add_bwrap (GPtrArray   *array,
+          ScriptExec  *script)
+{
+  char *fd_str;
+  int fd;
+
+  g_return_val_if_fail (script->outdir != NULL, FALSE);
+  g_return_val_if_fail (script->s_infile != NULL, FALSE);
+
+  add_args (array,
+           "bwrap",
+           "--ro-bind", "/usr", "/usr",
+           "--proc", "/proc",
+           "--dev", "/dev",
+           "--symlink", "usr/lib", "/lib",
+           "--symlink", "usr/lib64", "/lib64",
+           "--symlink", "usr/bin", "/bin",
+           "--symlink", "usr/sbin", "/sbin",
+           "--chdir", "/",
+           "--unshare-all",
+           "--die-with-parent",
+           NULL);
+
+  /* Add gnome-desktop's install prefix if needed */
+  if (g_strcmp0 (INSTALL_PREFIX, "") != 0 &&
+      g_strcmp0 (INSTALL_PREFIX, "/usr") != 0 &&
+      g_strcmp0 (INSTALL_PREFIX, "/usr/") != 0)
+    {
+      add_args (array,
+                "--ro-bind", INSTALL_PREFIX, INSTALL_PREFIX,
+                NULL);
+    }
+
+  g_ptr_array_add (array, g_strdup ("--bind"));
+  g_ptr_array_add (array, g_strdup (script->outdir));
+  g_ptr_array_add (array, g_strdup ("/tmp"));
+
+  /* Open the infile so we can pass the fd through bwrap,
+   * we make sure to also re-use the original file's original
+   * extension in case it's useful for the thumbnailer to
+   * identify the file type */
+  fd = open (script->infile, O_RDONLY | O_CLOEXEC);
+  if (fd == -1)
+    goto bail;
+  fd_str = g_strdup_printf ("%d", fd);
+  g_ptr_array_add (array, g_strdup ("--file"));
+  g_ptr_array_add (array, fd_str);
+  g_ptr_array_add (array, g_strdup (script->s_infile));
+
+  g_array_append_val (script->fd_array, fd);
+
+  return TRUE;
+
+bail:
+  g_ptr_array_set_size (array, 0);
+  return FALSE;
+}
+#endif /* HAVE_BWRAP */
+
 static char **
-expand_thumbnailing_script (const char  *cmd,
-                           ScriptExec  *script,
-                           int          size,
-                           GError     **error)
+expand_thumbnailing_cmd (const char  *cmd,
+                        ScriptExec  *script,
+                        int          size,
+                        GError     **error)
 {
   GPtrArray *array;
   g_auto(GStrv) cmd_elems = NULL;
@@ -117,6 +212,18 @@ expand_thumbnailing_script (const char  *cmd,
 
   array = g_ptr_array_new_with_free_func (g_free);
 
+#ifdef HAVE_BWRAP
+  if (script->sandbox)
+    {
+      if (!add_bwrap (array, script))
+        {
+          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                              "Bubblewrap setup failed");
+         goto bail;
+       }
+    }
+#endif
+
   got_in = got_out = FALSE;
   for (i = 0; cmd_elems[i] != NULL; i++)
     {
@@ -124,8 +231,8 @@ expand_thumbnailing_script (const char  *cmd,
 
       expanded = expand_thumbnailing_elem (cmd_elems[i],
                                           size,
-                                          script->infile,
-                                          script->outfile,
+                                          script->s_infile ? script->s_infile : script->infile,
+                                          script->s_outfile ? script->s_outfile : script->outfile,
                                           &got_in,
                                           &got_out);
 
@@ -155,6 +262,21 @@ bail:
 }
 
 static void
+child_setup (gpointer user_data)
+{
+  GArray *fd_array = user_data;
+  int i;
+
+  /* If no fd_array was specified, don't care. */
+  if (fd_array == NULL)
+    return;
+
+  /* Otherwise, mark not - close-on-exec all the fds in the array */
+  for (i = 0; i < fd_array->len; i++)
+    fcntl (g_array_index (fd_array, int, i), F_SETFD, 0);
+}
+
+static void
 script_exec_free (ScriptExec *exec)
 {
   g_free (exec->infile);
@@ -163,29 +285,78 @@ script_exec_free (ScriptExec *exec)
       g_unlink (exec->outfile);
       g_free (exec->outfile);
     }
+  if (exec->outdir)
+    {
+      g_rmdir (exec->outdir);
+      g_free (exec->outdir);
+    }
+  g_free (exec->s_infile);
+  g_free (exec->s_outfile);
+  if (exec->fd_array)
+    g_array_free (exec->fd_array, TRUE);
   g_free (exec);
 }
 
+static void
+clear_fd (gpointer data)
+{
+  int *fd_p = data;
+  if (fd_p != NULL && *fd_p != -1)
+    close (*fd_p);
+}
+
 static ScriptExec *
 script_exec_new (const char *uri)
 {
   ScriptExec *exec;
   g_autoptr(GFile) file = NULL;
-  int fd;
-  g_autofree char *tmpname = NULL;
 
   exec = g_new0 (ScriptExec, 1);
+#ifdef HAVE_BWRAP
+  /* Bubblewrap is not used if the application is already sandboxed in
+   * Flatpak as all privileges to create a new namespace are dropped when
+   * the initial one is created. */
+  if (!g_file_test ("/.flatpak-info", G_FILE_TEST_IS_REGULAR))
+    exec->sandbox = TRUE;
+#endif
+
   file = g_file_new_for_uri (uri);
 
   exec->infile = g_file_get_path (file);
   if (!exec->infile)
     goto bail;
 
-  fd = g_file_open_tmp (".gnome_desktop_thumbnail.XXXXXX", &tmpname, NULL);
-  if (fd == -1)
-    goto bail;
-  close (fd);
-  exec->outfile = g_steal_pointer (&tmpname);
+#ifdef HAVE_BWRAP
+  if (exec->sandbox)
+    {
+      char *tmpl;
+      g_autofree char *ext = NULL;
+
+      exec->fd_array = g_array_new (FALSE, TRUE, sizeof (int));
+      g_array_set_clear_func (exec->fd_array, clear_fd);
+
+      tmpl = g_strdup ("/tmp/gnome-desktop-thumbnailer-XXXXXX");
+      exec->outdir = g_mkdtemp (tmpl);
+      if (!exec->outdir)
+        goto bail;
+      exec->outfile = g_build_filename (exec->outdir, "gnome-desktop-thumbnailer.png", NULL);
+
+      ext = get_extension (exec->infile);
+      exec->s_infile = g_strdup_printf ("/tmp/gnome-desktop-file-to-thumbnail.%s", ext);
+      exec->s_outfile = g_strdup ("/tmp/gnome-desktop-thumbnailer.png");
+    }
+  else
+#endif
+    {
+      int fd;
+      g_autofree char *tmpname = NULL;
+
+      fd = g_file_open_tmp (".gnome_desktop_thumbnail.XXXXXX", &tmpname, NULL);
+      if (fd == -1)
+        goto bail;
+      close (fd);
+      exec->outfile = g_steal_pointer (&tmpname);
+    }
 
   return exec;
 
@@ -208,7 +379,7 @@ gnome_desktop_thumbnail_script_exec (const char  *cmd,
   ScriptExec *exec;
 
   exec = script_exec_new (uri);
-  expanded_script = expand_thumbnailing_script (cmd, exec, size, error);
+  expanded_script = expand_thumbnailing_cmd (cmd, exec, size, error);
   if (expanded_script == NULL)
     goto out;
 
@@ -222,7 +393,7 @@ gnome_desktop_thumbnail_script_exec (const char  *cmd,
 #endif
 
   ret = g_spawn_sync (NULL, expanded_script, NULL, G_SPAWN_SEARCH_PATH,
-                     NULL, NULL, NULL, &error_out,
+                     child_setup, exec->fd_array, NULL, &error_out,
                      &exit_status, error);
   if (ret && g_spawn_check_exit_status (exit_status, error))
     {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]