Skip to content

Commit 6cac99d

Browse files
refi64smcv
authored andcommitted
Ensure special characters in permissions and metadata are escaped
This prevents someone from placing special characters in order to manipulate the appearance of the permissions list. CVE-2023-28101, GHSA-h43h-fwqx-mpp8 Signed-off-by: Ryan Gonzalez <ryan.gonzalez@collabora.com>
1 parent 3abfddb commit 6cac99d

8 files changed

+168
-11
lines changed

Diff for: app/flatpak-builtins-info.c

+6-2
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,9 @@ flatpak_builtin_info (int argc, char **argv, GCancellable *cancellable, GError *
400400
if (!g_file_load_contents (file, cancellable, &data, &data_size, NULL, error))
401401
return FALSE;
402402

403-
g_print ("%s", data);
403+
flatpak_print_escaped_string (data,
404+
FLATPAK_ESCAPE_ALLOW_NEWLINES
405+
| FLATPAK_ESCAPE_DO_NOT_QUOTE);
404406
}
405407

406408
if (opt_show_permissions || opt_file_access)
@@ -421,7 +423,9 @@ flatpak_builtin_info (int argc, char **argv, GCancellable *cancellable, GError *
421423
if (contents == NULL)
422424
return FALSE;
423425

424-
g_print ("%s", contents);
426+
flatpak_print_escaped_string (contents,
427+
FLATPAK_ESCAPE_ALLOW_NEWLINES
428+
| FLATPAK_ESCAPE_DO_NOT_QUOTE);
425429
}
426430

427431
if (opt_file_access)

Diff for: app/flatpak-builtins-remote-info.c

