[gnome-desktop] thumbnail: Restrict thumbnailer syscalls using seccomp



commit 5a4844bdab8171efe27c6b0f6a3cd338534a66fb
Author: Bastien Nocera <hadess hadess net>
Date:   Fri Jul 21 14:26:20 2017 +0200

    thumbnail: Restrict thumbnailer syscalls using seccomp
    
    Use seccomp code from flatpak to limit the system calls thumbnailers can
    make, reducing the attach surface.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=785197

 configure.ac                                      |    7 +-
 libgnome-desktop/gnome-desktop-thumbnail-script.c |  375 +++++++++++++++++++++
 2 files changed, 381 insertions(+), 1 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index f277dcc..fd61928 100644
--- a/configure.ac
+++ b/configure.ac
@@ -159,10 +159,15 @@ else
        have_udev=no
 fi
 
+SECCOMP_PKG=""
 dnl Check for bubblewrap compatible platform
 case $host_os in
   linux*)
+    PKG_CHECK_MODULES(LIBSECCOMP, [libseccomp])
+    SECCOMP_PKG="libseccomp"
+    AC_DEFINE_UNQUOTED(_GNU_SOURCE, 1, [Define to include GNU extensions])
     AC_DEFINE_UNQUOTED(HAVE_BWRAP, 1, [Define to 1 if Bubblewrap support is available])
+    AC_DEFINE([ENABLE_SECCOMP], [1], [Define if using seccomp])
     AC_DEFINE_UNQUOTED(INSTALL_PREFIX, "$prefix", [Path to library install prefix])
     ;;
 esac
@@ -176,7 +181,7 @@ PKG_CHECK_MODULES(GNOME_DESKTOP, gdk-pixbuf-2.0 >= $GDK_PIXBUF_REQUIRED
                                  gsettings-desktop-schemas >= $GSETTINGS_DESKTOP_SCHEMAS_REQUIRED
                                  xkeyboard-config
                                  iso-codes
-                                 $UDEV_PKG)
+                                 $UDEV_PKG $SECCOMP_PKG)
 
 XKB_BASE=$($PKG_CONFIG --variable xkb_base xkeyboard-config)
 AC_SUBST(XKB_BASE)
diff --git a/libgnome-desktop/gnome-desktop-thumbnail-script.c 
b/libgnome-desktop/gnome-desktop-thumbnail-script.c
index 6a5a77c..b48b7de 100644
--- a/libgnome-desktop/gnome-desktop-thumbnail-script.c
+++ b/libgnome-desktop/gnome-desktop-thumbnail-script.c
@@ -33,6 +33,14 @@
 #include <sys/stat.h>
 #include <fcntl.h>
 
+#ifdef ENABLE_SECCOMP
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/utsname.h>
+#include <seccomp.h>
+#endif
+
 #include "gnome-desktop-thumbnail-script.h"
 
 typedef struct {
@@ -134,6 +142,357 @@ get_extension (const char *path)
   return g_strdup (p + 1);
 }
 
