CVE-2023-28101: Improper Encoding or Escaping of Output (CWE-116)
value: Flatpak is a system for building, distributing, and running sandboxed desktop applications on Linux. In versions prior to 1.10.8, 1.12.8, 1.14.4, and 1.15.4, if an attacker publishes a Flatpak app with elevated permissions, they can hide those permissions from users of the `flatpak(1)` command-line interface by setting other permissions to crafted values that contain non-printable control characters such as `ESC`. A fix is available in versions 1.10.8, 1.12.8, 1.14.4, and 1.15.4. As a workaround, use a GUI like GNOME Software rather than the command-line interface, or only install apps whose maintainers you trust.
CWE description: The product prepares a structured message for communication with another component, but encoding or escaping of the data is either missing or done incorrectly. As a result, the intended structure of the message is not preserved.
CVSS v3 base score: 4.3
Published: 2023-03-16T16:15Z
Repository: https://github.com/flatpak/flatpak
Language: C
Files changed: common/flatpak-context.c, common/flatpak-utils-private.h, common/flatpak-utils.c, tests/test-context.c, tests/testcommon.c
Commit date: 2023-03-16 09:54:14 +0000
Commit message: Reject paths given to --filesystem/--persist with special characters

There isn't much in the way of legit reasons for this, but it's a
potential security footgun when displaying the text.

CVE-2023-28101, GHSA-h43h-fwqx-mpp8

Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
Co-authored-by: Simon McVittie <smcv@collabora.com>

Diff: diff --git a/common/flatpak-context.c b/common/flatpak-context.c
index ddc891dba6..666404e83a 100644
--- a/common/flatpak-context.c
+++ b/common/flatpak-context.c
@@ -486,15 +486,21 @@ flatpak_context_apply_generic_policy (FlatpakContext *context,
 
   g_hash_table_insert (context->generic_policy, g_strdup (key),
                        g_ptr_array_free (new, FALSE));
 }
 