+4-1
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,10 @@ flatpak_builtin_remote_info (int argc, char **argv, GCancellable *cancellable, G
431431

432432
if (opt_show_metadata)
433433
{
434-
g_print ("%s", xa_metadata ? xa_metadata : "");
434+
if (xa_metadata != NULL)
435+
flatpak_print_escaped_string (xa_metadata,
436+
FLATPAK_ESCAPE_ALLOW_NEWLINES
437+
| FLATPAK_ESCAPE_DO_NOT_QUOTE);
435438
if (xa_metadata == NULL || !g_str_has_suffix (xa_metadata, "\n"))
436439
g_print ("\n");
437440
}

Diff for: app/flatpak-cli-transaction.c

+8-4
Original file line numberDiff line numberDiff line change
@@ -1121,12 +1121,16 @@ print_perm_line (int idx,
11211121
int cols)
11221122
{
11231123
g_autoptr(GString) res = g_string_new (NULL);
1124+
g_autofree char *escaped_first_perm = NULL;
11241125
int i;
11251126

1126-
g_string_append_printf (res, " [%d] %s", idx, (char *) items->pdata[0]);
1127+
escaped_first_perm = flatpak_escape_string (items->pdata[0], FLATPAK_ESCAPE_DEFAULT);
1128+
g_string_append_printf (res, " [%d] %s", idx, escaped_first_perm);
11271129

11281130
for (i = 1; i < items->len; i++)
11291131
{
1132+
g_autofree char *escaped = flatpak_escape_string (items->pdata[i],
1133+
FLATPAK_ESCAPE_DEFAULT);
11301134
char *p;
11311135
int len;
11321136

@@ -1135,10 +1139,10 @@ print_perm_line (int idx,
11351139
p = res->str;
11361140

11371141
len = (res->str + strlen (res->str)) - p;
1138-
if (len + strlen ((char *) items->pdata[i]) + 2 >= cols)
1139-
g_string_append_printf (res, ",\n %s", (char *) items->pdata[i]);
1142+
if (len + strlen (escaped) + 2 >= cols)
1143+
g_string_append_printf (res, ",\n %s", escaped);
11401144
else
1141-
g_string_append_printf (res, ", %s", (char *) items->pdata[i]);
1145+
g_string_append_printf (res, ", %s", escaped);
11421146
}
11431147

11441148
g_print ("%s\n", res->str);

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

+11
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,17 @@ gboolean flatpak_str_is_integer (const char *s);
926926
gboolean flatpak_uri_equal (const char *uri1,
927927
const char *uri2);
928928

929+
typedef enum {
930+
FLATPAK_ESCAPE_DEFAULT = 0,
931+
FLATPAK_ESCAPE_ALLOW_NEWLINES = 1 << 0,
932+
FLATPAK_ESCAPE_DO_NOT_QUOTE = 1 << 1,
933+
} FlatpakEscapeFlags;
934+
935+
char * flatpak_escape_string (const char *s,
936+
FlatpakEscapeFlags flags);
937+
void flatpak_print_escaped_string (const char *s,
938+
FlatpakEscapeFlags flags);
939+
929940
gboolean running_under_sudo (void);
930941

931942
#define FLATPAK_MESSAGE_ID "c7b39b1e006b464599465e105b361485"

Diff for: common/flatpak-utils.c

+81-1
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ load_kernel_module_list (void)
613613
g_autofree char *modules_data = NULL;
614614
g_autoptr(GError) error = NULL;
615615
char *start, *end;
616-
616+
617617
if (!g_file_get_contents ("/proc/modules", &modules_data, NULL, &error))
618618
{
619619
g_info ("Failed to read /proc/modules: %s", error->message);
@@ -9222,6 +9222,86 @@ flatpak_uri_equal (const char *uri1,
92229222
return g_strcmp0 (uri1_norm, uri2_norm) == 0;
92239223
}
92249224

9225+
static gboolean
9226+
is_char_safe (gunichar c)
9227+
{
9228+
return g_unichar_isgraph (c) || c == ' ';
9229+
}
9230+
9231+
static gboolean
9232+
should_hex_escape (gunichar c,
9233+
FlatpakEscapeFlags flags)
9234+
{
9235+
if ((flags & FLATPAK_ESCAPE_ALLOW_NEWLINES) && c == '\n')
9236+
return FALSE;
9237+
9238+
return !is_char_safe (c);
9239+
}
9240+
9241+
static void
9242+
append_hex_escaped_character (GString *result,
9243+
gunichar c)
9244+
{
9245+
if (c <= 0xFF)
9246+
g_string_append_printf (result, "\\x%02X", c);
9247+
else if (c <= 0xFFFF)
9248+
g_string_append_printf (result, "\\u%04X", c);
9249+
else
9250+
g_string_append_printf (result, "\\U%08X", c);
9251+
}
9252+
9253+
char *
9254+
flatpak_escape_string (const char *s,
9255+
FlatpakEscapeFlags flags)
9256+
{
9257+
g_autoptr(GString) res = g_string_new ("");
9258+
gboolean did_escape = FALSE;
9259+
9260+
while (*s)
9261+
{
9262+
gunichar c = g_utf8_get_char_validated (s, -1);
9263+
if (c == (gunichar)-2 || c == (gunichar)-1)
9264+
{
9265+
/* Need to convert to unsigned first, to avoid negative chars becoming
9266+
huge gunichars. */
9267+
append_hex_escaped_character (res, (unsigned char)*s++);
9268+
did_escape = TRUE;
9269+
continue;
9270+
}
9271+
else if (should_hex_escape (c, flags))
9272+
{
9273+
append_hex_escaped_character (res, c);
9274+
did_escape = TRUE;
9275+
}
9276+
else if (c == '\\' || (!(flags & FLATPAK_ESCAPE_DO_NOT_QUOTE) && c == '\''))
9277+
{
9278+
g_string_append_printf (res, "\\%c", (char) c);
9279+
did_escape = TRUE;
9280+
}
9281+
else
9282+
g_string_append_unichar (res, c);
9283+
9284+
s = g_utf8_find_next_char (s, NULL);
9285+
}
9286+
9287+
if (did_escape && !(flags & FLATPAK_ESCAPE_DO_NOT_QUOTE))
9288+
{
9289+
g_string_prepend_c (res, '\'');
9290+
g_string_append_c (res, '\'');
9291+
}
9292+
9293+
return g_string_free (g_steal_pointer (&res), FALSE);
9294+
}
9295+
9296+
void
9297+
flatpak_print_escaped_string (const char *s,
9298+
FlatpakEscapeFlags flags)
9299+
{
9300+
g_autofree char *escaped = flatpak_escape_string (s, flags);
9301+
g_print ("%s", escaped);
9302+
}
9303+
9304+
92259305
gboolean
92269306
running_under_sudo (void)
92279307
{

Diff for: tests/make-test-app.sh

+8
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ required-flatpak=$REQUIRED_VERSION
4040
EOF
4141
fi
4242

43+
if [ x${INCLUDE_SPECIAL_CHARACTER-} != x ]; then
44+
TAB=$'\t'
45+
cat >> ${DIR}/metadata <<EOF
46+
[Environment]
47+
A=x${TAB}y
48+
EOF
49+
fi
50+
4351
cat >> ${DIR}/metadata <<EOF
4452
[Extension $APP_ID.Locale]
4553
directory=share/runtime/locale

Diff for: tests/test-info.sh

+11-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ set -euo pipefail
66

77
skip_revokefs_without_fuse
88

9-
echo "1..7"
9+
echo "1..8"
1010

11-
setup_repo
11+
INCLUDE_SPECIAL_CHARACTER=1 setup_repo
1212
install_repo
1313

1414
COMMIT=`${FLATPAK} ${U} info --show-commit org.test.Hello`
@@ -19,9 +19,17 @@ assert_file_has_content info "^app/org\.test\.Hello/$(flatpak --default-arch)/ma
1919

2020
ok "info -rcos"
2121

22+
${FLATPAK} info --show-metadata org.test.Hello > info
23+
24+
# CVE-2023-28101
25+
assert_file_has_content info "name=org\.test\.Hello"
26+
assert_file_has_content info "^A=x\\\\x09y"
27+
28+
ok "info --show-metadata"
29+
2230
${FLATPAK} info --show-permissions org.test.Hello > info
2331

24-
assert_file_empty info
32+
assert_file_has_content info "^A=x\\\\x09y"
2533

2634
ok "info --show-permissions"
2735

Diff for: tests/testcommon.c

+39
Original file line numberDiff line numberDiff line change
@@ -1837,6 +1837,44 @@ test_parse_x11_display (void)
18371837
}
18381838
}
18391839

1840+
typedef struct {
1841+
const char *in;
1842+
FlatpakEscapeFlags flags;
1843+
const char *out;
1844+
} EscapeData;
1845+
1846+
static EscapeData escapes[] = {
1847+
{"abc def", FLATPAK_ESCAPE_DEFAULT, "abc def"},
1848+
{"やあ", FLATPAK_ESCAPE_DEFAULT, "やあ"},
1849+
{"\033[;1m", FLATPAK_ESCAPE_DEFAULT, "'\\x1B[;1m'"},
1850+
// non-printable U+061C
1851+
{"\u061C", FLATPAK_ESCAPE_DEFAULT, "'\\u061C'"},
1852+
// non-printable U+1343F
1853+
{"\xF0\x93\x90\xBF", FLATPAK_ESCAPE_DEFAULT, "'\\U0001343F'"},
1854+
// invalid utf-8
1855+
{"\xD8\1", FLATPAK_ESCAPE_DEFAULT, "'\\xD8\\x01'"},
1856+
{"\b \n abc ' \\", FLATPAK_ESCAPE_DEFAULT, "'\\x08 \\x0A abc \\' \\\\'"},
1857+
{"\b \n abc ' \\", FLATPAK_ESCAPE_DO_NOT_QUOTE, "\\x08 \\x0A abc ' \\\\"},
1858+
{"abc\tdef\n\033[;1m ghi\b", FLATPAK_ESCAPE_ALLOW_NEWLINES | FLATPAK_ESCAPE_DO_NOT_QUOTE,
1859+
"abc\\x09def\n\\x1B[;1m ghi\\x08"},
1860+
};
1861+
1862+
/* CVE-2023-28101 */
1863+
static void
1864+
test_string_escape (void)
1865+
{
1866+
gsize idx;
1867+
1868+
for (idx = 0; idx < G_N_ELEMENTS (escapes); idx++)
1869+
{
1870+
EscapeData *data = &escapes[idx];
1871+
g_autofree char *ret = NULL;
1872+
1873+
ret = flatpak_escape_string (data->in, data->flags);
1874+
g_assert_cmpstr (ret, ==, data->out);
1875+
}
1876+
}
1877+
18401878
int
18411879
main (int argc, char *argv[])
18421880
{
@@ -1870,6 +1908,7 @@ main (int argc, char *argv[])
18701908
g_test_add_func ("/common/quote-argv", test_quote_argv);
18711909
g_test_add_func ("/common/str-is-integer", test_str_is_integer);
18721910
g_test_add_func ("/common/parse-x11-display", test_parse_x11_display);
1911+
g_test_add_func ("/common/string-escape", test_string_escape);
18731912

18741913
g_test_add_func ("/app/looks-like-branch", test_looks_like_branch);
18751914
g_test_add_func ("/app/columns", test_columns);

0 commit comments

Comments
 (0)