Repository: flatpak/flatpak (C)

Issue:
[Bug]: input methods that use WAYLAND_SOCKET environment variable regress in 1.15.6
### Checklist

- [X] I agree to follow the [Code of Conduct](https://github.com/flatpak/flatpak/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
- [X] I have searched the [issue tracker](https://www.github.com/flatpak/flatpak/issues) for a bug that matches the one I want to file, without success.
- [X] If this is an issue with a particular app, I have tried filing it in the appropriate issue tracker for the app (e.g. under https://github.com/flathub/) and determined that it is an issue with Flatpak itself.
- [X] This issue is not a report of a security vulnerability (see [here](https://github.com/flatpak/flatpak/blob/main/SECURITY.md) if you need to report a security issue).

### Flatpak version

1.15.6

### What Linux distribution are you using?

Arch Linux

### Linux distribution version

Archlinux

### What architecture are you using?

x86_64

### How to reproduce

I have an application that may use WAYLAND_SOCKET environment to connect to wayland in certain cases. With WAYLAND_SOCKET, the compositor can identify the client and may expose special wayland object to the client, like granting a certain permission.

Specifically, this is used by weston and kwin to allow input method process to be only forked by compositor.

To reproduce this with weston, install org.fcitx.Fcitx5 from flathub.

Put following content in ~/.config/weston.ini 
```
[core]
xwayland=false

[input-method]
path=/home/user/.local/share/flatpak/exports/bin/org.fcitx.Fcitx5
```

and start weston (make sure there's no fcitx5 running).

### Expected Behavior

fcitx connects to wayland correctly.

### Actual Behavior

fcitx quits from weston output saying something about wayland disconnected.

### Additional Information

I believe this is relevant to the new wayland security context feature's implementation.

https://gitlab.freedesktop.org/wayland/wayland/-/blob/main/src/wayland-client.c#L1297

wl_display_connect will unset WAYLAND_SOCKET when it's called. The new wayland security context actually calls wl_display_connect and it will effectively unset WAYLAND_SOCKET for the sub process.

WAYLAND_SOCKET should not be used by flatpak for this, because the fd specified by WAYLAND_SOCKET can be only used once.



Discussion: cc @emersion @swick 

> I have an application that may use WAYLAND_SOCKET environment to connect to wayland in certain cases. With WAYLAND_SOCKET, the compositor can identify the client and may expose special wayland object to the client, like granting a certain permission.

It sounds as though the compositor is implementing an ad-hoc precursor of the "security context" extension, so that instead of Flatpak giving the app an identifiable socket, the Wayland compositor is giving Flatpak an identifiable socket.

> wl_display_connect will unset WAYLAND_SOCKET when it's called

This is unsafe to do in a multi-threaded process: any thread could be calling `getenv()` concurrently.
It seems odd to me to be using an input method installed as a Flatpak app and giving it special privileges that the rest of the desktop environment - even parts of it that are explicitly in the trusted computing base, like the desktop shell - do not have. Normally, Flatpak apps have *fewer* privileges than un-sandboxed desktop apps.

In particular, the input method seems like something very "powerful" that needs to be absolutely trusted: it can do anything that you would be able to input with the keyboard.
Indeed a bit weird. I was aware that compositors like weston create special privileged sockets and exec apps which then have access to them. For this to be useful all apps must be separated from the trusted computing base the compositor is running in, and flatpak gives you this separation. So, I kind of get the use case here and I think this is a genuine bug.
> It seems odd to me to be using an input method installed as a Flatpak app and giving it special privileges that the rest of the desktop environment - even parts of it that are explicitly in the trusted computing base, like the desktop shell - do not have. Normally, Flatpak apps have _fewer_ privileges than un-sandboxed desktop apps.
> 
> In particular, the input method seems like something very "powerful" that needs to be absolutely trusted: it can do anything that you would be able to input with the keyboard.

In the real world, android allows third-party input method from the very beginning, and iOS allows that in some recent versions. I don't think Input method and sandbox really conflicts. I think the "trust" comes from how you install it and set it as input method, not by the fact whether it runs within a sandbox or not.

On other hand, sandbox may help enhance the security (I'm not saying that this is guaranteed today with flatpak though), e.g. preventing other app from accessing the sensitive data that input method saves because input method will use user typing history to adapt to improve user experience. 

Right now security context and WAYLAND_SOCKET provides two similar but somewhat different capabilities:
1. WAYLAND_SOCKET can be only used by ONE wayland client. 
2. security context is applied to ALL wayland clients in the sandbox.

While there is some conflict with the implementation (listener fd and WAYLAND_SOCKET fd). If we put away the implementation details aside, as today WAYLAND_SOCKET indeed provides something that security context doesn't and I hope at least it can be used with flatpak for now until there's some other alternatives (or maybe extend security context) implemented.
> In the real world, android allows third-party input method from the very beginning, and iOS allows that in some recent versions

Yes, and they're very attractive to attackers on those platforms, because a malicious application that says "I'm an input method" is the perfect combination of user-installable and extremely powerful.



Fix (patch):
diff --git a/common/flatpak-context-private.h b/common/flatpak-context-private.h
index 3d1b3418e1..5a9ebe4cdd 100644
--- a/common/flatpak-context-private.h
+++ b/common/flatpak-context-private.h
@@ -50,6 +50,7 @@ typedef enum {
   FLATPAK_CONTEXT_SOCKET_PCSC        = 1 << 7,
   FLATPAK_CONTEXT_SOCKET_CUPS        = 1 << 8,
   FLATPAK_CONTEXT_SOCKET_GPG_AGENT   = 1 << 9,
+  FLATPAK_CONTEXT_SOCKET_INHERIT_WAYLAND_SOCKET = 1 << 10,
 } FlatpakContextSockets;
 
 typedef enum {
diff --git a/common/flatpak-context.c b/common/flatpak-context.c
index 2d98d5c912..297a89ef1c 100644
--- a/common/flatpak-context.c
+++ b/common/flatpak-context.c
@@ -61,6 +61,7 @@ const char *flatpak_context_sockets[] = {
   "pcsc",
   "cups",
   "gpg-agent",
+  "inherit-wayland-socket",
   NULL
 };
 
diff --git a/common/flatpak-run-sockets.c b/common/flatpak-run-sockets.c
index a20e9f34db..be120f36f8 100644
--- a/common/flatpak-run-sockets.c
+++ b/common/flatpak-run-sockets.c
@@ -178,12 +178,15 @@ flatpak_run_add_socket_args_environment (FlatpakBwrap         *bwrap,
 {
   gboolean has_wayland = FALSE;
   gboolean allow_x11;
+  gboolean inherit_wayland_socket;
 
   if (sockets & FLATPAK_CONTEXT_SOCKET_WAYLAND)
     {
       g_info ("Allowing wayland access");
       g_assert (app_id && instance_id);
-      has_wayland = flatpak_run_add_wayland_args (bwrap, app_id, instance_id);
+      inherit_wayland_socket = (sockets & FLATPAK_CONTEXT_SOCKET_INHERIT_WAYLAND_SOCKET) != 0;
+      has_wayland = flatpak_run_add_wayland_args (bwrap, app_id, instance_id,
+                                                  inherit_wayland_socket);
     }
 
   if ((sockets & FLATPAK_CONTEXT_SOCKET_FALLBACK_X11) != 0)
diff --git a/common/flatpak-run-wayland-private.h b/common/flatpak-run-wayland-private.h
index 14389a9190..ad00d234ee 100644
--- a/common/flatpak-run-wayland-private.h
+++ b/common/flatpak-run-wayland-private.h
@@ -31,6 +31,7 @@ G_BEGIN_DECLS
 gboolean
 flatpak_run_add_wayland_args (FlatpakBwrap *bwrap,
                               const char   *app_id,
-                              const char   *instance_id);
+                              const char   *instance_id,
+                              gboolean      inherit_wayland_socket);
 
 G_END_DECLS
diff --git a/common/flatpak-run-wayland.c b/common/flatpak-run-wayland.c
index 0670ab6837..1aafddbb04 100644
--- a/common/flatpak-run-wayland.c
+++ b/common/flatpak-run-wayland.c
@@ -30,6 +30,38 @@
 
 #include "flatpak-utils-private.h"
 
+static char *
+get_wayland_socket_path (void)
+{
+  g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
+  const char *wayland_display;
+
+  wayland_display = g_getenv ("WAYLAND_DISPLAY");
+  if (!wayland_display)
+    wayland_display = "wayland-0";
+
+  if (wayland_display[0] == '/')
+    return g_strdup (wayland_display);
+
+  return g_build_filename (user_runtime_dir, wayland_display, NULL);
+}
+
+static int
+get_wayland_socket_fd (void)
+{
+  const char *wayland_socket_fd;
+  guint64 fd;
+
+  wayland_socket_fd = g_getenv ("WAYLAND_SOCKET");
+  if (!wayland_socket_fd)
+    return -1;
+
+  if (!g_ascii_string_to_unsigned (wayland_socket_fd, 10, 0, INT_MAX, &fd, NULL))
+    return -1;
+
+  return (int) fd;
+}
+
 #ifdef ENABLE_WAYLAND_SECURITY_CONTEXT
 
 static void registry_handle_global (void *data, struct wl_registry *registry,
@@ -57,6 +89,29 @@ static const struct wl_registry_listener registry_listener = {
   .global_remove = registry_handle_global_remove,
 };
 
+/* Similar to wl_display_connect (), but does not use WAYLAND_SOCKET,
+ * which can only be used once, and also does not unset environment
+ * variables, which would not be thread-safe. */
+static struct wl_display *
+connect_to_wayland_display (void)
+{
+  struct sockaddr_un sockaddr = {0};
+  g_autofree char *socket_path = NULL;
+  glnx_autofd int fd = -1;
+
+  socket_path = get_wayland_socket_path ();
+  fd = socket (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+  if (fd < 0)
+    return NULL;
+
+  sockaddr.sun_family = AF_UNIX;
+  snprintf (sockaddr.sun_path, sizeof (sockaddr.sun_path), "%s", socket_path);
+  if (connect (fd, (struct sockaddr *) &sockaddr, sizeof (sockaddr)) < 0)
+    return NULL;
+
+  return wl_display_connect_to_fd (g_steal_fd (&fd));
+}
+
 static char *
 create_wl_socket (char *template)
 {
@@ -94,7 +149,17 @@ flatpak_run_add_wayland_security_context_args (FlatpakBwrap *bwrap,
 
   *available_out = TRUE;
 
-  display = wl_display_connect (NULL);
+  /* We don't use wl_display_connect () here, for two reasons:
+   * 1. It would unsetenv ("WAYLAND_SOCKET"), which is not thread-safe.
+   * 2. If the compositor has set WAYLAND_SOCKET to a special, higher-privileged
+   *    socket, the application should be able to get those privileges for its first
+   *    connection; but that fd can only be used once, so having flatpak itself
+   *    do that first connection would defeat that mechanism.
+   *
+   * We still set up a security context for the second and subsequent connections
+   * to Wayland from within the sandbox.
+   */
+  display = connect_to_wayland_display ();
   if (!display)
     return FALSE;
 
@@ -171,14 +236,15 @@ flatpak_run_add_wayland_security_context_args (FlatpakBwrap *bwrap,
 gboolean
 flatpak_run_add_wayland_args (FlatpakBwrap *bwrap,
                               const char   *app_id,
-                              const char   *instance_id)
+                              const char   *instance_id,
+                              gboolean      inherit_wayland_socket)
 {
   const char *wayland_display;
-  g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
   g_autofree char *wayland_socket = NULL;
   g_autofree char *sandbox_wayland_socket = NULL;
   gboolean res = FALSE;
   struct stat statbuf;
+  int fd;
 
 #ifdef ENABLE_WAYLAND_SECURITY_CONTEXT
   gboolean security_context_available = FALSE;
@@ -194,10 +260,7 @@ flatpak_run_add_wayland_args (FlatpakBwrap *bwrap,
   if (!wayland_display)
     wayland_display = "wayland-0";
 
-  if (wayland_display[0] == '/')
-    wayland_socket = g_strdup (wayland_display);
-  else
-    wayland_socket = g_build_filename (user_runtime_dir, wayland_display, NULL);
+  wayland_socket = get_wayland_socket_path ();
 
   if (!g_str_has_prefix (wayland_display, "wayland-") ||
       strchr (wayland_display, '/') != NULL)
@@ -217,5 +280,29 @@ flatpak_run_add_wayland_args (FlatpakBwrap *bwrap,
                               NULL);
       flatpak_bwrap_add_runtime_dir_member (bwrap, wayland_display);
     }
+
+  /* If inherit-wayland-socket is not set, unset WAYLAND_SOCKET unconditionally
+   * without checking the validity of the value of WAYLAND_SOCKET. */
+  if (!inherit_wayland_socket)
+    flatpak_bwrap_unset_env (bwrap, "WAYLAND_SOCKET");
+
+  fd = get_wayland_socket_fd ();
+  if (fd >= 0)
+    {
+      if (inherit_wayland_socket)
+        {
+          flatpak_bwrap_add_fd (bwrap, fd);
+        }
+      else
+        {
+          /* Make sure the fd is close-on-execute so it won't be inherited by the
+           * application. We do this in preference to closing it, because if this function
+           * was somehow called twice, and the same fd number was reused for an
+           * unrelated purpose, we don't want to close the unrelated fd the
+           * second time. */
+          fcntl (fd, F_SETFD, FD_CLOEXEC);
+        }
+    }
+
   return res;
 }
diff --git a/doc/flatpak-build-finish.xml b/doc/flatpak-build-finish.xml
index 2c7a1393fd..50394baa7b 100644
--- a/doc/flatpak-build-finish.xml
+++ b/doc/flatpak-build-finish.xml
@@ -134,7 +134,7 @@
                     Expose a well-known socket to the application. This updates
                     the [Context] group in the metadata.
                     SOCKET must be one of: x11, wayland, fallback-x11, pulseaudio, system-bus, session-bus,
-                    ssh-auth, pcsc, cups, gpg-agent.
+                    ssh-auth, pcsc, cups, gpg-agent, inherit-wayland-socket.
                     This option can be used multiple times.
                 </para><para>
                     The fallback-x11 option makes the X11 socket available only if
@@ -151,7 +151,7 @@
                     Don't expose a well known socket to the application. This updates
                     the [Context] group in the metadata.
                     SOCKET must be one of: x11, wayland, fallback-x11, pulseaudio, system-bus, session-bus,
-                    ssh-auth, pcsc, cups, gpg-agent.
+                    ssh-auth, pcsc, cups, gpg-agent, inherit-wayland-socket.
                     This option can be used multiple times.
                 </para></listitem>
             </varlistentry>
diff --git a/doc/flatpak-build.xml b/doc/flatpak-build.xml
index a9e29f5bab..d197800f17 100644
--- a/doc/flatpak-build.xml
+++ b/doc/flatpak-build.xml
@@ -149,7 +149,7 @@
                     Expose a well-known socket to the application. This overrides to
                     the Context section from the application metadata.
                     <arg choice="plain">SOCKET</arg> must be one of: x11, wayland, fallback-x11, pulseaudio, system-bus, session-bus,
-                    ssh-auth, pcsc, cups, gpg-agent.
+                    ssh-auth, pcsc, cups, gpg-agent, inherit-wayland-socket.
                     This option can be used multiple times.
                 </para></listitem>
             </varlistentry>
@@ -161,7 +161,7 @@
                     Don't expose a well-known socket to the application. This overrides to
                     the Context section from the application metadata.
                     <arg choice="plain">SOCKET</arg> must be one of: x11, wayland, fallback-x11, pulseaudio, system-bus, session-bus,
-                    ssh-auth, pcsc, cups, gpg-agent.
+                    ssh-auth, pcsc, cups, gpg-agent, inherit-wayland-socket.
                     This option can be used multiple times.
                 </para></listitem>
             </varlistentry>
diff --git a/doc/flatpak-metadata.xml b/doc/flatpak-metadata.xml
index a8ab2ee9b0..d847ed7aa4 100644
--- a/doc/flatpak-metadata.xml
+++ b/doc/flatpak-metadata.xml
@@ -152,7 +152,7 @@
                     <listitem><para>
                         List of well-known sockets to make available in the sandbox.
                         Possible sockets: x11, wayland, fallback-x11, pulseaudio, session-bus, system-bus,
-                        ssh-auth, pcsc, cups.
+                        ssh-auth, pcsc, cups, gpg-agent, inherit-wayland-socket.
                         When making a socket available, flatpak also sets
                         well-known environment variables like DISPLAY or
                         DBUS_SYSTEM_BUS_ADDRESS to let the application
diff --git a/doc/flatpak-override.xml b/doc/flatpak-override.xml
index 352919da96..a74b30f0d9 100644
--- a/doc/flatpak-override.xml
+++ b/doc/flatpak-override.xml
@@ -137,7 +137,7 @@
                     Expose a well-known socket to the application. This overrides to
                     the Context section from the application metadata.
                     <arg choice="plain">SOCKET</arg> must be one of: x11, wayland, fallback-x11, pulseaudio, system-bus, session-bus,
-                    ssh-auth, pcsc, cups, gpg-agent.
+                    ssh-auth, pcsc, cups, gpg-agent, inherit-wayland-socket.
                     This option can be used multiple times.
                 </para></listitem>
             </varlistentry>
@@ -149,7 +149,7 @@
                     Don't expose a well-known socket to the application. This overrides to
                     the Context section from the application metadata.
                     <arg choice="plain">SOCKET</arg> must be one of: x11, wayland, fallback-x11, pulseaudio, system-bus, session-bus,
-                    ssh-auth, pcsc, cups, gpg-agent.
+                    ssh-auth, pcsc, cups, gpg-agent, inherit-wayland-socket.
                     This option can be used multiple times.
                 </para></listitem>
             </varlistentry>
diff --git a/doc/flatpak-run.xml b/doc/flatpak-run.xml
index 7a9611cb04..2cb46b207a 100644
--- a/doc/flatpak-run.xml
+++ b/doc/flatpak-run.xml
@@ -308,7 +308,7 @@
                     Expose a well known socket to the application. This overrides to
                     the Context section from the application metadata.
                     <arg choice="plain">SOCKET</arg> must be one of: x11, wayland, fallback-x11, pulseaudio, system-bus, session-bus,
-                    ssh-auth, pcsc, cups, gpg-agent.
+                    ssh-auth, pcsc, cups, gpg-agent, inherit-wayland-socket.
                     This option can be used multiple times.
                 </para></listitem>
             </varlistentry>
@@ -320,7 +320,7 @@
                     Don't expose a well known socket to the application. This overrides to
                     the Context section from the application metadata.
                     <arg choice="plain">SOCKET</arg> must be one of: x11, wayland, fallback-x11, pulseaudio, system-bus, session-bus,
-                    ssh-auth, pcsc, cups, gpg-agent.
+                    ssh-auth, pcsc, cups, gpg-agent, inherit-wayland-socket.
                     This option can be used multiple times.
                 </para></listitem>
             </varlistentry>