-static void
+
+static gboolean
 flatpak_context_set_persistent (FlatpakContext *context,
-                                const char     *path)
+                                const char     *path,
+                                GError        **error)
 {
+  if (!flatpak_validate_path_characters (path, error))
+    return FALSE;
+
   g_hash_table_insert (context->persistent, g_strdup (path), GINT_TO_POINTER (1));
+  return TRUE;
 }
 
 static gboolean
 get_xdg_dir_from_prefix (const char  *prefix,
                          const char **where,
@@ -852,10 +858,13 @@ flatpak_context_parse_filesystem (const char             *filesystem_and_mode,
                                   GError                **error)
 {
   g_autofree char *filesystem = NULL;
   char *slash;
 
+  if (!flatpak_validate_path_characters (filesystem_and_mode, error))
+    return FALSE;
+
   filesystem = parse_filesystem_flags (filesystem_and_mode, negated, mode_out, error);
   if (filesystem == NULL)
     return FALSE;
 
   slash = strchr (filesystem, '/');
@@ -1508,12 +1517,11 @@ option_persist_cb (const gchar *option_name,
                    gpointer     data,
                    GError     **error)
 {
   FlatpakContext *context = data;
 
-  flatpak_context_set_persistent (context, value);
-  return TRUE;
+  return flatpak_context_set_persistent (context, value, error);
 }
 
 static gboolean option_no_desktop_deprecated;
 
 static GOptionEntry context_options[] = {
@@ -1701,15 +1709,28 @@ flatpak_context_load_metadata (FlatpakContext *context,
 
       for (i = 0; filesystems[i] != NULL; i++)
         {
           const char *fs = parse_negated (filesystems[i], &remove);
           g_autofree char *filesystem = NULL;
+          g_autoptr(GError) local_error = NULL;
           FlatpakFilesystemMode mode;
 
           if (!flatpak_context_parse_filesystem (fs, remove,
-                                                 &filesystem, &mode, NULL))
-            g_info ("Unknown filesystem type %s", filesystems[i]);
+                                                 &filesystem, &mode, &local_error))
+            {
+              if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA))
+                {
+                  /* Invalid characters, so just hard-fail. */
+                  g_propagate_error (error, g_steal_pointer (&local_error));
+                  return FALSE;
+                }
+              else
+                {
+                  g_info ("Unknown filesystem type %s", filesystems[i]);
+                  g_clear_error (&local_error);
+                }
+            }
           else
             {
               g_assert (mode == FLATPAK_FILESYSTEM_MODE_NONE || !remove);
               flatpak_context_take_filesystem (context, g_steal_pointer (&filesystem), mode);
             }
@@ -1722,11 +1743,12 @@ flatpak_context_load_metadata (FlatpakContext *context,
                                                              FLATPAK_METADATA_KEY_PERSISTENT, NULL, error);
       if (persistent == NULL)
         return FALSE;
 
       for (i = 0; persistent[i] != NULL; i++)
-        flatpak_context_set_persistent (context, persistent[i]);
+        if (!flatpak_context_set_persistent (context, persistent[i], error))
+          return FALSE;
     }
 
   if (g_key_file_has_group (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY))
     {
       g_auto(GStrv) keys = NULL;
diff --git a/common/flatpak-utils-private.h b/common/flatpak-utils-private.h
index 1ded8afce8..b37302f031 100644
--- a/common/flatpak-utils-private.h
+++ b/common/flatpak-utils-private.h
@@ -935,10 +935,13 @@ typedef enum {
 char * flatpak_escape_string (const char        *s,
                               FlatpakEscapeFlags flags);
 void   flatpak_print_escaped_string (const char        *s,
                                      FlatpakEscapeFlags flags);
 
+gboolean flatpak_validate_path_characters (const char *path,
+                                           GError    **error);
+
 gboolean running_under_sudo (void);
 
 #define FLATPAK_MESSAGE_ID "c7b39b1e006b464599465e105b361485"
 
 #endif /* __FLATPAK_UTILS_H__ */
diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c
index fa8c465a79..f28fdc071b 100644
--- a/common/flatpak-utils.c
+++ b/common/flatpak-utils.c
@@ -9248,10 +9248,18 @@ append_hex_escaped_character (GString *result,
     g_string_append_printf (result, "\\u%04X", c);
   else
     g_string_append_printf (result, "\\U%08X", c);
 }
 
+static char *
+escape_character (gunichar c)
+{
+  g_autoptr(GString) res = g_string_new ("");
+  append_hex_escaped_character (res, c);
+  return g_string_free (g_steal_pointer (&res), FALSE);
+}
+
 char *
 flatpak_escape_string (const char        *s,
                        FlatpakEscapeFlags flags)
 {
   g_autoptr(GString) res = g_string_new ("");
@@ -9299,10 +9307,41 @@ flatpak_print_escaped_string (const char        *s,
 {
   g_autofree char *escaped = flatpak_escape_string (s, flags);
   g_print ("%s", escaped);
 }
 
+gboolean
+flatpak_validate_path_characters (const char *path,
+                                  GError    **error)
+{
+  while (*path)
+    {
+      gunichar c = g_utf8_get_char_validated (path, -1);
+      if (c == (gunichar)-1 || c == (gunichar)-2)
+        {
+          /* Need to convert to unsigned first, to avoid negative chars becoming
+             huge gunichars. */
+          g_autofree char *escaped_char = escape_character ((unsigned char)*path);
+          g_autofree char *escaped = flatpak_escape_string (path, FLATPAK_ESCAPE_DEFAULT);
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+                       "Non-UTF8 byte %s in path %s", escaped_char, escaped);
+          return FALSE;
+        }
+      else if (!is_char_safe (c))
+        {
+          g_autofree char *escaped_char = escape_character (c);
+          g_autofree char *escaped = flatpak_escape_string (path, FLATPAK_ESCAPE_DEFAULT);
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+                       "Non-graphical character %s in path %s", escaped_char, escaped);
+          return FALSE;
+        }
+
+      path = g_utf8_find_next_char (path, NULL);
+    }
+
+  return TRUE;
+}
 
 gboolean
 running_under_sudo (void)
 {
   const char *sudo_command_env = g_getenv ("SUDO_COMMAND");
diff --git a/tests/test-context.c b/tests/test-context.c
index 785ca50927..d7afaa064a 100644
--- a/tests/test-context.c
+++ b/tests/test-context.c
@@ -127,27 +127,28 @@ test_context_env_fd (void)
   g_assert_false (ok);
   g_clear_error (&error);
 }
 
 static void context_parse_args (FlatpakContext *context,
+                                GError        **error,
                                 ...) G_GNUC_NULL_TERMINATED;
 
 static void
 context_parse_args (FlatpakContext *context,
+                    GError        **error,
                     ...)
 {
-  g_autoptr(GError) local_error = NULL;
   g_autoptr(GOptionContext) oc = NULL;
   g_autoptr(GOptionGroup) group = NULL;
   g_autoptr(GPtrArray) args = g_ptr_array_new_with_free_func (g_free);
   g_auto(GStrv) argv = NULL;
   const char *arg;
   va_list ap;
 
   g_ptr_array_add (args, g_strdup ("argv[0]"));
 
-  va_start (ap, context);
+  va_start (ap, error);
 
   while ((arg = va_arg (ap, const char *)) != NULL)
     g_ptr_array_add (args, g_strdup (arg));
 
   va_end (ap);
@@ -156,12 +157,11 @@ context_parse_args (FlatpakContext *context,
   argv = (GStrv) g_ptr_array_free (g_steal_pointer (&args), FALSE);
 
   oc = g_option_context_new ("");
   group = flatpak_context_get_options (context);
   g_option_context_add_group (oc, group);
-  g_option_context_parse_strv (oc, &argv, &local_error);
-  g_assert_no_error (local_error);
+  g_option_context_parse_strv (oc, &argv, error);
 }
 
 static void
 test_context_merge_fs (void)
 {
@@ -177,23 +177,30 @@ test_context_merge_fs (void)
   for (merge_order = LOWEST_FIRST; merge_order < INVALID; merge_order++)
     {
       g_autoptr(FlatpakContext) lowest = flatpak_context_new ();
       g_autoptr(FlatpakContext) middle = flatpak_context_new ();
       g_autoptr(FlatpakContext) highest = flatpak_context_new ();
+      g_autoptr(GError) local_error = NULL;
       gpointer value;
 
       context_parse_args (lowest,
+                          &local_error,
                           "--filesystem=/one",
                           NULL);
+      g_assert_no_error (local_error);
       context_parse_args (middle,
+                          &local_error,
                           "--nofilesystem=host:reset",
                           "--filesystem=/two",
                           NULL);
+      g_assert_no_error (local_error);
       context_parse_args (highest,
+                          &local_error,
                           "--nofilesystem=host",
                           "--filesystem=/three",
                           NULL);
+      g_assert_no_error (local_error);
 
       g_assert_false (g_hash_table_lookup_extended (lowest->filesystems, "host", NULL, NULL));
       g_assert_false (g_hash_table_lookup_extended (lowest->filesystems, "host-reset", NULL, NULL));
       g_assert_true (g_hash_table_lookup_extended (lowest->filesystems, "/one", NULL, &value));
       g_assert_cmpint (GPOINTER_TO_INT (value), ==, FLATPAK_FILESYSTEM_MODE_READ_WRITE);
@@ -271,24 +278,32 @@ test_context_merge_fs (void)
       g_autoptr(GPtrArray) args = g_ptr_array_new_with_free_func (g_free);
       g_autofree char *filesystems = NULL;
       gpointer value;
 
       context_parse_args (lowest,
+                          &local_error,
                           "--filesystem=/one",
                           NULL);
+      g_assert_no_error (local_error);
       context_parse_args (mid_low,
+                          &local_error,
                           "--nofilesystem=host:reset",
                           "--filesystem=/two",
                           NULL);
+      g_assert_no_error (local_error);
       context_parse_args (mid_high,
+                          &local_error,
                           "--filesystem=host",
                           "--filesystem=/three",
                           NULL);
+      g_assert_no_error (local_error);
       context_parse_args (highest,
+                          &local_error,
                           "--nofilesystem=host",
                           "--filesystem=/four",
                           NULL);
+      g_assert_no_error (local_error);
 
       g_assert_false (g_hash_table_lookup_extended (lowest->filesystems, "host", NULL, NULL));
       g_assert_false (g_hash_table_lookup_extended (lowest->filesystems, "host-reset", NULL, NULL));
       g_assert_true (g_hash_table_lookup_extended (lowest->filesystems, "/one", NULL, &value));
       g_assert_cmpint (GPOINTER_TO_INT (value), ==, FLATPAK_FILESYSTEM_MODE_READ_WRITE);
@@ -425,16 +440,77 @@ test_context_merge_fs (void)
       g_assert_true (g_ptr_array_find_with_equal_func (args, "--filesystem=/three", g_str_equal, NULL));
       g_assert_true (g_ptr_array_find_with_equal_func (args, "--filesystem=/four", g_str_equal, NULL));
     }
 }
 
+const char *invalid_path_args[] = {
+  "--filesystem=/\033[J:ro",
+  "--filesystem=/\033[J",
+  "--persist=\033[J",
+};
+
+/* CVE-2023-28101 */
+static void
+test_validate_path_args (void)
+{
+  gsize idx;
+
+  for (idx = 0; idx < G_N_ELEMENTS (invalid_path_args); idx++)
+    {
+      g_autoptr(FlatpakContext) context = flatpak_context_new ();
+      g_autoptr(GError) local_error = NULL;
+      const char *path = invalid_path_args[idx];
+
+      context_parse_args (context, &local_error, path, NULL);
+      g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA);
+      g_assert (strstr (local_error->message, "Non-graphical character"));
+    }
+}
+
+typedef struct {
+  const char *key;
+  const char *value;
+} PathValidityData;
+
+PathValidityData invalid_path_meta[] = {
+  {FLATPAK_METADATA_KEY_FILESYSTEMS, "\033[J"},
+  {FLATPAK_METADATA_KEY_PERSISTENT, "\033[J"},
+};
+
+/* CVE-2023-28101 */
+static void
+test_validate_path_meta (void)
+{
+  gsize idx;
+
+  for (idx = 0; idx < G_N_ELEMENTS (invalid_path_meta); idx++)
+    {
+      g_autoptr(FlatpakContext) context = flatpak_context_new ();
+      g_autoptr(GKeyFile) metakey = g_key_file_new ();
+      g_autoptr(GError) local_error = NULL;
+      PathValidityData *data = &invalid_path_meta[idx];
+      gboolean ret = FALSE;
+
+      g_key_file_set_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT,
+                                  data->key, &data->value, 1);
+
+      ret = flatpak_context_load_metadata (context, metakey, &local_error);
+      g_assert_false (ret);
+      g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA);
+      g_assert (strstr (local_error->message, "Non-graphical character"));
+    }
+
+}
+
 int
 main (int argc, char *argv[])
 {
   g_test_init (&argc, &argv, NULL);
 
   g_test_add_func ("/context/env", test_context_env);
   g_test_add_func ("/context/env-fd", test_context_env_fd);
   g_test_add_func ("/context/merge-fs", test_context_merge_fs);
+  g_test_add_func ("/context/validate-path-args", test_validate_path_args);
+  g_test_add_func ("/context/validate-path-meta", test_validate_path_meta);
 
   return g_test_run ();
 }
