Skip to content

Commit 7fe63f2

Browse files
refi64smcv
andcommitted
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>
1 parent 6cac99d commit 7fe63f2

File tree

5 files changed

+189
-14
lines changed

5 files changed

+189
-14
lines changed

Diff for: common/flatpak-context.c

+29-7
Original file line numberDiff line numberDiff line change
@@ -488,11 +488,17 @@ flatpak_context_apply_generic_policy (FlatpakContext *context,
488488
g_ptr_array_free (new, FALSE));
489489
}
490490

491-
static void
491+
492+
static gboolean
492493
flatpak_context_set_persistent (FlatpakContext *context,
493-
const char *path)
494+
const char *path,
495+
GError **error)
494496
{
497+
if (!flatpak_validate_path_characters (path, error))
498+
return FALSE;
499+
495500
g_hash_table_insert (context->persistent, g_strdup (path), GINT_TO_POINTER (1));
501+
return TRUE;
496502
}
497503

498504
static gboolean
@@ -854,6 +860,9 @@ flatpak_context_parse_filesystem (const char *filesystem_and_mode,
854860
g_autofree char *filesystem = NULL;
855861
char *slash;
856862

863+
if (!flatpak_validate_path_characters (filesystem_and_mode, error))
864+
return FALSE;
865+
857866
filesystem = parse_filesystem_flags (filesystem_and_mode, negated, mode_out, error);
858867
if (filesystem == NULL)
859868
return FALSE;
@@ -1510,8 +1519,7 @@ option_persist_cb (const gchar *option_name,
15101519
{
15111520
FlatpakContext *context = data;
15121521

1513-
flatpak_context_set_persistent (context, value);
1514-
return TRUE;
1522+
return flatpak_context_set_persistent (context, value, error);
15151523
}
15161524

15171525
static gboolean option_no_desktop_deprecated;
@@ -1703,11 +1711,24 @@ flatpak_context_load_metadata (FlatpakContext *context,
17031711
{
17041712
const char *fs = parse_negated (filesystems[i], &remove);
17051713
g_autofree char *filesystem = NULL;
1714+
g_autoptr(GError) local_error = NULL;
17061715
FlatpakFilesystemMode mode;
17071716

17081717
if (!flatpak_context_parse_filesystem (fs, remove,
1709-
&filesystem, &mode, NULL))
1710-
g_info ("Unknown filesystem type %s", filesystems[i]);
1718+
&filesystem, &mode, &local_error))
1719+
{
1720+
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA))
1721+
{
1722+
/* Invalid characters, so just hard-fail. */
1723+
g_propagate_error (error, g_steal_pointer (&local_error));
1724+
return FALSE;
1725+
}
1726+
else
1727+
{
1728+
g_info ("Unknown filesystem type %s", filesystems[i]);
1729+
g_clear_error (&local_error);
1730+
}
1731+
}
17111732
else
17121733
{
17131734
g_assert (mode == FLATPAK_FILESYSTEM_MODE_NONE || !remove);
@@ -1724,7 +1745,8 @@ flatpak_context_load_metadata (FlatpakContext *context,
17241745
return FALSE;
17251746

17261747
for (i = 0; persistent[i] != NULL; i++)
1727-
flatpak_context_set_persistent (context, persistent[i]);
1748+
if (!flatpak_context_set_persistent (context, persistent[i], error))
1749+
return FALSE;
17281750
}
17291751

17301752
if (g_key_file_has_group (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY))

Diff for: common/flatpak-utils-private.h

+3
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,9 @@ char * flatpak_escape_string (const char *s,
937937
void flatpak_print_escaped_string (const char *s,
938938
FlatpakEscapeFlags flags);
939939

940+
gboolean flatpak_validate_path_characters (const char *path,
941+
GError **error);
942+
940943
gboolean running_under_sudo (void);
941944

942945
#define FLATPAK_MESSAGE_ID "c7b39b1e006b464599465e105b361485"

Diff for: common/flatpak-utils.c

+39
Original file line numberDiff line numberDiff line change
@@ -9250,6 +9250,14 @@ append_hex_escaped_character (GString *result,
92509250
g_string_append_printf (result, "\\U%08X", c);
92519251
}
92529252

9253+
static char *
9254+
escape_character (gunichar c)
9255+
{
9256+
g_autoptr(GString) res = g_string_new ("");
9257+
append_hex_escaped_character (res, c);
9258+
return g_string_free (g_steal_pointer (&res), FALSE);
9259+
}
9260+
92539261
char *
92549262
flatpak_escape_string (const char *s,
92559263
FlatpakEscapeFlags flags)
@@ -9301,6 +9309,37 @@ flatpak_print_escaped_string (const char *s,
93019309
g_print ("%s", escaped);
93029310
}
93039311

9312+
gboolean
9313+
flatpak_validate_path_characters (const char *path,
9314+
GError **error)
9315+
{
9316+
while (*path)
9317+
{
9318+
gunichar c = g_utf8_get_char_validated (path, -1);
9319+
if (c == (gunichar)-1 || c == (gunichar)-2)
9320+
{
9321+
/* Need to convert to unsigned first, to avoid negative chars becoming
9322+
huge gunichars. */
9323+
g_autofree char *escaped_char = escape_character ((unsigned char)*path);
9324+
g_autofree char *escaped = flatpak_escape_string (path, FLATPAK_ESCAPE_DEFAULT);
9325+
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
9326+
"Non-UTF8 byte %s in path %s", escaped_char, escaped);
9327+
return FALSE;
9328+
}
9329+
else if (!is_char_safe (c))
9330+
{
9331+
g_autofree char *escaped_char = escape_character (c);
9332+
g_autofree char *escaped = flatpak_escape_string (path, FLATPAK_ESCAPE_DEFAULT);
9333+
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
9334+
"Non-graphical character %s in path %s", escaped_char, escaped);
9335+
return FALSE;
9336+
}
9337+
9338+
path = g_utf8_find_next_char (path, NULL);
9339+
}
9340+
9341+
return TRUE;
9342+
}
93049343

