CVE-2021-21261: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection') (CWE-74)
value: Flatpak is a system for building, distributing, and running sandboxed desktop applications on Linux. A bug was discovered in the `flatpak-portal` service that can allow sandboxed applications to execute arbitrary code on the host system (a sandbox escape). This sandbox-escape bug is present in versions from 0.11.4 and before fixed versions 1.8.5 and 1.10.0. The Flatpak portal D-Bus service (`flatpak-portal`, also known by its D-Bus service name `org.freedesktop.portal.Flatpak`) allows apps in a Flatpak sandbox to launch their own subprocesses in a new sandbox instance, either with the same security settings as the caller or with more restrictive security settings. For example, this is used in Flatpak-packaged web browsers such as Chromium to launch subprocesses that will process untrusted web content, and give those subprocesses a more restrictive sandbox than the browser itself. In vulnerable versions, the Flatpak portal service passes caller-specified environment variables to non-sandboxed processes on the host system, and in particular to the `flatpak run` command that is used to launch the new sandbox instance. A malicious or compromised Flatpak app could set environment variables that are trusted by the `flatpak run` command, and use them to execute arbitrary code that is not in a sandbox. As a workaround, this vulnerability can be mitigated by preventing the `flatpak-portal` service from starting, but that mitigation will prevent many Flatpak apps from working correctly. This is fixed in versions 1.8.5 and 1.10.0.
CWE description: The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
CVSS v3 base score: 8.8
Published: 2021-01-14T20:15Z
Repository: https://github.com/flatpak/flatpak
Language: XML
Files changed: common/flatpak-context.c, doc/flatpak-build-finish.xml, doc/flatpak-build.xml, doc/flatpak-override.xml, doc/flatpak-run.xml
Commit date: 2021-01-14 09:33:24 +0100
Commit message: context: Add --env-fd option

This allows environment variables to be added to the context without
making their values visible to processes running under a different uid,
which might be significant if the variable's value is a token or some
other secret value.

Signed-off-by: Simon McVittie <smcv@collabora.com>
Part-of: https://github.com/flatpak/flatpak/security/advisories/GHSA-4ppf-fxf6-vxg2

Diff: diff --git a/common/flatpak-context.c b/common/flatpak-context.c
index a94f12e6..19fe883e 100644
--- a/common/flatpak-context.c
+++ b/common/flatpak-context.c
@@ -1117,10 +1117,69 @@ option_env_cb (const gchar *option_name,
 
   flatpak_context_set_env_var (context, split[0], split[1]);
   return TRUE;
 }
 
+static gboolean
+option_env_fd_cb (const gchar *option_name,
+                  const gchar *value,
+                  gpointer     data,
+                  GError     **error)
+{
+  FlatpakContext *context = data;
+  g_autoptr(GBytes) env_block = NULL;
+  gsize remaining;
+  const char *p;
+  guint64 fd;
+  gchar *endptr;
+
+  fd = g_ascii_strtoull (value, &endptr, 10);
+
+  if (endptr == NULL || *endptr != '\0' || fd > G_MAXINT)
+    return glnx_throw (error, "Not a valid file descriptor: %s", value);
+
+  env_block = glnx_fd_readall_bytes ((int) fd, NULL, error);
+
+  if (env_block == NULL)
+    return FALSE;
+
+  p = g_bytes_get_data (env_block, &remaining);
+
+  /* env_block might not be \0-terminated */
+  while (remaining > 0)
+    {
+      size_t len = strnlen (p, remaining);
+      const char *equals;
+
+      g_assert (len <= remaining);
+
+      equals = memchr (p, '=', len);
+
+      if (equals == NULL || equals == p)
+        return glnx_throw (error,
+                           "Environment variable must be given in the form VARIABLE=VALUE, not %.*s", (int) len, p);
+
+      flatpak_context_set_env_var (context,
+                                   g_strndup (p, equals - p),
+                                   g_strndup (equals + 1, len - (equals - p) - 1));
+      p += len;
+      remaining -= len;
+
+      if (remaining > 0)
+        {
+          g_assert (*p == '\0');
+          p += 1;
+          remaining -= 1;
+        }
+    }
+
+  if (fd >= 3)
+    close (fd);
+
+  return TRUE;
+}
+
 static gboolean
 option_own_name_cb (const gchar *option_name,
                     const gchar *value,
                     gpointer     data,
                     GError     **error)