diff --git a/tests/testcommon.c b/tests/testcommon.c
index d8ad09c745..8ba3770d6b 100644
--- a/tests/testcommon.c
+++ b/tests/testcommon.c
@@ -1845,15 +1845,16 @@ typedef struct {
 
 static EscapeData escapes[] = {
   {"abc def", FLATPAK_ESCAPE_DEFAULT, "abc def"},
   {"やあ", FLATPAK_ESCAPE_DEFAULT, "やあ"},
   {"\033[;1m", FLATPAK_ESCAPE_DEFAULT, "'\\x1B[;1m'"},
-  // non-printable U+061C
+  /* U+061C ARABIC LETTER MARK, non-printable */
   {"\u061C", FLATPAK_ESCAPE_DEFAULT, "'\\u061C'"},
-  // non-printable U+1343F
+  /* U+1343F EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE, non-printable and
+   * outside BMP */
   {"\xF0\x93\x90\xBF", FLATPAK_ESCAPE_DEFAULT, "'\\U0001343F'"},
-  // invalid utf-8
+  /* invalid utf-8 */
   {"\xD8\1", FLATPAK_ESCAPE_DEFAULT, "'\\xD8\\x01'"},
   {"\b \n abc ' \\", FLATPAK_ESCAPE_DEFAULT, "'\\x08 \\x0A abc \\' \\\\'"},
   {"\b \n abc ' \\", FLATPAK_ESCAPE_DO_NOT_QUOTE, "\\x08 \\x0A abc ' \\\\"},
   {"abc\tdef\n\033[;1m ghi\b", FLATPAK_ESCAPE_ALLOW_NEWLINES | FLATPAK_ESCAPE_DO_NOT_QUOTE,
    "abc\\x09def\n\\x1B[;1m ghi\\x08"},
@@ -1873,10 +1874,43 @@ test_string_escape (void)
       ret = flatpak_escape_string (data->in, data->flags);
       g_assert_cmpstr (ret, ==, data->out);
     }
 }
 
