-
Simon McVittie authored
No functional changes. Signed-off-by:
Simon McVittie <smcv@collabora.com>
Simon McVittie authoredNo functional changes. Signed-off-by:
Simon McVittie <smcv@collabora.com>
flatpak-run-x11.c 10.87 KiB
/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s:
* Taken from Flatpak
* Last updated: Flatpak 1.15.10
* Copyright © 2014-2019 Red Hat, Inc
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* 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 (g_steal_fd (&xauth_tmpf.fd));
if (tmp_fd != -1)
{
static const char dest[] = "/run/pressure-vessel/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");
}
}