93059344
gboolean
93069345
running_under_sudo (void)

Diff for: tests/test-context.c

+80-4
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,14 @@ test_context_env_fd (void)
129129
}
130130

131131
static void context_parse_args (FlatpakContext *context,
132+
GError **error,
132133
...) G_GNUC_NULL_TERMINATED;
133134

134135
static void
135136
context_parse_args (FlatpakContext *context,
137+
GError **error,
136138
...)
137139
{
138-
g_autoptr(GError) local_error = NULL;
139140
g_autoptr(GOptionContext) oc = NULL;
140141
g_autoptr(GOptionGroup) group = NULL;
141142
g_autoptr(GPtrArray) args = g_ptr_array_new_with_free_func (g_free);
@@ -145,7 +146,7 @@ context_parse_args (FlatpakContext *context,
145146

146147
g_ptr_array_add (args, g_strdup ("argv[0]"));
147148

148-
va_start (ap, context);
149+
va_start (ap, error);
149150

150151
while ((arg = va_arg (ap, const char *)) != NULL)
151152
g_ptr_array_add (args, g_strdup (arg));
@@ -158,8 +159,7 @@ context_parse_args (FlatpakContext *context,
158159
oc = g_option_context_new ("");
159160
group = flatpak_context_get_options (context);
160161
g_option_context_add_group (oc, group);
161-
g_option_context_parse_strv (oc, &argv, &local_error);
162-
g_assert_no_error (local_error);
162+
g_option_context_parse_strv (oc, &argv, error);
163163
}
164164

165165
static void
@@ -179,19 +179,26 @@ test_context_merge_fs (void)
179179
g_autoptr(FlatpakContext) lowest = flatpak_context_new ();
180180
g_autoptr(FlatpakContext) middle = flatpak_context_new ();
181181
g_autoptr(FlatpakContext) highest = flatpak_context_new ();
182+
g_autoptr(GError) local_error = NULL;
182183
gpointer value;
183184

184185
context_parse_args (lowest,
186+
&local_error,
185187
"--filesystem=/one",
186188
NULL);
189+
g_assert_no_error (local_error);
187190
context_parse_args (middle,
191+
&local_error,
188192
"--nofilesystem=host:reset",
189193
"--filesystem=/two",
190194
NULL);
195+
g_assert_no_error (local_error);
191196
context_parse_args (highest,
197+
&local_error,
192198
"--nofilesystem=host",
193199
"--filesystem=/three",
194200
NULL);
201+
g_assert_no_error (local_error);
195202

196203
g_assert_false (g_hash_table_lookup_extended (lowest->filesystems, "host", NULL, NULL));
197204
g_assert_false (g_hash_table_lookup_extended (lowest->filesystems, "host-reset", NULL, NULL));
@@ -273,20 +280,28 @@ test_context_merge_fs (void)
273280
gpointer value;
274281

275282
context_parse_args (lowest,
283+
&local_error,
276284
"--filesystem=/one",
277285
NULL);
286+
g_assert_no_error (local_error);
278287
context_parse_args (mid_low,
288+
&local_error,
279289
"--nofilesystem=host:reset",
280290
"--filesystem=/two",
281291
NULL);
292+
g_assert_no_error (local_error);
282293
context_parse_args (mid_high,
294+
&local_error,
283295
"--filesystem=host",
284296
"--filesystem=/three",
285297
NULL);
298+
g_assert_no_error (local_error);
286299
context_parse_args (highest,
300+
&local_error,
287301
"--nofilesystem=host",
288302
"--filesystem=/four",
289303
NULL);
304+
g_assert_no_error (local_error);
290305

291306
g_assert_false (g_hash_table_lookup_extended (lowest->filesystems, "host", NULL, NULL));
292307
g_assert_false (g_hash_table_lookup_extended (lowest->filesystems, "host-reset", NULL, NULL));
@@ -427,6 +442,65 @@ test_context_merge_fs (void)
427442
}
428443
}
429444