+typedef struct {
+  const char *path;
+  gboolean ret;
+} PathValidityData;
+
+static PathValidityData paths[] = {
+  {"/a/b/../c.def", TRUE},
+  {"やあ", TRUE},
+  /* U+061C ARABIC LETTER MARK, non-printable */
+  {"\u061C", FALSE},
+  /* U+1343F EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE, non-printable and
+   * outside BMP */
+  {"\xF0\x93\x90\xBF", FALSE},
+  /* invalid utf-8 */
+  {"\xD8\1", FALSE},
+};
+
+/* CVE-2023-28101 */
+static void
+test_validate_path_characters (void)
+{
+  gsize idx;
+
+  for (idx = 0; idx < G_N_ELEMENTS (paths); idx++)
+    {
+      PathValidityData *data = &paths[idx];
+      gboolean ret = FALSE;
+
+      ret = flatpak_validate_path_characters (data->path, NULL);
+      g_assert_cmpint (ret, ==, data->ret);
+    }
+}
+
 int
 main (int argc, char *argv[])
 {
   int res;
 
@@ -1907,10 +1941,11 @@ main (int argc, char *argv[])
   g_test_add_func ("/common/needs-quoting", test_needs_quoting);
   g_test_add_func ("/common/quote-argv", test_quote_argv);
   g_test_add_func ("/common/str-is-integer", test_str_is_integer);
   g_test_add_func ("/common/parse-x11-display", test_parse_x11_display);
   g_test_add_func ("/common/string-escape", test_string_escape);
+  g_test_add_func ("/common/validate-path-characters", test_validate_path_characters);
 
   g_test_add_func ("/app/looks-like-branch", test_looks_like_branch);
   g_test_add_func ("/app/columns", test_columns);
   g_test_add_func ("/app/string-ellipsize", test_string_ellipsize);
   g_test_add_func ("/app/table", test_table);

Vulnerable code: static void
                                const char     *path)
  flatpak_context_set_persistent (context, value);
  return TRUE;
                                                 &filesystem, &mode, NULL))
            g_info ("Unknown filesystem type %s", filesystems[i]);
        flatpak_context_set_persistent (context, persistent[i]);
  g_autoptr(GError) local_error = NULL;
  va_start (ap, context);
  g_option_context_parse_strv (oc, &argv, &local_error);
  g_assert_no_error (local_error);
  // non-printable U+061C
  // non-printable U+1343F
  // invalid utf-8