+#ifdef ENABLE_SECCOMP
+static gboolean
+flatpak_fail (GError     **error,
+             const char  *msg,
+             ...)
+{
+  g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, msg);
+  return FALSE;
+}
+
+/* From https://github.com/flatpak/flatpak/blob/master/common/flatpak-utils.c */
+#if !defined(__i386__) && !defined(__x86_64__) && !defined(__aarch64__) && !defined(__arm__)
+static const char *
+flatpak_get_kernel_arch (void)
+{
+  static struct utsname buf;
+  static char *arch = NULL;
+  char *m;
+
+  if (arch != NULL)
+    return arch;
+
+  if (uname (&buf))
+    {
+      arch = "unknown";
+      return arch;
+    }
+
+  /* By default, just pass on machine, good enough for most arches */
+  arch = buf.machine;
+
+  /* Override for some arches */
+
+  m = buf.machine;
+  /* i?86 */
+  if (strlen (m) == 4 && m[0] == 'i' && m[2] == '8'  && m[3] == '6')
+    {
+      arch = "i386";
+    }
+  else if (g_str_has_prefix (m, "arm"))
+    {
+      if (g_str_has_suffix (m, "b"))
+        arch = "armeb";
+      else
+        arch = "arm";
+    }
+  else if (strcmp (m, "mips") == 0)
+    {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+      arch = "mipsel";
+#endif
+    }
+  else if (strcmp (m, "mips64") == 0)
+    {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+      arch = "mips64el";
+#endif
+    }
+
+  return arch;
+}
+#endif
+
+/* This maps the kernel-reported uname to a single string representing
+ * the cpu family, in the sense that all members of this family would
+ * be able to understand and link to a binary file with such cpu
+ * opcodes. That doesn't necessarily mean that all members of the
+ * family can run all opcodes, for instance for modern 32bit intel we
+ * report "i386", even though they support instructions that the
+ * original i386 cpu cannot run. Still, such an executable would
+ * at least try to execute a 386, whereas an arm binary would not.
+ */
+static const char *
+flatpak_get_arch (void)
+{
+  /* Avoid using uname on multiarch machines, because uname reports the kernels
+   * arch, and that may be different from userspace. If e.g. the kernel is 64bit and
+   * the userspace is 32bit we want to use 32bit by default. So, we take the current build
+   * arch as the default. */
+#if defined(__i386__)
+  return "i386";
+#elif defined(__x86_64__)
+  return "x86_64";
+#elif defined(__aarch64__)
+  return "aarch64";
+#elif defined(__arm__)
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+  return "arm";
+#else
+  return "armeb";
+#endif
+#else
+  return flatpak_get_kernel_arch ();
+#endif
+}
+
+/* From https://github.com/flatpak/flatpak/blob/master/common/flatpak-run.c */
+static const uint32_t seccomp_x86_64_extra_arches[] = { SCMP_ARCH_X86, 0, };
+
+#ifdef SCMP_ARCH_AARCH64
+static const uint32_t seccomp_aarch64_extra_arches[] = { SCMP_ARCH_ARM, 0 };
+#endif
+
+static inline void
+cleanup_seccomp (void *p)
+{
+  scmp_filter_ctx *pp = (scmp_filter_ctx *) p;
+
+  if (*pp)
+    seccomp_release (*pp);
+}
+
+static gboolean
+setup_seccomp (GPtrArray  *argv_array,
+               GArray     *fd_array,
+               const char *arch,
+               gboolean    multiarch,
+               gboolean    devel,
+               GError    **error)
+{
+  __attribute__((cleanup (cleanup_seccomp))) scmp_filter_ctx seccomp = NULL;
+
+  /**** BEGIN NOTE ON CODE SHARING
+   *
+   * There are today a number of different Linux container
+   * implementations.  That will likely continue for long into the
+   * future.  But we can still try to share code, and it's important
+   * to do so because it affects what library and application writers
+   * can do, and we should support code portability between different
+   * container tools.
+   *
+   * This syscall blacklist is copied from linux-user-chroot, which was in turn
+   * clearly influenced by the Sandstorm.io blacklist.
+   *
+   * If you make any changes here, I suggest sending the changes along
+   * to other sandbox maintainers.  Using the libseccomp list is also
+   * an appropriate venue:
+   * https://groups.google.com/forum/#!topic/libseccomp
+   *
+   * A non-exhaustive list of links to container tooling that might
+   * want to share this blacklist:
+   *
+   *  https://github.com/sandstorm-io/sandstorm
+   *    in src/sandstorm/supervisor.c++
+   *  http://cgit.freedesktop.org/xdg-app/xdg-app/
+   *    in common/flatpak-run.c
+   *  https://git.gnome.org/browse/linux-user-chroot
+   *    in src/setup-seccomp.c
+   *
+   **** END NOTE ON CODE SHARING
+   */
+  struct
+  {
+    int                  scall;
+    struct scmp_arg_cmp *arg;
+  } syscall_blacklist[] = {
+    /* Block dmesg */
+    {SCMP_SYS (syslog)},
+    /* Useless old syscall */
+    {SCMP_SYS (uselib)},
+    /* Don't allow you to switch to bsd emulation or whatnot */
+    {SCMP_SYS (personality)},
+    /* Don't allow disabling accounting */
+    {SCMP_SYS (acct)},
+    /* 16-bit code is unnecessary in the sandbox, and modify_ldt is a
+       historic source of interesting information leaks. */
+    {SCMP_SYS (modify_ldt)},
+    /* Don't allow reading current quota use */
+    {SCMP_SYS (quotactl)},
+
+    /* Don't allow access to the kernel keyring */
+    {SCMP_SYS (add_key)},
+    {SCMP_SYS (keyctl)},
+    {SCMP_SYS (request_key)},
+
+    /* Scary VM/NUMA ops */
+    {SCMP_SYS (move_pages)},
+    {SCMP_SYS (mbind)},
+    {SCMP_SYS (get_mempolicy)},
+    {SCMP_SYS (set_mempolicy)},
+    {SCMP_SYS (migrate_pages)},
+
+    /* Don't allow subnamespace setups: */
+    {SCMP_SYS (unshare)},
+    {SCMP_SYS (mount)},
+    {SCMP_SYS (pivot_root)},
+    {SCMP_SYS (clone), &SCMP_A0 (SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER)},
+
+    /* Don't allow faking input to the controlling tty (CVE-2017-5226) */
+    {SCMP_SYS (ioctl), &SCMP_A1(SCMP_CMP_EQ, (int)TIOCSTI)},
+  };
+
+  struct
+  {
+    int                  scall;
+    struct scmp_arg_cmp *arg;
+  } syscall_nondevel_blacklist[] = {
+    /* Profiling operations; we expect these to be done by tools from outside
+     * the sandbox.  In particular perf has been the source of many CVEs.
+     */
+    {SCMP_SYS (perf_event_open)},
+    {SCMP_SYS (ptrace)}
+  };
+  /* Blacklist all but unix, inet, inet6 and netlink */
+  int socket_family_blacklist[] = {
+    AF_AX25,
+    AF_IPX,
+    AF_APPLETALK,
+    AF_NETROM,
+    AF_BRIDGE,
+    AF_ATMPVC,
+    AF_X25,
+    AF_ROSE,
+    AF_DECnet,
+    AF_NETBEUI,
+    AF_SECURITY,
+    AF_KEY,
+    AF_NETLINK + 1, /* Last gets CMP_GE, so order is important */
+  };
+  int i, r;
+  int fd = -1;
+  g_autofree char *fd_str = NULL;
+  g_autofree char *path = NULL;
+
+  seccomp = seccomp_init (SCMP_ACT_ALLOW);
+  if (!seccomp)
+    return flatpak_fail (error, "Initialize seccomp failed");
+
+  if (arch != NULL)
+    {
+      uint32_t arch_id = 0;
+      const uint32_t *extra_arches = NULL;
+
+      if (strcmp (arch, "i386") == 0)
+        {
+          arch_id = SCMP_ARCH_X86;
+        }
+      else if (strcmp (arch, "x86_64") == 0)
+        {
+          arch_id = SCMP_ARCH_X86_64;
+          extra_arches = seccomp_x86_64_extra_arches;
+        }
+      else if (strcmp (arch, "arm") == 0)
+        {
+          arch_id = SCMP_ARCH_ARM;
+        }
+#ifdef SCMP_ARCH_AARCH64
+      else if (strcmp (arch, "aarch64") == 0)
+        {
+          arch_id = SCMP_ARCH_AARCH64;
+          extra_arches = seccomp_aarch64_extra_arches;
+        }
+#endif
+
+      /* We only really need to handle arches on multiarch systems.
+       * If only one arch is supported the default is fine */
+      if (arch_id != 0)
+        {
+          /* This *adds* the target arch, instead of replacing the
+             native one. This is not ideal, because we'd like to only
+             allow the target arch, but we can't really disallow the
+             native arch at this point, because then bubblewrap
+             couldn't continue running. */
+          r = seccomp_arch_add (seccomp, arch_id);
+          if (r < 0 && r != -EEXIST)
+            return flatpak_fail (error, "Failed to add architecture to seccomp filter");
+
+          if (multiarch && extra_arches != NULL)
+            {
+              unsigned i;
+              for (i = 0; extra_arches[i] != 0; i++)
+                {
+                  r = seccomp_arch_add (seccomp, extra_arches[i]);
+                  if (r < 0 && r != -EEXIST)
+                    return flatpak_fail (error, "Failed to add multiarch architecture to seccomp filter");
+                }
+            }
+        }
+    }
+
+  /* TODO: Should we filter the kernel keyring syscalls in some way?
+   * We do want them to be used by desktop apps, but they could also perhaps
+   * leak system stuff or secrets from other apps.
+   */
+
+  for (i = 0; i < G_N_ELEMENTS (syscall_blacklist); i++)
+    {
+      int scall = syscall_blacklist[i].scall;
+      if (syscall_blacklist[i].arg)
+        r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 1, *syscall_blacklist[i].arg);
+      else
+        r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 0);
+      if (r < 0 && r == -EFAULT /* unknown syscall */)
+        return flatpak_fail (error, "Failed to block syscall %d", scall);
+    }
+
+  if (!devel)
+    {
+      for (i = 0; i < G_N_ELEMENTS (syscall_nondevel_blacklist); i++)
+        {
+          int scall = syscall_nondevel_blacklist[i].scall;
+          if (syscall_nondevel_blacklist[i].arg)
+            r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 1, 
*syscall_nondevel_blacklist[i].arg);
+          else
+            r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 0);
+
+          if (r < 0 && r == -EFAULT /* unknown syscall */)
+            return flatpak_fail (error, "Failed to block syscall %d", scall);
+        }
+    }
+
+  /* Socket filtering doesn't work on e.g. i386, so ignore failures here
+   * However, we need to user seccomp_rule_add_exact to avoid libseccomp doing
+   * something else: https://github.com/seccomp/libseccomp/issues/8 */
+  for (i = 0; i < G_N_ELEMENTS (socket_family_blacklist); i++)
+    {
+      int family = socket_family_blacklist[i];
+      if (i == G_N_ELEMENTS (socket_family_blacklist) - 1)
+        seccomp_rule_add_exact (seccomp, SCMP_ACT_ERRNO (EAFNOSUPPORT), SCMP_SYS (socket), 1, SCMP_A0 
(SCMP_CMP_GE, family));
+      else
+        seccomp_rule_add_exact (seccomp, SCMP_ACT_ERRNO (EAFNOSUPPORT), SCMP_SYS (socket), 1, SCMP_A0 
(SCMP_CMP_EQ, family));
+    }
+
+  fd = g_file_open_tmp ("flatpak-seccomp-XXXXXX", &path, error);
+  if (fd == -1)
+    return FALSE;
+
+  unlink (path);
+
+  if (seccomp_export_bpf (seccomp, fd) != 0)
+    {
+      close (fd);
+      return flatpak_fail (error, "Failed to export bpf");
+    }
+
+  lseek (fd, 0, SEEK_SET);
+
+  fd_str = g_strdup_printf ("%d", fd);
+  if (fd_array)
+    g_array_append_val (fd_array, fd);
+
+  add_args (argv_array,
+            "--seccomp", fd_str,
+            NULL);
+
+  fd = -1; /* Don't close on success */
+
+  return TRUE;
+}
+#endif
+
 #ifdef HAVE_BWRAP
 static gboolean
 add_bwrap (GPtrArray   *array,
@@ -224,6 +583,22 @@ expand_thumbnailing_cmd (const char  *cmd,
     }
 #endif
 
+#ifdef ENABLE_SECCOMP
+  const char *arch;
+
+  arch = flatpak_get_arch ();
+  g_assert (arch);
+  if (!setup_seccomp (array,
+                      script->fd_array,
+                      arch,
+                      FALSE,
+                      FALSE,
+                      error))
+    {
+      goto bail;
+    }
+#endif
+
   got_in = got_out = FALSE;
   for (i = 0; cmd_elems[i] != NULL; i++)
     {


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