From 5709f1aaed6579f0136976e14e7f3cae399134ca Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Sun, 16 Jan 2022 12:42:30 +0000
Subject: [PATCH] context: Introduce new --nofilesystem=host:reset

This reintroduces the special case that existed in Flatpak 1.12.3, but
under a different name, so that it will be backwards-compatible. With
this change, flatpak-builder will be able to resolve CVE-2022-21682 by
using --filesystem=host:reset.

We want to implement this as a suffix rather than as a new keyword,
because unknown suffixes are ignored with a warning, rather than causing
a fatal error. This means that the new version of flatpak-builder will
be able to run against older versions of flatpak: it will still be
vulnerable to CVE-2022-21682 in that situation, but at least it will run.

Co-authored-by: Alexander Larsson <alexl@redhat.com>

Conflict:NA
Reference:https://github.com/flatpak/flatpak/commit/5709f1aaed6579f0136976e14e7f3cae399134ca
---
 common/flatpak-context-private.h |   1 +
 common/flatpak-context.c         | 166 ++++++++++++++++++++++++++++---
 2 files changed, 153 insertions(+), 14 deletions(-)

diff --git a/common/flatpak-context-private.h b/common/flatpak-context-private.h
index 45879ac..b6b9e56 100644
--- a/common/flatpak-context-private.h
+++ b/common/flatpak-context-private.h
@@ -83,6 +83,7 @@ extern const char *flatpak_context_features[];
 extern const char *flatpak_context_shares[];
 
 gboolean       flatpak_context_parse_filesystem (const char             *filesystem_and_mode,
+                                                 gboolean                negated,
                                                  char                  **filesystem_out,
                                                  FlatpakFilesystemMode  *mode_out,
                                                  GError                **error);
diff --git a/common/flatpak-context.c b/common/flatpak-context.c
index abeda35..a7cd891 100644
--- a/common/flatpak-context.c
+++ b/common/flatpak-context.c
@@ -86,6 +86,7 @@ const char *flatpak_context_special_filesystems[] = {
   "host",
   "host-etc",
   "host-os",
+  "host-reset",
   NULL
 };
 
@@ -703,6 +704,12 @@ unparse_filesystem_flags (const char           *path,
 
     case FLATPAK_FILESYSTEM_MODE_NONE:
       g_string_insert_c (s, 0, '!');
+      
+      if (g_str_has_suffix (s->str, "-reset"))
+        {
+          g_string_truncate (s, s->len - 6);
+          g_string_append (s, ":reset");
+        }
       break;
 
     default:
@@ -715,11 +722,14 @@ unparse_filesystem_flags (const char           *path,
 
 static char *
 parse_filesystem_flags (const char            *filesystem,
-                        FlatpakFilesystemMode *mode_out)
+                        gboolean               negated,
+                        FlatpakFilesystemMode *mode_out,
+                        GError               **error)
 {
   g_autoptr(GString) s = g_string_new ("");
   const char *p, *suffix;
   FlatpakFilesystemMode mode;
+  gboolean reset = FALSE;
 
   p = filesystem;
   while (*p != 0 && *p != ':')
@@ -734,7 +744,31 @@ parse_filesystem_flags (const char            *filesystem,
         g_string_append_c (s, *p++);
     }
 
-  mode = FLATPAK_FILESYSTEM_MODE_READ_WRITE;
+  if (negated)
+    mode = FLATPAK_FILESYSTEM_MODE_NONE;
+  else
+    mode = FLATPAK_FILESYSTEM_MODE_READ_WRITE;
+
+  if (g_str_equal (s->str, "host-reset"))
+    {
+      reset = TRUE;
+
+      if (!negated)
+        {
+          g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+                       "Filesystem token \"%s\" is only applicable for --nofilesystem",
+                       s->str);
+          return NULL;
+        }
+
+      if (*p != '\0')
+        {
+          g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+                       "Filesystem token \"%s\" cannot be used with a suffix",
+                       s->str);
+          return NULL;
+        }
+    }
 
   if (*p == ':')
     {
@@ -746,10 +780,63 @@ parse_filesystem_flags (const char            *filesystem,
         mode = FLATPAK_FILESYSTEM_MODE_READ_WRITE;
       else if (strcmp (suffix, "create") == 0)
         mode = FLATPAK_FILESYSTEM_MODE_CREATE;
+      else if (strcmp (suffix, "reset") == 0)
+        reset = TRUE;
       else if (*suffix != 0)
         g_warning ("Unexpected filesystem suffix %s, ignoring", suffix);
+
+      if (negated && mode != FLATPAK_FILESYSTEM_MODE_NONE)
+        {
+          g_warning ("Filesystem suffix \"%s\" is not applicable for --nofilesystem",
+                     suffix);
+          mode = FLATPAK_FILESYSTEM_MODE_NONE;
+        }
+
+      if (reset)
+        {
+          if (!negated)
+            {
+              g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+                           "Filesystem suffix \"%s\" only applies to --nofilesystem",
+                           suffix);
+              return NULL;
+            }
+
+          if (!g_str_equal (s->str, "host"))
+            {
+              g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
+                           "Filesystem suffix \"%s\" can only be applied to "
+                           "--nofilesystem=host",
+                           suffix);
+              return NULL;
+            }
+
+          /* We internally handle host:reset (etc) as host-reset, only exposing it as a flag in the public
+             part to allow it to be ignored (with a warning) for old flatpak versions */
+          g_string_append (s, "-reset");
+        }
+    }
+
+  /* Postcondition check: the code above should make some results
+   * impossible */
+  if (negated)
+    {
+      g_assert (mode == FLATPAK_FILESYSTEM_MODE_NONE);
+    }
+  else
+    {
+      g_assert (mode > FLATPAK_FILESYSTEM_MODE_NONE);
+      /* This flag is only applicable to --nofilesystem */
+      g_assert (!reset);
     }
 