Fixed code: 
static gboolean
                                const char     *path,
                                GError        **error)
  if (!flatpak_validate_path_characters (path, error))
    return FALSE;

  return TRUE;
  if (!flatpak_validate_path_characters (filesystem_and_mode, error))
    return FALSE;

  return flatpak_context_set_persistent (context, value, error);
          g_autoptr(GError) local_error = NULL;
                                                 &filesystem, &mode, &local_error))
            {
              if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA))
                {
                  /* Invalid characters, so just hard-fail. */
                  g_propagate_error (error, g_steal_pointer (&local_error));
                  return FALSE;
                }
              else
                {
                  g_info ("Unknown filesystem type %s", filesystems[i]);
                  g_clear_error (&local_error);
                }
            }
        if (!flatpak_context_set_persistent (context, persistent[i], error))
          return FALSE;
gboolean flatpak_validate_path_characters (const char *path,
                                           GError    **error);

static char *
escape_character (gunichar c)
{
  g_autoptr(GString) res = g_string_new ("");
  append_hex_escaped_character (res, c);
  return g_string_free (g_steal_pointer (&res), FALSE);
}

gboolean
flatpak_validate_path_characters (const char *path,
                                  GError    **error)
{
  while (*path)
    {
      gunichar c = g_utf8_get_char_validated (path, -1);
      if (c == (gunichar)-1 || c == (gunichar)-2)
        {
          /* Need to convert to unsigned first, to avoid negative chars becoming
             huge gunichars. */
          g_autofree char *escaped_char = escape_character ((unsigned char)*path);
          g_autofree char *escaped = flatpak_escape_string (path, FLATPAK_ESCAPE_DEFAULT);
          g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
                       "Non-UTF8 byte %s in path %s", escaped_char, escaped);
          return FALSE;
        }
      else if (!is_char_safe (c))
        {
          g_autofree char *escaped_char = escape_character (c);
          g_autofree char *escaped = flatpak_escape_string (path, FLATPAK_ESCAPE_DEFAULT);
          g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
                       "Non-graphical character %s in path %s", escaped_char, escaped);
          return FALSE;
        }

      path = g_utf8_find_next_char (path, NULL);
    }

  return TRUE;
}
                                GError        **error,
                    GError        **error,
  va_start (ap, error);
  g_option_context_parse_strv (oc, &argv, error);
      g_autoptr(GError) local_error = NULL;
                          &local_error,
      g_assert_no_error (local_error);
                          &local_error,
      g_assert_no_error (local_error);
                          &local_error,
      g_assert_no_error (local_error);
                          &local_error,
      g_assert_no_error (local_error);
                          &local_error,
      g_assert_no_error (local_error);
                          &local_error,
      g_assert_no_error (local_error);
                          &local_error,
      g_assert_no_error (local_error);
