[gnome-desktop] thumbnail: Restrict thumbnailer syscalls using seccomp
- From: Bastien Nocera <hadess src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-desktop] thumbnail: Restrict thumbnailer syscalls using seccomp
- Date: Fri, 21 Jul 2017 12:33:35 +0000 (UTC)
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]