@@ -1314,10 +1373,11 @@ static GOptionEntry context_options[] = {
   { "allow", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_allow_cb, N_("Allow feature"), N_("FEATURE") },
   { "disallow", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_disallow_cb, N_("Don't allow feature"), N_("FEATURE") },
   { "filesystem", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_filesystem_cb, N_("Expose filesystem to app (:ro for read-only)"), N_("FILESYSTEM[:ro]") },
   { "nofilesystem", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nofilesystem_cb, N_("Don't expose filesystem to app"), N_("FILESYSTEM") },
   { "env", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_env_cb, N_("Set environment variable"), N_("VAR=VALUE") },
+  { "env-fd", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_env_fd_cb, N_("Read environment variables in env -0 format from FD"), N_("FD") },
   { "own-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_own_name_cb, N_("Allow app to own name on the session bus"), N_("DBUS_NAME") },
   { "talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_talk_name_cb, N_("Allow app to talk to name on the session bus"), N_("DBUS_NAME") },
   { "no-talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_no_talk_name_cb, N_("Don't allow app to talk to name on the session bus"), N_("DBUS_NAME") },
   { "system-own-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_system_own_name_cb, N_("Allow app to own name on the system bus"), N_("DBUS_NAME") },
   { "system-talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_system_talk_name_cb, N_("Allow app to talk to name on the system bus"), N_("DBUS_NAME") },
diff --git a/doc/flatpak-build-finish.xml b/doc/flatpak-build-finish.xml
index b46778ca..da96b518 100644
--- a/doc/flatpak-build-finish.xml
+++ b/doc/flatpak-build-finish.xml
@@ -284,10 +284,28 @@ key=v1;v2;
                     This overrides to the Context section from the application metadata.
                     This option can be used multiple times.
                 </para></listitem>
             </varlistentry>
 
+            <varlistentry>
+                <term><option>--env-fd=<replaceable>FD</replaceable></option></term>
+
+                <listitem><para>
+                    Read environment variables from the file descriptor
+                    <replaceable>FD</replaceable>, and set them as if
+                    via <option>--env</option>. This can be used to avoid
+                    environment variables and their values becoming visible
+                    to other users.
+                </para><para>
+                    Each environment variable is in the form
+                    <replaceable>VAR</replaceable>=<replaceable>VALUE</replaceable>
+                    followed by a zero byte. This is the same format used by
+                    <literal>env -0</literal> and
+                    <filename>/proc/*/environ</filename>.
+                </para></listitem>
+            </varlistentry>
+
             <varlistentry>
                 <term><option>--own-name=NAME</option></term>
 
                 <listitem><para>
                     Allow the application to own the well known name <arg choice="plain">NAME</arg> on the session bus.
diff --git a/doc/flatpak-build.xml b/doc/flatpak-build.xml
index f269376c..9972699c 100644
--- a/doc/flatpak-build.xml
+++ b/doc/flatpak-build.xml
@@ -286,10 +286,28 @@ key=v1;v2;
                     This overrides to the Context section from the application metadata.
                     This option can be used multiple times.
                 </para></listitem>
             </varlistentry>
 
+            <varlistentry>
+                <term><option>--env-fd=<replaceable>FD</replaceable></option></term>
+
+                <listitem><para>
+                    Read environment variables from the file descriptor
+                    <replaceable>FD</replaceable>, and set them as if
+                    via <option>--env</option>. This can be used to avoid
+                    environment variables and their values becoming visible
+                    to other users.
+                </para><para>
+                    Each environment variable is in the form
+                    <replaceable>VAR</replaceable>=<replaceable>VALUE</replaceable>
+                    followed by a zero byte. This is the same format used by
+                    <literal>env -0</literal> and
+                    <filename>/proc/*/environ</filename>.
+                </para></listitem>
+            </varlistentry>
+
             <varlistentry>
                 <term><option>--own-name=NAME</option></term>
 
                 <listitem><para>
                     Allow the application to own the well-known name NAME on the session bus.
diff --git a/doc/flatpak-override.xml b/doc/flatpak-override.xml
index 89214cf4..fe3a70a9 100644
--- a/doc/flatpak-override.xml
+++ b/doc/flatpak-override.xml
@@ -260,10 +260,28 @@ key=v1;v2;
                     This overrides to the Context section from the application metadata.
                     This option can be used multiple times.
                 </para></listitem>
             </varlistentry>
 
+            <varlistentry>
+                <term><option>--env-fd=<replaceable>FD</replaceable></option></term>
+
+                <listitem><para>
+                    Read environment variables from the file descriptor
+                    <replaceable>FD</replaceable>, and set them as if
+                    via <option>--env</option>. This can be used to avoid
+                    environment variables and their values becoming visible
+                    to other users.
+                </para><para>
+                    Each environment variable is in the form
+                    <replaceable>VAR</replaceable>=<replaceable>VALUE</replaceable>
+                    followed by a zero byte. This is the same format used by
+                    <literal>env -0</literal> and
+                    <filename>/proc/*/environ</filename>.
+                </para></listitem>
+            </varlistentry>
+
             <varlistentry>
                 <term><option>--own-name=NAME</option></term>
 
                 <listitem><para>
                     Allow the application to own the well-known name <arg choice="plain">NAME</arg> on the session bus.
diff --git a/doc/flatpak-run.xml b/doc/flatpak-run.xml
index d7e6b5bc..4aa558a7 100644
--- a/doc/flatpak-run.xml
+++ b/doc/flatpak-run.xml
@@ -407,10 +407,28 @@ key=v1;v2;
                     This overrides to the Context section from the application metadata.
                     This option can be used multiple times.
                 </para></listitem>
             </varlistentry>
 
+            <varlistentry>
+                <term><option>--env-fd=<replaceable>FD</replaceable></option></term>
+
+                <listitem><para>
+                    Read environment variables from the file descriptor
+                    <replaceable>FD</replaceable>, and set them as if
+                    via <option>--env</option>. This can be used to avoid
+                    environment variables and their values becoming visible
+                    to other users.
+                </para><para>
+                    Each environment variable is in the form
+                    <replaceable>VAR</replaceable>=<replaceable>VALUE</replaceable>
+                    followed by a zero byte. This is the same format used by
+                    <literal>env -0</literal> and
+                    <filename>/proc/*/environ</filename>.
+                </para></listitem>
+            </varlistentry>
+
             <varlistentry>
                 <term><option>--own-name=NAME</option></term>
 
                 <listitem><para>
                     Allow the application to own the well known name <arg choice="plain">NAME</arg> on the session bus.

Fixed code: static gboolean
option_env_fd_cb (const gchar *option_name,
                  const gchar *value,
                  gpointer     data,
                  GError     **error)
{
  FlatpakContext *context = data;
  g_autoptr(GBytes) env_block = NULL;
  gsize remaining;
  const char *p;
  guint64 fd;
  gchar *endptr;

  fd = g_ascii_strtoull (value, &endptr, 10);

  if (endptr == NULL || *endptr != '\0' || fd > G_MAXINT)
    return glnx_throw (error, "Not a valid file descriptor: %s", value);

  env_block = glnx_fd_readall_bytes ((int) fd, NULL, error);

  if (env_block == NULL)
    return FALSE;

  p = g_bytes_get_data (env_block, &remaining);

  /* env_block might not be \0-terminated */
  while (remaining > 0)
    {
      size_t len = strnlen (p, remaining);
      const char *equals;

      g_assert (len <= remaining);

      equals = memchr (p, '=', len);

      if (equals == NULL || equals == p)
        return glnx_throw (error,
                           "Environment variable must be given in the form VARIABLE=VALUE, not %.*s", (int) len, p);

      flatpak_context_set_env_var (context,
                                   g_strndup (p, equals - p),
                                   g_strndup (equals + 1, len - (equals - p) - 1));
      p += len;
      remaining -= len;

      if (remaining > 0)
        {
          g_assert (*p == '\0');
          p += 1;
          remaining -= 1;
        }
    }

  if (fd >= 3)
    close (fd);

  return TRUE;
}

  { "env-fd", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_env_fd_cb, N_("Read environment variables in env -0 format from FD"), N_("FD") },
            <varlistentry>
                <term><option>--env-fd=<replaceable>FD</replaceable></option></term>

                <listitem><para>
                    Read environment variables from the file descriptor
                    <replaceable>FD</replaceable>, and set them as if
                    via <option>--env</option>. This can be used to avoid
                    environment variables and their values becoming visible
                    to other users.
                </para><para>
                    Each environment variable is in the form
                    <replaceable>VAR</replaceable>=<replaceable>VALUE</replaceable>
                    followed by a zero byte. This is the same format used by
                    <literal>env -0</literal> and
                    <filename>/proc/*/environ</filename>.
                </para></listitem>
            </varlistentry>

            <varlistentry>
                <term><option>--env-fd=<replaceable>FD</replaceable></option></term>

                <listitem><para>
                    Read environment variables from the file descriptor
                    <replaceable>FD</replaceable>, and set them as if
                    via <option>--env</option>. This can be used to avoid
                    environment variables and their values becoming visible
                    to other users.
                </para><para>
                    Each environment variable is in the form
                    <replaceable>VAR</replaceable>=<replaceable>VALUE</replaceable>
                    followed by a zero byte. This is the same format used by
                    <literal>env -0</literal> and
                    <filename>/proc/*/environ</filename>.
                </para></listitem>
            </varlistentry>

            <varlistentry>
                <term><option>--env-fd=<replaceable>FD</replaceable></option></term>

                <listitem><para>
                    Read environment variables from the file descriptor
                    <replaceable>FD</replaceable>, and set them as if
                    via <option>--env</option>. This can be used to avoid
                    environment variables and their values becoming visible
                    to other users.
                </para><para>
                    Each environment variable is in the form
                    <replaceable>VAR</replaceable>=<replaceable>VALUE</replaceable>
                    followed by a zero byte. This is the same format used by
                    <literal>env -0</literal> and
                    <filename>/proc/*/environ</filename>.
                </para></listitem>
            </varlistentry>

            <varlistentry>
                <term><option>--env-fd=<replaceable>FD</replaceable></option></term>

                <listitem><para>
                    Read environment variables from the file descriptor
                    <replaceable>FD</replaceable>, and set them as if
                    via <option>--env</option>. This can be used to avoid
                    environment variables and their values becoming visible
                    to other users.
                </para><para>
                    Each environment variable is in the form
                    <replaceable>VAR</replaceable>=<replaceable>VALUE</replaceable>
                    followed by a zero byte. This is the same format used by
                    <literal>env -0</literal> and
                    <filename>/proc/*/environ</filename>.
                </para></listitem>
            </varlistentry>