summaryrefslogtreecommitdiff
path: root/common/flatpak-run-x11.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/flatpak-run-x11.c')
-rw-r--r--common/flatpak-run-x11.c333
1 files changed, 333 insertions, 0 deletions
diff --git a/common/flatpak-run-x11.c b/common/flatpak-run-x11.c
new file mode 100644
index 00000000..af8f0fa5
--- /dev/null
+++ b/common/flatpak-run-x11.c
@@ -0,0 +1,333 @@
+/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s:
+ * Copyright © 2014-2019 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Alexander Larsson <alexl@redhat.com>
+ */
+
+#include "config.h"
+#include "flatpak-run-x11-private.h"
+
+#include <sys/utsname.h>
+
+#ifdef ENABLE_XAUTH
+#include <X11/Xauth.h>
+#endif
+
+/* This is part of the X11 protocol, so we can safely hard-code it here */
+#define FamilyInternet6 (6)
+
+#ifdef ENABLE_XAUTH
+static gboolean
+auth_streq (const char *str,
+ const char *au_str,
+ size_t au_len)
+{
+ return au_len == strlen (str) && memcmp (str, au_str, au_len) == 0;
+}
+
+static gboolean
+xauth_entry_should_propagate (const Xauth *xa,
+ int family,
+ const char *remote_hostname,
+ const char *local_hostname,
+ const char *number)
+{
+ /* ensure entry isn't for a different type of access */
+ if (family != FamilyWild && xa->family != family && xa->family != FamilyWild)
+ return FALSE;
+
+ /* ensure entry isn't for remote access, except that if remote_hostname
+ * is specified, then remote access to that hostname is OK */
+ if (xa->family != FamilyWild && xa->family != FamilyLocal &&
+ (remote_hostname == NULL ||
+ !auth_streq (remote_hostname, xa->address, xa->address_length)))
+ return FALSE;
+
+ /* ensure entry is for this machine */
+ if (xa->family == FamilyLocal && !auth_streq (local_hostname, xa->address, xa->address_length))
+ {
+ /* OpenSUSE inherits the hostname value from DHCP without updating
+ * its X11 authentication cookie. The old hostname value can still
+ * be found in the environment variable XAUTHLOCALHOSTNAME.
+ * For reference:
+ * https://bugzilla.opensuse.org/show_bug.cgi?id=262309
+ * For this reason if we have a cookie whose address is equal to the
+ * variable XAUTHLOCALHOSTNAME, we still need to propagate it, but
+ * we also need to change its address to `unames.nodename`.
+ */
+ const char *xauth_local_hostname;
+ xauth_local_hostname = g_getenv ("XAUTHLOCALHOSTNAME");
+ if (xauth_local_hostname == NULL)
+ return FALSE;
+
+ if (!auth_streq ((char *) xauth_local_hostname, xa->address, xa->address_length))
+ return FALSE;
+ }
+
+ /* ensure entry is for this session */
+ if (xa->number != NULL && !auth_streq (number, xa->number, xa->number_length))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+write_xauth (int family,
+ const char *remote_host,
+ const char *number,
+ FILE *output)
+{
+ Xauth *xa, local_xa;
+ char *filename;
+ FILE *f;
+ struct utsname unames;
+
+ if (uname (&unames))
+ {
+ g_warning ("uname failed");
+ return;
+ }
+
+ filename = XauFileName ();
+ f = fopen (filename, "rb");
+ if (f == NULL)
+ return;
+
+ while (TRUE)
+ {
+ xa = XauReadAuth (f);
+ if (xa == NULL)
+ break;
+ if (xauth_entry_should_propagate (xa, family, remote_host,
+ unames.nodename, number))
+ {
+ local_xa = *xa;
+
+ if (local_xa.family == FamilyLocal &&
+ !auth_streq (unames.nodename, local_xa.address, local_xa.address_length))
+ {
+ /* If we decided to propagate this cookie, but its address
+ * doesn't match `unames.nodename`, we need to change it or
+ * inside the container it will not work.
+ */
+ local_xa.address = unames.nodename;
+ local_xa.address_length = strlen (local_xa.address);
+ }
+
+ if (!XauWriteAuth (output, &local_xa))
+ g_warning ("xauth write error");
+ }
+
+ XauDisposeAuth (xa);
+ }
+
+ fclose (f);
+}
+#else /* !ENABLE_XAUTH */
+
+/* When not doing Xauth, any distinct values will do, but use the same
+ * ones Xauth does so that we can refer to them in our unit test. */
+#define FamilyLocal (256)
+#define FamilyWild (65535)
+
+#endif /* !ENABLE_XAUTH */
+
+/*
+ * @family: (out) (not optional):
+ * @x11_socket: (out) (not optional):
+ * @display_nr_out: (out) (not optional):
+ */
+gboolean
+flatpak_run_parse_x11_display (const char *display,
+ int *family,
+ char **x11_socket,
+ char **remote_host,
+ char **display_nr_out,
+ GError **error)
+{
+ const char *colon;
+ const char *display_nr;
+ const char *display_nr_end;
+
+ /* Use the last ':', not the first, to cope with [::1]:0 */
+ colon = strrchr (display, ':');
+
+ if (colon == NULL)
+ return glnx_throw (error, "No colon found in DISPLAY=%s", display);
+
+ if (!g_ascii_isdigit (colon[1]))
+ return glnx_throw (error, "Colon not followed by a digit in DISPLAY=%s", display);
+
+ display_nr = &colon[1];
+ display_nr_end = display_nr;
+
+ while (g_ascii_isdigit (*display_nr_end))
+ display_nr_end++;
+
+ *display_nr_out = g_strndup (display_nr, display_nr_end - display_nr);
+
+ if (display == colon || g_str_has_prefix (display, "unix:"))
+ {
+ *family = FamilyLocal;
+ *x11_socket = g_strdup_printf ("/tmp/.X11-unix/X%s", *display_nr_out);
+ }
+ else if (display[0] == '[' && display[colon - display - 1] == ']')
+ {
+ *family = FamilyInternet6;
+ *remote_host = g_strndup (display + 1, colon - display - 2);
+ }
+ else
+ {
+ *family = FamilyWild;
+ *remote_host = g_strndup (display, colon - display);
+ }
+
+ return TRUE;
+}
+
+void
+flatpak_run_add_x11_args (FlatpakBwrap *bwrap,
+ gboolean allowed,
+ FlatpakContextShares shares)
+{
+ g_autofree char *x11_socket = NULL;
+ const char *display;
+ g_autoptr(GError) local_error = NULL;
+
+ /* Always cover /tmp/.X11-unix, that way we never see the host one in case
+ * we have access to the host /tmp. If you request X access we'll put the right
+ * thing in this anyway.
+ *
+ * We need to be a bit careful here, because there are two situations in
+ * which potentially hostile processes have access to /tmp and could
+ * create symlinks, which in principle could cause us to create the
+ * directory and mount the tmpfs at the target of the symlink instead
+ * of in the intended place:
+ *
+ * - With --filesystem=/tmp, it's the host /tmp - but because of the
+ * special historical status of /tmp/.X11-unix, we can assume that
+ * it is pre-created by the host system before user code gets to run.
+ *
+ * - When /tmp is shared between all instances of the same app ID,
+ * in principle the app has control over what's in /tmp, but in
+ * practice it can't interfere with /tmp/.X11-unix, because we do
+ * this unconditionally - therefore by the time app code runs,
+ * /tmp/.X11-unix is already a mount point, meaning the app cannot
+ * rename or delete it.
+ */
+ flatpak_bwrap_add_args (bwrap,
+ "--tmpfs", "/tmp/.X11-unix",
+ NULL);
+
+ if (!allowed)
+ {
+ flatpak_bwrap_unset_env (bwrap, "DISPLAY");
+ return;
+ }
+
+ g_info ("Allowing x11 access");
+
+ display = g_getenv ("DISPLAY");
+
+ if (display != NULL)
+ {
+ g_autofree char *remote_host = NULL;
+ g_autofree char *display_nr = NULL;
+ int family = -1;
+
+ if (!flatpak_run_parse_x11_display (display, &family, &x11_socket,
+ &remote_host, &display_nr,
+ &local_error))
+ {
+ g_warning ("%s", local_error->message);
+ flatpak_bwrap_unset_env (bwrap, "DISPLAY");
+ return;
+ }
+
+ g_assert (display_nr != NULL);
+
+ if (x11_socket != NULL
+ && g_file_test (x11_socket, G_FILE_TEST_EXISTS))
+ {
+ g_assert (g_str_has_prefix (x11_socket, "/tmp/.X11-unix/X"));
+ flatpak_bwrap_add_args (bwrap,
+ "--ro-bind", x11_socket, x11_socket,
+ NULL);
+ flatpak_bwrap_set_env (bwrap, "DISPLAY", display, TRUE);
+ }
+ else if ((shares & FLATPAK_CONTEXT_SHARED_NETWORK) == 0)
+ {
+ /* If DISPLAY is for example :42 but /tmp/.X11-unix/X42
+ * doesn't exist, then the only way this is going to work
+ * is if the app can connect to abstract socket
+ * @/tmp/.X11-unix/X42 or to TCP port localhost:6042,
+ * either of which requires a shared network namespace.
+ *
+ * Alternatively, if DISPLAY is othermachine:23, then we
+ * definitely need access to TCP port othermachine:6023. */
+ if (x11_socket != NULL)
+ g_warning ("X11 socket %s does not exist in filesystem.",
+ x11_socket);
+ else
+ g_warning ("Remote X11 display detected.");
+
+ g_warning ("X11 access will require --share=network permission.");
+ }
+ else if (x11_socket != NULL)
+ {
+ g_warning ("X11 socket %s does not exist in filesystem, "
+ "trying to use abstract socket instead.",
+ x11_socket);
+ }
+ else
+ {
+ g_debug ("Assuming --share=network gives access to remote X11");
+ }
+
+#ifdef ENABLE_XAUTH
+ g_auto(GLnxTmpfile) xauth_tmpf = { 0, };
+
+ if (glnx_open_anonymous_tmpfile_full (O_RDWR | O_CLOEXEC, "/tmp", &xauth_tmpf, NULL))
+ {
+ FILE *output = fdopen (xauth_tmpf.fd, "wb");
+ if (output != NULL)
+ {
+ /* fd is now owned by output, steal it from the tmpfile */
+ int tmp_fd = dup (glnx_steal_fd (&xauth_tmpf.fd));
+ if (tmp_fd != -1)
+ {
+ static const char dest[] = "/run/flatpak/Xauthority";
+
+ write_xauth (family, remote_host, display_nr, output);
+ flatpak_bwrap_add_args_data_fd (bwrap, "--ro-bind-data", tmp_fd, dest);
+
+ flatpak_bwrap_set_env (bwrap, "XAUTHORITY", dest, TRUE);
+ }
+
+ fclose (output);
+
+ if (tmp_fd != -1)
+ lseek (tmp_fd, 0, SEEK_SET);
+ }
+ }
+#endif
+ }
+ else
+ {
+ flatpak_bwrap_unset_env (bwrap, "DISPLAY");
+ }
+}