const char *invalid_path_args[] = {
  "--filesystem=/\033[J:ro",
  "--filesystem=/\033[J",
  "--persist=\033[J",
};

/* CVE-2023-28101 */
static void
test_validate_path_args (void)
{
  gsize idx;

  for (idx = 0; idx < G_N_ELEMENTS (invalid_path_args); idx++)
    {
      g_autoptr(FlatpakContext) context = flatpak_context_new ();
      g_autoptr(GError) local_error = NULL;
      const char *path = invalid_path_args[idx];

      context_parse_args (context, &local_error, path, NULL);
      g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA);
      g_assert (strstr (local_error->message, "Non-graphical character"));
    }
}

typedef struct {
  const char *key;
  const char *value;
} PathValidityData;

PathValidityData invalid_path_meta[] = {
  {FLATPAK_METADATA_KEY_FILESYSTEMS, "\033[J"},
  {FLATPAK_METADATA_KEY_PERSISTENT, "\033[J"},
};

/* CVE-2023-28101 */
static void
test_validate_path_meta (void)
{
  gsize idx;

  for (idx = 0; idx < G_N_ELEMENTS (invalid_path_meta); idx++)
    {
      g_autoptr(FlatpakContext) context = flatpak_context_new ();
      g_autoptr(GKeyFile) metakey = g_key_file_new ();
      g_autoptr(GError) local_error = NULL;
      PathValidityData *data = &invalid_path_meta[idx];
      gboolean ret = FALSE;

      g_key_file_set_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT,
                                  data->key, &data->value, 1);

      ret = flatpak_context_load_metadata (context, metakey, &local_error);
      g_assert_false (ret);
      g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA);
      g_assert (strstr (local_error->message, "Non-graphical character"));
    }

}

  g_test_add_func ("/context/validate-path-args", test_validate_path_args);
  g_test_add_func ("/context/validate-path-meta", test_validate_path_meta);
  /* U+061C ARABIC LETTER MARK, non-printable */
  /* U+1343F EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE, non-printable and
   * outside BMP */
  /* invalid utf-8 */
typedef struct {
  const char *path;
  gboolean ret;
} PathValidityData;

static PathValidityData paths[] = {
  {"/a/b/../c.def", TRUE},
  {"やあ", TRUE},
  /* U+061C ARABIC LETTER MARK, non-printable */
  {"\u061C", FALSE},
  /* U+1343F EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE, non-printable and
   * outside BMP */
  {"\xF0\x93\x90\xBF", FALSE},
  /* invalid utf-8 */
  {"\xD8\1", FALSE},
};

/* CVE-2023-28101 */
static void
test_validate_path_characters (void)
{
  gsize idx;

  for (idx = 0; idx < G_N_ELEMENTS (paths); idx++)
    {
      PathValidityData *data = &paths[idx];
      gboolean ret = FALSE;

      ret = flatpak_validate_path_characters (data->path, NULL);
      g_assert_cmpint (ret, ==, data->ret);
    }
}

  g_test_add_func ("/common/validate-path-characters", test_validate_path_characters);