445+
const char *invalid_path_args[] = {
446+
"--filesystem=/\033[J:ro",
447+
"--filesystem=/\033[J",
448+
"--persist=\033[J",
449+
};
450+
451+
/* CVE-2023-28101 */
452+
static void
453+
test_validate_path_args (void)
454+
{
455+
gsize idx;
456+
457+
for (idx = 0; idx < G_N_ELEMENTS (invalid_path_args); idx++)
458+
{
459+
g_autoptr(FlatpakContext) context = flatpak_context_new ();
460+
g_autoptr(GError) local_error = NULL;
461+
const char *path = invalid_path_args[idx];
462+
463+
context_parse_args (context, &local_error, path, NULL);
464+
g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA);
465+
g_assert (strstr (local_error->message, "Non-graphical character"));
466+
}
467+
}
468+
469+
typedef struct {
470+
const char *key;
471+
const char *value;
472+
} PathValidityData;
473+
474+
PathValidityData invalid_path_meta[] = {
475+
{FLATPAK_METADATA_KEY_FILESYSTEMS, "\033[J"},
476+
{FLATPAK_METADATA_KEY_PERSISTENT, "\033[J"},
477+
};
478+
479+
/* CVE-2023-28101 */
480+
static void
481+
test_validate_path_meta (void)
482+
{
483+
gsize idx;
484+
485+
for (idx = 0; idx < G_N_ELEMENTS (invalid_path_meta); idx++)
486+
{
487+
g_autoptr(FlatpakContext) context = flatpak_context_new ();
488+
g_autoptr(GKeyFile) metakey = g_key_file_new ();
489+
g_autoptr(GError) local_error = NULL;
490+
PathValidityData *data = &invalid_path_meta[idx];
491+
gboolean ret = FALSE;
492+
493+
g_key_file_set_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT,
494+
data->key, &data->value, 1);
495+
496+
ret = flatpak_context_load_metadata (context, metakey, &local_error);
497+
g_assert_false (ret);
498+
g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA);
499+
g_assert (strstr (local_error->message, "Non-graphical character"));
500+
}
501+
502+
}
503+
430504
int
431505
main (int argc, char *argv[])
432506
{
@@ -435,6 +509,8 @@ main (int argc, char *argv[])
435509
g_test_add_func ("/context/env", test_context_env);
436510
g_test_add_func ("/context/env-fd", test_context_env_fd);
437511
g_test_add_func ("/context/merge-fs", test_context_merge_fs);
512+
g_test_add_func ("/context/validate-path-args", test_validate_path_args);
513+
g_test_add_func ("/context/validate-path-meta", test_validate_path_meta);
438514

439515
return g_test_run ();
440516
}