+  /* Postcondition check: filesystem token is host-reset iff reset flag
+   * was found */
+  if (reset)
+    g_assert (g_str_equal (s->str, "host-reset"));
+  else
+    g_assert (!g_str_equal (s->str, "host-reset"));
+
   if (mode_out)
     *mode_out = mode;
 
@@ -758,13 +845,18 @@ parse_filesystem_flags (const char            *filesystem,
 
 gboolean
 flatpak_context_parse_filesystem (const char             *filesystem_and_mode,
+                                  gboolean                negated,
                                   char                  **filesystem_out,
                                   FlatpakFilesystemMode  *mode_out,
                                   GError                **error)
 {
-  g_autofree char *filesystem = parse_filesystem_flags (filesystem_and_mode, mode_out);
+  g_autofree char *filesystem = NULL;
   char *slash;
 
+  filesystem = parse_filesystem_flags (filesystem_and_mode, negated, mode_out, error);
+  if (filesystem == NULL)
+    return FALSE;
+
   slash = strchr (filesystem, '/');
 
   /* Forbid /../ in paths */
@@ -856,6 +948,14 @@ flatpak_context_take_filesystem (FlatpakContext        *context,
                                  char                  *fs,
                                  FlatpakFilesystemMode  mode)
 {
+  /* Special case: --nofilesystem=host-reset implies --nofilesystem=host.
+   * --filesystem=host-reset (or host:reset) is not allowed. */
+  if (g_str_equal (fs, "host-reset"))
+    {
+      g_return_if_fail (mode == FLATPAK_FILESYSTEM_MODE_NONE);
+      g_hash_table_insert (context->filesystems, g_strdup ("host"), GINT_TO_POINTER (mode));
+    }
+
   g_hash_table_insert (context->filesystems, fs, GINT_TO_POINTER (mode));
 }
 
@@ -887,6 +987,14 @@ flatpak_context_merge (FlatpakContext *context,
   while (g_hash_table_iter_next (&iter, &key, &value))
     g_hash_table_insert (context->persistent, g_strdup (key), value);
 
+  /* We first handle host:reset, as it overrides all other keys from the parent */
+  if (g_hash_table_lookup_extended (other->filesystems, "host-reset", NULL, &value))
+    {
+      g_warn_if_fail (GPOINTER_TO_INT (value) == FLATPAK_FILESYSTEM_MODE_NONE);
+      g_hash_table_remove_all (context->filesystems);
+    }
+
+  /* Then set the new ones, which includes propagating host:reset. */
   g_hash_table_iter_init (&iter, other->filesystems);
   while (g_hash_table_iter_next (&iter, &key, &value))
     g_hash_table_insert (context->filesystems, g_strdup (key), value);
@@ -1074,7 +1182,7 @@ option_filesystem_cb (const gchar *option_name,
   g_autofree char *fs = NULL;
   FlatpakFilesystemMode mode;
 
-  if (!flatpak_context_parse_filesystem (value, &fs, &mode, error))
+  if (!flatpak_context_parse_filesystem (value, FALSE, &fs, &mode, error))
     return FALSE;
 
   flatpak_context_take_filesystem (context, g_steal_pointer (&fs), mode);
@@ -1091,7 +1199,7 @@ option_nofilesystem_cb (const gchar *option_name,
   g_autofree char *fs = NULL;
   FlatpakFilesystemMode mode;
 
-  if (!flatpak_context_parse_filesystem (value, &fs, &mode, error))
+  if (!flatpak_context_parse_filesystem (value, TRUE, &fs, &mode, error))
     return FALSE;
 
   flatpak_context_take_filesystem (context, g_steal_pointer (&fs),
@@ -1571,15 +1679,13 @@ flatpak_context_load_metadata (FlatpakContext *context,
           g_autofree char *filesystem = NULL;
           FlatpakFilesystemMode mode;
 
-          if (!flatpak_context_parse_filesystem (fs, &filesystem, &mode, NULL))
+          if (!flatpak_context_parse_filesystem (fs, remove,
+                                                 &filesystem, &mode, NULL))
             g_debug ("Unknown filesystem type %s", filesystems[i]);
           else
             {
-              if (remove)
-                flatpak_context_take_filesystem (context, g_steal_pointer (&filesystem),
-                                                 FLATPAK_FILESYSTEM_MODE_NONE);
-              else
-                flatpak_context_take_filesystem (context, g_steal_pointer (&filesystem), mode);
+              g_assert (mode == FLATPAK_FILESYSTEM_MODE_NONE || !remove);
+              flatpak_context_take_filesystem (context, g_steal_pointer (&filesystem), mode);
             }
         }
     }
@@ -1825,11 +1931,24 @@ flatpak_context_save_metadata (FlatpakContext *context,
     {
       g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func (g_free);
 
+      /* Serialize host-reset first, because order can matter in
+       * corner cases. */
+      if (g_hash_table_lookup_extended (context->filesystems, "host-reset",
+                                        NULL, &value))
+        {
+          g_warn_if_fail (GPOINTER_TO_INT (value) == FLATPAK_FILESYSTEM_MODE_NONE);
+          g_ptr_array_add (array, g_strdup ("!host:reset"));
+        }
+
       g_hash_table_iter_init (&iter, context->filesystems);
       while (g_hash_table_iter_next (&iter, &key, &value))
         {
           FlatpakFilesystemMode mode = GPOINTER_TO_INT (value);
 
+          /* We already did this */
+          if (g_str_equal (key, "host-reset"))
+            continue;
+
           g_ptr_array_add (array, unparse_filesystem_flags (key, mode));
         }
 
@@ -1968,7 +2087,8 @@ flatpak_context_save_metadata (FlatpakContext *context,
 void
 flatpak_context_allow_host_fs (FlatpakContext *context)
 {
-  flatpak_context_take_filesystem (context, g_strdup ("host"), FLATPAK_FILESYSTEM_MODE_READ_WRITE);
+  flatpak_context_take_filesystem (context, g_strdup ("host"),
+                                   FLATPAK_FILESYSTEM_MODE_READ_WRITE);
 }
 
 gboolean
@@ -2155,18 +2275,36 @@ flatpak_context_to_args (FlatpakContext *context,
       g_ptr_array_add (args, g_strdup_printf ("--system-%s-name=%s", flatpak_policy_to_string (policy), name));
     }
 
+  /* Serialize host-reset first, because order can matter in
+   * corner cases. */
+  if (g_hash_table_lookup_extended (context->filesystems, "host-reset",
+                                    NULL, &value))
+    {
+      g_warn_if_fail (GPOINTER_TO_INT (value) == FLATPAK_FILESYSTEM_MODE_NONE);
+      g_ptr_array_add (args, g_strdup ("--nofilesystem=host:reset"));
+    }
+
   g_hash_table_iter_init (&iter, context->filesystems);
   while (g_hash_table_iter_next (&iter, &key, &value))
     {
+      g_autofree char *fs = NULL;
       FlatpakFilesystemMode mode = GPOINTER_TO_INT (value);
 
+      /* We already did this */
+      if (g_str_equal (key, "host-reset"))
+        continue;
+
+      fs = unparse_filesystem_flags (key, mode);
+
       if (mode != FLATPAK_FILESYSTEM_MODE_NONE)
         {
-          g_autofree char *fs = unparse_filesystem_flags (key, mode);
           g_ptr_array_add (args, g_strdup_printf ("--filesystem=%s", fs));
         }
       else
-        g_ptr_array_add (args, g_strdup_printf ("--nofilesystem=%s", (char *) key));
+        {
+          g_assert (fs[0] == '!');
+          g_ptr_array_add (args, g_strdup_printf ("--nofilesystem=%s", &fs[1]));
+        }
     }
 }
 
-- 
2.27.0