Diff for: tests/testcommon.c

+38-3
Original file line numberDiff line numberDiff line change
@@ -1847,11 +1847,12 @@ static EscapeData escapes[] = {
18471847
{"abc def", FLATPAK_ESCAPE_DEFAULT, "abc def"},
18481848
{"やあ", FLATPAK_ESCAPE_DEFAULT, "やあ"},
18491849
{"\033[;1m", FLATPAK_ESCAPE_DEFAULT, "'\\x1B[;1m'"},
1850-
// non-printable U+061C
1850+
/* U+061C ARABIC LETTER MARK, non-printable */
18511851
{"\u061C", FLATPAK_ESCAPE_DEFAULT, "'\\u061C'"},
1852-
// non-printable U+1343F
1852+
/* U+1343F EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE, non-printable and
1853+
* outside BMP */
18531854
{"\xF0\x93\x90\xBF", FLATPAK_ESCAPE_DEFAULT, "'\\U0001343F'"},
1854-
// invalid utf-8
1855+
/* invalid utf-8 */
18551856
{"\xD8\1", FLATPAK_ESCAPE_DEFAULT, "'\\xD8\\x01'"},
18561857
{"\b \n abc ' \\", FLATPAK_ESCAPE_DEFAULT, "'\\x08 \\x0A abc \\' \\\\'"},
18571858
{"\b \n abc ' \\", FLATPAK_ESCAPE_DO_NOT_QUOTE, "\\x08 \\x0A abc ' \\\\"},
@@ -1875,6 +1876,39 @@ test_string_escape (void)
18751876
}
18761877
}
18771878

1879+
typedef struct {
1880+
const char *path;
1881+
gboolean ret;
1882+
} PathValidityData;
1883+
1884+
static PathValidityData paths[] = {
1885+
{"/a/b/../c.def", TRUE},
1886+
{"やあ", TRUE},
1887+
/* U+061C ARABIC LETTER MARK, non-printable */
1888+
{"\u061C", FALSE},
1889+
/* U+1343F EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE, non-printable and
1890+
* outside BMP */
1891+
{"\xF0\x93\x90\xBF", FALSE},
1892+
/* invalid utf-8 */
1893+
{"\xD8\1", FALSE},
1894+
};
1895+
1896+
/* CVE-2023-28101 */
1897+
static void
1898+
test_validate_path_characters (void)
1899+
{
1900+
gsize idx;
1901+
1902+
for (idx = 0; idx < G_N_ELEMENTS (paths); idx++)
1903+
{
1904+
PathValidityData *data = &paths[idx];
1905+
gboolean ret = FALSE;
1906+
1907+
ret = flatpak_validate_path_characters (data->path, NULL);
1908+
g_assert_cmpint (ret, ==, data->ret);
1909+
}
1910+
}
1911+
18781912
int
18791913
main (int argc, char *argv[])
18801914
{
@@ -1909,6 +1943,7 @@ main (int argc, char *argv[])
19091943
g_test_add_func ("/common/str-is-integer", test_str_is_integer);
19101944
g_test_add_func ("/common/parse-x11-display", test_parse_x11_display);
19111945
g_test_add_func ("/common/string-escape", test_string_escape);
1946+
g_test_add_func ("/common/validate-path-characters", test_validate_path_characters);
19121947

19131948
g_test_add_func ("/app/looks-like-branch", test_looks_like_branch);
19141949
g_test_add_func ("/app/columns", test_columns);

0 commit comments

Comments
 (0)