diff --git a/SOURCES/mingw-glib2-2.70.1-CVE-2025-14087.patch b/SOURCES/mingw-glib2-2.70.1-CVE-2025-14087.patch new file mode 100644 index 0000000..c6c130e --- /dev/null +++ b/SOURCES/mingw-glib2-2.70.1-CVE-2025-14087.patch @@ -0,0 +1,65 @@ +From 074d82962028125c7886e5edcff1a58a508e0e61 Mon Sep 17 00:00:00 2001 +From: Philip Withnall +Date: Tue, 25 Nov 2025 19:02:56 +0000 +Subject: [PATCH] gvariant-parser: Fix potential integer overflow parsing + (byte)strings + +The termination condition for parsing string and bytestring literals in +GVariant text format input was subject to an integer overflow for input +string (or bytestring) literals longer than `INT_MAX`. + +Fix that by counting as a `size_t` rather than as an `int`. The counter +can never correctly be negative. + +Spotted by treeplus. Thanks to the Sovereign Tech Resilience programme +from the Sovereign Tech Agency. ID: #YWH-PGM9867-145 + +Signed-off-by: Philip Withnall +Fixes: #3834 +--- + glib/gvariant-parser.c | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/glib/gvariant-parser.c b/glib/gvariant-parser.c +index bb5238bea..af6527d40 100644 +--- a/glib/gvariant-parser.c ++++ b/glib/gvariant-parser.c +@@ -594,7 +594,7 @@ ast_resolve (AST *ast, + { + GVariant *value; + gchar *pattern; +- gint i, j = 0; ++ size_t i, j = 0; + + pattern = ast_get_pattern (ast, error); + +@@ -1555,9 +1555,9 @@ string_free (AST *ast) + * No leading/trailing space allowed. */ + static gboolean + unicode_unescape (const gchar *src, +- gint *src_ofs, ++ size_t *src_ofs, + gchar *dest, +- gint *dest_ofs, ++ size_t *dest_ofs, + gsize length, + SourceRef *ref, + GError **error) +@@ -1618,7 +1618,7 @@ string_parse (TokenStream *stream, + gsize length; + gchar quote; + gchar *str; +- gint i, j; ++ size_t i, j; + + token_stream_start_ref (stream, &ref); + token = token_stream_get (stream); +@@ -1748,7 +1748,7 @@ bytestring_parse (TokenStream *stream, + gsize length; + gchar quote; + gchar *str; +- gint i, j; ++ size_t i, j; + + token_stream_start_ref (stream, &ref); + token = token_stream_get (stream); diff --git a/SOURCES/mingw-glib2-2.70.1-CVE-2026-58010.patch b/SOURCES/mingw-glib2-2.70.1-CVE-2026-58010.patch new file mode 100644 index 0000000..9cff400 --- /dev/null +++ b/SOURCES/mingw-glib2-2.70.1-CVE-2026-58010.patch @@ -0,0 +1,106 @@ +From eb63e89e237d35fb1d3cb757ea97b4c8806269a0 Mon Sep 17 00:00:00 2001 +From: Philip Withnall +Date: Sun, 29 Mar 2026 19:10:41 +0100 +Subject: [PATCH] gvariant: Fix an off-by-one error in an offset comparison +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This allows a single byte out-of-bounds read off the end of the +(potentially untrusted) byte array backing a `GVariant` when it’s +being checked for normal form. + +I can’t see how this could practically be exploited, but it’s certainly +a security bug as the `GVariant` normal form checking code is supposed +to be robust to malicious inputs. + +Spotted by linhlhq as #YWH-PGM9867-190, and fix and reproducer provided +by them too, thanks. Confirmed and turned into a unit test by me. + +Signed-off-by: Philip Withnall + +Fixes: #3915 +--- + glib/gvariant-serialiser.c | 2 +- + glib/tests/gvariant.c | 48 ++++++++++++++++++++++++++++++++++++++ + 2 files changed, 49 insertions(+), 1 deletion(-) + +diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c +index 832a8fdc2..1bf3e4688 100644 +--- a/glib/gvariant-serialiser.c ++++ b/glib/gvariant-serialiser.c +@@ -1079,7 +1079,7 @@ gvs_tuple_is_normal (GVariantSerialised value) + + while (offset & alignment) + { +- if (offset > value.size || value.data[offset] != '\0') ++ if (offset >= value.size || value.data[offset] != '\0') + return FALSE; + offset++; + } +diff --git a/glib/tests/gvariant.c b/glib/tests/gvariant.c +index 0110f2664..dd3af6941 100644 +--- a/glib/tests/gvariant.c ++++ b/glib/tests/gvariant.c +@@ -5047,6 +5047,52 @@ test_normal_checking_tuple_offsets (void) + g_variant_unref (variant); + } + ++/* This is a regression test that looping over the padding bytes in a short ++ * (non-normal) tuple doesn’t overflow the input data. ++ * ++ * See https://gitlab.gnome.org/GNOME/glib/-/issues/3915 */ ++static void ++test_normal_checking_tuple_offsets6 (void) ++{ ++ /* ++ * Type: (ynqiuxthdsog) — 12 members, first member 'y' (byte) has ++ * alignment 0, second 'n' (int16) has alignment 1. ++ * With 1 byte of data (0x28), after reading the first byte member, ++ * offset=1, alignment check for 'n' requires offset to be even, ++ * so the while loop checks value.data[1] — but size is only 1. ++ * ++ * Use heap allocation via GBytes so ASan reports heap-buffer-overflow. ++ */ ++ uint8_t *heap_data = NULL; ++ GBytes *bytes = NULL; ++ const GVariantType *data_type = G_VARIANT_TYPE ("(ynqiuxthdsog)"); ++ GVariant *variant = NULL; ++ GVariant *normal_variant = NULL; ++ GVariant *expected = NULL; ++ ++ g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3915"); ++ ++ heap_data = g_malloc (1); ++ heap_data[0] = 0x28; ++ bytes = g_bytes_new_take (heap_data, 1); ++ ++ variant = g_variant_new_from_bytes (data_type, bytes, FALSE); ++ g_assert_nonnull (variant); ++ ++ g_assert_false (g_variant_is_normal_form (variant)); ++ ++ normal_variant = g_variant_get_normal_form (variant); ++ g_assert_nonnull (normal_variant); ++ ++ expected = g_variant_new_parsed ("(byte 0x28, int16 0, uint16 0, 0, uint32 0, int64 0, uint64 0, handle 0, 0.0, '', objectpath '/', signature '')"); ++ g_assert_cmpvariant (expected, variant); ++ g_assert_cmpvariant (expected, normal_variant); ++ ++ g_variant_unref (expected); ++ g_variant_unref (normal_variant); ++ g_variant_unref (variant); ++} ++ + /* Test that an empty object path is normalised successfully to the base object + * path, ‘/’. */ + static void +@@ -5191,6 +5237,8 @@ main (int argc, char **argv) + test_normal_checking_array_offsets); + g_test_add_func ("/gvariant/normal-checking/tuple-offsets", + test_normal_checking_tuple_offsets); ++ g_test_add_func ("/gvariant/normal-checking/tuple-offsets6", ++ test_normal_checking_tuple_offsets6); + g_test_add_func ("/gvariant/normal-checking/empty-object-path", + test_normal_checking_empty_object_path); + diff --git a/SOURCES/mingw-glib2-2.70.1-CVE-2026-58011.patch b/SOURCES/mingw-glib2-2.70.1-CVE-2026-58011.patch new file mode 100644 index 0000000..f91862a --- /dev/null +++ b/SOURCES/mingw-glib2-2.70.1-CVE-2026-58011.patch @@ -0,0 +1,122 @@ +From fbef21c7da7757239d6df411a24c7dbb345ab966 Mon Sep 17 00:00:00 2001 +From: Philip Withnall +Date: Sun, 29 Mar 2026 23:19:47 +0100 +Subject: [PATCH 1/2] gdatetime: Factor out a couple of magic constants + +This introduces no functional changes, it just makes the code a little +clearer. + +Signed-off-by: Philip Withnall +--- + glib/gdatetime.c | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +diff --git a/glib/gdatetime.c b/glib/gdatetime.c +index a31afe713..029636c08 100644 +--- a/glib/gdatetime.c ++++ b/glib/gdatetime.c +@@ -130,7 +130,7 @@ struct _GDateTime + gint interval; + + /* 1 is 0001-01-01 in Proleptic Gregorian */ +- gint32 days; ++ gint32 days; /* in range [MIN_DAYS, MAX_DAYS] */ + + gint ref_count; /* (atomic) */ + }; +@@ -172,6 +172,9 @@ struct _GDateTime + #define JULIAN_YEAR(d) ((d)->julian / 365.25) + #define DAYS_PER_PERIOD (G_GINT64_CONSTANT (2914695)) + ++#define MIN_DAYS 1 /* the days count for 0001-01-01 in Proleptic Gregorian */ ++#define MAX_DAYS 3652059 /* the days count for 9999-12-31 in Proleptic Gregorian */ ++ + static const guint16 days_in_months[2][13] = + { + { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, +@@ -781,7 +784,7 @@ g_date_time_from_instant (GTimeZone *tz, + datetime->days = instant / USEC_PER_DAY; + datetime->usec = instant % USEC_PER_DAY; + +- if (datetime->days < 1 || 3652059 < datetime->days) ++ if (datetime->days < MIN_DAYS || datetime->days > MAX_DAYS) + { + g_date_time_unref (datetime); + datetime = NULL; +@@ -817,7 +820,7 @@ g_date_time_deal_with_date_change (GDateTime *datetime) + gint64 full_time; + gint64 usec; + +- if (datetime->days < 1 || datetime->days > 3652059) ++ if (datetime->days < MIN_DAYS || datetime->days > MAX_DAYS) + return FALSE; + + was_dst = g_time_zone_is_dst (datetime->tz, datetime->interval); + +From 5f0c67435364fe682fe9dcc1d23645134a445a17 Mon Sep 17 00:00:00 2001 +From: Philip Withnall +Date: Sun, 29 Mar 2026 23:46:17 +0100 +Subject: [PATCH 2/2] gdatetime: Add missing range validation to + g_date_time_add_full() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Otherwise it’s possible to create a non-`NULL` but invalid `GDateTime`, +which breaks all kinds of internal assumptions. + +Spotted by linhlhq as #YWH-PGM9867-191. Thanks to them for providing a +suggested fix and a test case, which I have adapted and validated. + +Signed-off-by: Philip Withnall + +Fixes: #3917 +--- + glib/gdatetime.c | 4 +++- + glib/tests/gdatetime.c | 18 ++++++++++++++++++ + 2 files changed, 21 insertions(+), 1 deletion(-) + +diff --git a/glib/gdatetime.c b/glib/gdatetime.c +index 029636c08..883322891 100644 +--- a/glib/gdatetime.c ++++ b/glib/gdatetime.c +@@ -2022,7 +2022,9 @@ g_date_time_add_full (GDateTime *datetime, + new->days = full_time / USEC_PER_DAY; + new->usec = full_time % USEC_PER_DAY; + +- /* XXX validate */ ++ /* Validate it’s still in the range 0001-01-01 to 9999-12-31 */ ++ if (new->days < MIN_DAYS || new->days > MAX_DAYS) ++ g_clear_pointer (&new, g_date_time_unref); + + return new; + } +diff --git a/glib/tests/gdatetime.c b/glib/tests/gdatetime.c +index 12f332b44..88041a684 100644 +--- a/glib/tests/gdatetime.c ++++ b/glib/tests/gdatetime.c +@@ -1098,6 +1098,24 @@ test_GDateTime_add_full (void) + TEST_ADD_FULL (2010, 8, 25, 22, 45, 0, + 0, 1, 6, 1, 25, 0, + 2010, 10, 2, 0, 10, 0); ++ ++#define TEST_ADD_FULL_ERROR(y,m,d,h,mi,s,ay,am,ad,ah,ami,as) G_STMT_START { \ ++ GDateTime *dt; \ ++ dt = g_date_time_new_utc (y, m, d, h, mi, s); \ ++ g_assert_null (g_date_time_add_full (dt, ay, am, ad, ah, ami, as)); \ ++ g_date_time_unref (dt); \ ++} G_STMT_END ++ ++ TEST_ADD_FULL_ERROR ( 1, 12, 1, 0, 0, 0, ++ -1, 0, 0, 0, 0, 0); ++ TEST_ADD_FULL_ERROR ( 1, 12, 1, 0, 0, 0, ++ 10000, 0, 0, 0, 0, 0); ++ TEST_ADD_FULL_ERROR ( 9999, 12, 1, 0, 0, 0, ++ -10000, 0, 0, 0, 0, 0); ++ TEST_ADD_FULL_ERROR ( 1, 12, 1, 0, 0, 0, ++ 0, 0, 3660001, 0, 0, 0); ++ TEST_ADD_FULL_ERROR ( 9999, 12, 1, 0, 0, 0, ++ 0, 0, -3660001, 0, 0, 0); + } + + static void diff --git a/SOURCES/mingw-glib2-2.70.1-CVE-2026-58012.patch b/SOURCES/mingw-glib2-2.70.1-CVE-2026-58012.patch new file mode 100644 index 0000000..2d1dd69 --- /dev/null +++ b/SOURCES/mingw-glib2-2.70.1-CVE-2026-58012.patch @@ -0,0 +1,221 @@ +From a9a4c378a05723eb164f193d585b6611e4646b65 Mon Sep 17 00:00:00 2001 +From: Philip Withnall +Date: Tue, 31 Mar 2026 16:13:57 +0100 +Subject: [PATCH] gregex: Fix case changing substitutions with G_REGEX_RAW + +In `G_REGEX_RAW` mode, the input string is treated as a byte array +(basically ASCII) rather than a unichar array. Accordingly, the case +changing code for substitutions needs to operate on bytes with +`G_REGEX_RAW`, rather than operating on unichars. + +This fixes a potential buffer overflow when trying to do a case change +on a match of a set of bytes which are a truncated multi-byte UTF-8 +encoding at the end of the input buffer. + +Spotted by linhlhq as #YWH-PGM9867-193. I adapted their reproducer as +the unit test, but implemented the fix in `gregex.c` independently. + +Signed-off-by: Philip Withnall + +Fixes: #3918 +--- + glib/gregex.c | 59 ++++++++++++++++++++++++++++++++++------------ + glib/tests/regex.c | 53 +++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 97 insertions(+), 15 deletions(-) + +diff --git a/glib/gregex.c b/glib/gregex.c +index a8a35a424..948df3f95 100644 +--- a/glib/gregex.c ++++ b/glib/gregex.c +@@ -2655,19 +2655,25 @@ split_replacement (const gchar *replacement, + return g_list_reverse (list); + } + +-/* Change the case of c based on change_case. */ +-#define CHANGE_CASE(c, change_case) \ ++/* Change the case of c based on change_case. ++ * g_ascii_to*() will happily pass through non-ASCII bytes unchanged. */ ++#define UTF8_CHANGE_CASE(c, change_case) \ + (((change_case) & CHANGE_CASE_LOWER_MASK) ? \ + g_unichar_tolower (c) : \ + g_unichar_toupper (c)) ++#define RAW_CHANGE_CASE(c, change_case) \ ++ (((change_case) & CHANGE_CASE_LOWER_MASK) ? \ ++ g_ascii_tolower (c) : \ ++ g_ascii_toupper (c)) + ++/* If @text_is_raw is set, @text might not be valid UTF-8 (but will be ++ * nul-terminated). */ + static void + string_append (GString *string, + const gchar *text, ++ gboolean text_is_raw, + ChangeCase *change_case) + { +- gunichar c; +- + if (text[0] == '\0') + return; + +@@ -2677,22 +2683,44 @@ string_append (GString *string, + } + else if (*change_case & CHANGE_CASE_SINGLE_MASK) + { +- c = g_utf8_get_char (text); +- g_string_append_unichar (string, CHANGE_CASE (c, *change_case)); +- g_string_append (string, g_utf8_next_char (text)); ++ if (!text_is_raw) ++ { ++ gunichar c = g_utf8_get_char (text); ++ g_string_append_unichar (string, UTF8_CHANGE_CASE (c, *change_case)); ++ g_string_append (string, g_utf8_next_char (text)); ++ } ++ else ++ { ++ g_string_append_c (string, RAW_CHANGE_CASE (text[0], *change_case)); ++ g_string_append (string, text + 1); ++ } ++ + *change_case = CHANGE_CASE_NONE; + } + else + { +- while (*text != '\0') ++ if (!text_is_raw) + { +- c = g_utf8_get_char (text); +- g_string_append_unichar (string, CHANGE_CASE (c, *change_case)); +- text = g_utf8_next_char (text); ++ while (*text != '\0') ++ { ++ gunichar c = g_utf8_get_char (text); ++ g_string_append_unichar (string, UTF8_CHANGE_CASE (c, *change_case)); ++ text = g_utf8_next_char (text); ++ } ++ } ++ else ++ { ++ while (*text != '\0') ++ { ++ char c = *text; ++ g_string_append_c (string, RAW_CHANGE_CASE (c, *change_case)); ++ text++; ++ } + } + } + } + ++/* @match_info is (nullable) */ + static gboolean + interpolate_replacement (const GMatchInfo *match_info, + GString *result, +@@ -2702,6 +2730,7 @@ interpolate_replacement (const GMatchInfo *match_info, + InterpolationData *idata; + gchar *match; + ChangeCase change_case = CHANGE_CASE_NONE; ++ gboolean is_raw = (match_info != NULL && (match_info->regex->compile_opts & G_REGEX_RAW)); + + for (list = data; list; list = list->next) + { +@@ -2709,10 +2738,10 @@ interpolate_replacement (const GMatchInfo *match_info, + switch (idata->type) + { + case REPL_TYPE_STRING: +- string_append (result, idata->text, &change_case); ++ string_append (result, idata->text, is_raw, &change_case); + break; + case REPL_TYPE_CHARACTER: +- g_string_append_c (result, CHANGE_CASE (idata->c, change_case)); ++ g_string_append_c (result, UTF8_CHANGE_CASE (idata->c, change_case)); + if (change_case & CHANGE_CASE_SINGLE_MASK) + change_case = CHANGE_CASE_NONE; + break; +@@ -2720,7 +2749,7 @@ interpolate_replacement (const GMatchInfo *match_info, + match = g_match_info_fetch (match_info, idata->num); + if (match) + { +- string_append (result, match, &change_case); ++ string_append (result, match, is_raw, &change_case); + g_free (match); + } + break; +@@ -2728,7 +2757,7 @@ interpolate_replacement (const GMatchInfo *match_info, + match = g_match_info_fetch_named (match_info, idata->text); + if (match) + { +- string_append (result, match, &change_case); ++ string_append (result, match, is_raw, &change_case); + g_free (match); + } + break; +diff --git a/glib/tests/regex.c b/glib/tests/regex.c +index 88d12edf6..818d6d0a0 100644 +--- a/glib/tests/regex.c ++++ b/glib/tests/regex.c +@@ -2183,6 +2183,58 @@ pcre_ge (guint64 major, guint64 minor) + return (pcre_major > major) || (pcre_major == major && pcre_minor >= minor); + } + ++static void ++test_replace_raw_change_case (void) ++{ ++ GError *local_error = NULL; ++ GRegex *regex = NULL; ++ ++ g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3918"); ++ g_test_summary ("Test that case changes as part of a replacement are handled correctly in G_REGEX_RAW mode"); ++ ++ /* ++ * Match a multi-byte sequence in RAW mode. The pattern matches ++ * exactly 2 bytes. The subject contains a 4-byte UTF-8 lead (0xF4) ++ * followed by only one continuation byte, then NUL. ++ * ++ * The matched substring will be "\xf4\x80" (2 bytes, heap-allocated ++ * as 3-byte buffer with NUL). If the code regresses and tries to handle ++ * the replacement as UTF-8 then g_utf8_get_char() would see 0xF4 and try ++ * to read 4 bytes, going 1 byte past the NUL into OOB territory. ++ */ ++ regex = g_regex_new ("..", G_REGEX_RAW, 0, &local_error); ++ g_assert_no_error (local_error); ++ ++ /* ++ * Build a subject string with truncated UTF-8. ++ * \xF4 = 4-byte UTF-8 lead byte ++ * \x80 = continuation byte ++ * No 3rd/4th continuation bytes — the match is only 2 bytes. ++ * ++ * \U\0 = uppercase the entire match → triggers string_append() ++ * with case change on the 2-byte non-UTF-8 match. ++ */ ++ char subject[] = "\xf4\x80"; ++ char *result = g_regex_replace (regex, subject, -1, 0, "\\U\\0", 0, &local_error); ++ g_assert_no_error (local_error); ++ ++ g_clear_pointer (&result, g_free); ++ g_clear_pointer (®ex, g_regex_unref); ++ ++ /* ++ * Second variant: single-char case change \u with \0 backreference. ++ */ ++ regex = g_regex_new (".", G_REGEX_RAW, 0, &local_error); ++ g_assert_no_error (local_error); ++ ++ char subject2[] = "\xe6\xb0"; /* 3-byte UTF-8 lead, only 2 bytes */ ++ result = g_regex_replace (regex, subject2, -1, 0, "\\u\\0", 0, &local_error); ++ g_assert_no_error (local_error); ++ ++ g_clear_pointer (&result, g_free); ++ g_clear_pointer (®ex, g_regex_unref); ++} ++ + int + main (int argc, char *argv[]) + { +@@ -2200,6 +2252,7 @@ main (int argc, char *argv[]) + g_test_add_func ("/regex/multiline", test_multiline); + g_test_add_func ("/regex/explicit-crlf", test_explicit_crlf); + g_test_add_func ("/regex/max-lookbehind", test_max_lookbehind); ++ g_test_add_func ("/regex/replace-raw-change-case", test_replace_raw_change_case); + + /* TEST_NEW(pattern, compile_opts, match_opts) */ + TEST_NEW("[A-Z]+", G_REGEX_CASELESS | G_REGEX_EXTENDED | G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTBOL | G_REGEX_MATCH_PARTIAL); diff --git a/SOURCES/mingw-glib2-2.70.1-CVE-2026-58013.patch b/SOURCES/mingw-glib2-2.70.1-CVE-2026-58013.patch new file mode 100644 index 0000000..b0afd38 --- /dev/null +++ b/SOURCES/mingw-glib2-2.70.1-CVE-2026-58013.patch @@ -0,0 +1,120 @@ +From 5e8a44975bddd21a762e58c6717f841e90594f03 Mon Sep 17 00:00:00 2001 +From: Philip Withnall +Date: Tue, 28 Apr 2026 16:45:14 +0100 +Subject: [PATCH] giochannel: Fix memcmp() off the end of the buffer with long + terminators +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +If the line terminator is longer than a single byte, and the current +line extends to the end of the buffer, and the buffer (which is a +`GString`) is near a power of two in length (as that’s how `GString`s +are allocated) it’s possible for the `memcmp()` which checks the +terminator to read off the end of the string buffer. + +Fix that by checking the terminator length against the last character +before calling `memcmp()`. Add a unit test. + +Spotted by linhlhq as #YWH-PGM9867-199. The fix is theirs (validated by +me), and the unit test is adapted from their proof of concept. + +Signed-off-by: Philip Withnall +Fixes: #3925 +--- + glib/giochannel.c | 3 ++- + glib/tests/io-channel.c | 60 +++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 62 insertions(+), 1 deletion(-) + +diff --git a/glib/giochannel.c b/glib/giochannel.c +index e93c4b458..66b68e645 100644 +--- a/glib/giochannel.c ++++ b/glib/giochannel.c +@@ -1830,7 +1830,8 @@ read_again: + { + if (channel->line_term) + { +- if (memcmp (channel->line_term, nextchar, line_term_len) == 0) ++ if ((size_t) (lastchar - nextchar) >= line_term_len && ++ memcmp (channel->line_term, nextchar, line_term_len) == 0) + { + line_length = nextchar - use_buf->str; + got_term_len = line_term_len; +diff --git a/glib/tests/io-channel.c b/glib/tests/io-channel.c +index 4a1b10876..c619fb00f 100644 +--- a/glib/tests/io-channel.c ++++ b/glib/tests/io-channel.c +@@ -69,6 +69,65 @@ test_read_line_embedded_nuls (void) + g_free (filename); + } + ++static void ++test_read_line_long_terminator (void) ++{ ++ uint8_t *test_data = NULL; ++ size_t test_data_len = 0; ++ int fd; ++ char *filename = NULL; ++ GIOChannel *channel = NULL; ++ GError *local_error = NULL; ++ char *line = NULL; ++ size_t line_length, terminator_pos; ++ const char *line_term; ++ int line_term_length; ++ GIOStatus status; ++ ++ g_test_summary ("Test that reading a line when using a long terminator doesn’t over-read the buffer."); ++ g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/work_items/3925"); ++ ++ /* Write out a temporary file containing 2047 bytes. This is enough to make it ++ * near the length of the GString buffer when read back in. */ ++ fd = g_file_open_tmp ("glib-test-io-channel-XXXXXX", &filename, &local_error); ++ g_assert_no_error (local_error); ++ g_close (g_steal_fd (&fd), NULL); ++ ++ test_data_len = 2047; ++ test_data = g_malloc (test_data_len); ++ memset (test_data, 'M', test_data_len); ++ g_file_set_contents (filename, (const gchar *) test_data, test_data_len, &local_error); ++ g_assert_no_error (local_error); ++ ++ /* Create the channel. */ ++ channel = g_io_channel_new_file (filename, "r", &local_error); ++ g_assert_no_error (local_error); ++ ++ /* Use a long line terminator so it could potentially over-read the end of the buffer. */ ++ g_io_channel_set_line_term (channel, "DEADBEEF", 8); ++ ++ line_term = g_io_channel_get_line_term (channel, &line_term_length); ++ g_assert_cmpstr (line_term, ==, "DEADBEEF"); ++ g_assert_cmpint (line_term_length, ==, 8); ++ ++ g_io_channel_set_encoding (channel, "UTF-8", &local_error); ++ g_assert_no_error (local_error); ++ ++ status = g_io_channel_read_line (channel, &line, &line_length, ++ &terminator_pos, &local_error); ++ g_assert_no_error (local_error); ++ g_assert_cmpint (status, ==, G_IO_STATUS_NORMAL); ++ g_assert_cmpuint (line_length, ==, 2047); ++ g_assert_cmpuint (terminator_pos, ==, 2047); ++ g_assert_cmpmem (line, line_length, test_data, test_data_len); ++ ++ g_free (line); ++ g_io_channel_unref (channel); ++ g_free (test_data); ++ g_unlink (filename); ++ g_free (filename); ++} ++ + int + main (int argc, + char *argv[]) +@@ -76,6 +135,7 @@ main (int argc, + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/io-channel/read-line/embedded-nuls", test_read_line_embedded_nuls); ++ g_test_add_func ("/io-channel/read-line/long-terminator", test_read_line_long_terminator); + + return g_test_run (); + } diff --git a/SOURCES/mingw-glib2-2.70.1-CVE-2026-58014.patch b/SOURCES/mingw-glib2-2.70.1-CVE-2026-58014.patch new file mode 100644 index 0000000..c276ddf --- /dev/null +++ b/SOURCES/mingw-glib2-2.70.1-CVE-2026-58014.patch @@ -0,0 +1,101 @@ +From 8e49ae633b6eaceff9881ea15c8bb48cf11c8a53 Mon Sep 17 00:00:00 2001 +From: Philip Withnall +Date: Sat, 11 Apr 2026 14:42:57 +0100 +Subject: [PATCH] gkeyfile: Fix a one-byte heap under-read with + g_key_file_get_locale_string_list() + +If this method was called on a key file key which has an empty value, +`len == 0` and this leads to a one-byte under-read off the start of the +key file buffer. + +Spotted by linhlhq as #YWH-PGM9867-200. The suggested fix is theirs, and +the unit test is adapted from their report. I added the fuzzing test. + +Signed-off-by: Philip Withnall + +Fixes: #3930 +--- + fuzzing/fuzz_key.c | 9 +++++++++ + glib/gkeyfile.c | 2 +- + glib/tests/keyfile.c | 23 +++++++++++++++++++++++ + 3 files changed, 33 insertions(+), 1 deletion(-) + +diff --git a/fuzzing/fuzz_key.c b/fuzzing/fuzz_key.c +index 9f1f9187e..285bad26f 100644 +--- a/fuzzing/fuzz_key.c ++++ b/fuzzing/fuzz_key.c +@@ -6,11 +6,20 @@ test_parse (const gchar *data, + GKeyFileFlags flags) + { + GKeyFile *key = NULL; ++ char *comment = NULL; ++ char **list = NULL; + + key = g_key_file_new (); + g_key_file_load_from_data (key, (const gchar*) data, size, G_KEY_FILE_NONE, + NULL); + ++ /* Also try some additional parsing and see if it crashes */ ++ comment = g_key_file_get_comment (key, "group", "key", NULL); ++ g_free (comment); ++ ++ list = g_key_file_get_locale_string_list (key, "group", "key", "de", NULL, NULL); ++ g_strfreev (list); ++ + g_key_file_free (key); + } + +diff --git a/glib/gkeyfile.c b/glib/gkeyfile.c +index 17cf85660..76459daab 100644 +--- a/glib/gkeyfile.c ++++ b/glib/gkeyfile.c +@@ -2407,7 +2407,7 @@ g_key_file_get_locale_string_list (GKeyFile *key_file, + } + + len = strlen (value); +- if (value[len - 1] == key_file->list_separator) ++ if (len > 0 && value[len - 1] == key_file->list_separator) + value[len - 1] = '\0'; + + list_separator[0] = key_file->list_separator; +diff --git a/glib/tests/keyfile.c b/glib/tests/keyfile.c +index 1f5be8b38..17724bc15 100644 +--- a/glib/tests/keyfile.c ++++ b/glib/tests/keyfile.c +@@ -800,6 +800,28 @@ test_locale_string_multiple_loads (void) + g_free (old_locale); + } + ++static void ++test_locale_string_empty (void) ++{ ++ GKeyFile *keyfile = NULL; ++ GError *local_error = NULL; ++ const char *data = ++ "[valid]\n" ++ "key1=\n"; ++ ++ g_test_summary ("Check that loading an empty translatable string works"); ++ g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3930"); ++ ++ keyfile = g_key_file_new (); ++ ++ g_key_file_load_from_data (keyfile, data, -1, G_KEY_FILE_NONE, &local_error); ++ g_assert_no_error (local_error); ++ ++ check_locale_string_list_value (keyfile, "valid", "key1", NULL, NULL); ++ ++ g_key_file_free (keyfile); ++} ++ + static void + test_lists (void) + { +@@ -1832,6 +1854,7 @@ main (int argc, char *argv[]) + g_test_add_func ("/keyfile/number", test_number); + g_test_add_func ("/keyfile/locale-string", test_locale_string); + g_test_add_func ("/keyfile/locale-string/multiple-loads", test_locale_string_multiple_loads); ++ g_test_add_func ("/keyfile/locale-string/empty", test_locale_string_empty); + g_test_add_func ("/keyfile/lists", test_lists); + g_test_add_func ("/keyfile/lists-set-get", test_lists_set_get); + g_test_add_func ("/keyfile/group-remove", test_group_remove); diff --git a/SOURCES/mingw-glib2-2.70.1-CVE-2026-58015.patch b/SOURCES/mingw-glib2-2.70.1-CVE-2026-58015.patch new file mode 100644 index 0000000..16cd1ac --- /dev/null +++ b/SOURCES/mingw-glib2-2.70.1-CVE-2026-58015.patch @@ -0,0 +1,578 @@ +From a37a31d4088cf9563756dbbf0c3a7d78e43cb9ee Mon Sep 17 00:00:00 2001 +From: Philip Withnall +Date: Tue, 28 Apr 2026 15:47:30 +0100 +Subject: [PATCH 1/5] gdbusauthmechanismsha1: Validate cookie context +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Without validation, the server could send a malicious context which +contains path traversal characters, allowing it to exfiltrate a SHA-1 +hashed copy of arbitrary data from the client’s file system. + +To exploit this successfully would require the client to choose to +connect peer-to-peer to a malicious D-Bus server and to choose the SHA-1 +authentication mechanism in preference to all the other mechanisms. This +is vanishingly unlikely. + +Signed-off-by: Philip Withnall + +Fixes: #3931 +--- + gio/gdbusauthmechanismsha1.c | 36 ++++++++++++++++++++++++++++++++++++ + 1 file changed, 36 insertions(+) + +diff --git a/gio/gdbusauthmechanismsha1.c b/gio/gdbusauthmechanismsha1.c +index 94fe0bce8..d2a045f4c 100644 +--- a/gio/gdbusauthmechanismsha1.c ++++ b/gio/gdbusauthmechanismsha1.c +@@ -1160,6 +1160,34 @@ mechanism_client_initiate (GDBusAuthMechanism *mechanism, + return initial_response; + } + ++/* Context names must be valid ASCII, nonzero length, and may not contain the ++ * characters slash ("/"), backslash ("\"), space (" "), newline ("\n"), ++ * carriage return ("\r"), tab ("\t"), or period ("."). ++ * ++ * See https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-sha */ ++static gboolean ++validate_cookie_context (const char *cookie_context) ++{ ++ size_t i = 0; ++ ++ g_return_val_if_fail (cookie_context != NULL, FALSE); ++ ++ for (i = 0; cookie_context[i] != '\0'; i++) ++ { ++ if ((uint8_t) cookie_context[i] >= 128 || ++ cookie_context[i] == '/' || ++ cookie_context[i] == '\\' || ++ cookie_context[i] == ' ' || ++ cookie_context[i] == '\n' || ++ cookie_context[i] == '\r' || ++ cookie_context[i] == '\t' || ++ cookie_context[i] == '.') ++ return FALSE; ++ } ++ ++ return (i > 0); ++} ++ + static void + mechanism_client_data_receive (GDBusAuthMechanism *mechanism, + const gchar *data, +@@ -1194,6 +1222,14 @@ mechanism_client_data_receive (GDBusAuthMechanism *mechanism, + } + + cookie_context = tokens[0]; ++ if (!validate_cookie_context (tokens[0])) ++ { ++ g_free (m->priv->reject_reason); ++ m->priv->reject_reason = g_strdup_printf ("Malformed cookie_context '%s'", tokens[0]); ++ m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; ++ goto out; ++ } ++ + cookie_id = g_ascii_strtoll (tokens[1], &endp, 10); + if (*endp != '\0') + { + +From 5bf53b50de87f0ad037508e4bd0099c4d0519b4b Mon Sep 17 00:00:00 2001 +From: Philip Withnall +Date: Tue, 28 Apr 2026 15:49:54 +0100 +Subject: [PATCH 2/5] gdbusauthmechanismsha1: Improve validation of cookie ID +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The D-Bus specification says the cookie ID has to be non-negative, but +we weren’t checking that (or checking that it was non-empty). + +Signed-off-by: Philip Withnall +--- + gio/gdbusauthmechanismsha1.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/gio/gdbusauthmechanismsha1.c b/gio/gdbusauthmechanismsha1.c +index d2a045f4c..9f2d3ded6 100644 +--- a/gio/gdbusauthmechanismsha1.c ++++ b/gio/gdbusauthmechanismsha1.c +@@ -1196,7 +1196,7 @@ mechanism_client_data_receive (GDBusAuthMechanism *mechanism, + GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); + gchar **tokens; + const gchar *cookie_context; +- guint cookie_id; ++ int64_t cookie_id; + const gchar *server_challenge; + gchar *client_challenge; + gchar *endp; +@@ -1231,7 +1231,7 @@ mechanism_client_data_receive (GDBusAuthMechanism *mechanism, + } + + cookie_id = g_ascii_strtoll (tokens[1], &endp, 10); +- if (*endp != '\0') ++ if (*endp != '\0' || endp == tokens[1] || cookie_id < 0 || cookie_id > UINT32_MAX) + { + g_free (m->priv->reject_reason); + m->priv->reject_reason = g_strdup_printf ("Malformed cookie_id '%s'", tokens[1]); +@@ -1241,7 +1241,7 @@ mechanism_client_data_receive (GDBusAuthMechanism *mechanism, + server_challenge = tokens[2]; + + error = NULL; +- cookie = keyring_lookup_entry (cookie_context, cookie_id, &error); ++ cookie = keyring_lookup_entry (cookie_context, (unsigned int) cookie_id, &error); + if (cookie == NULL) + { + g_free (m->priv->reject_reason); + +From 2b185a1b6ff0fca50e3bf0032e81a09a4dbfd550 Mon Sep 17 00:00:00 2001 +From: Philip Withnall +Date: Tue, 28 Apr 2026 15:51:00 +0100 +Subject: [PATCH 3/5] gdbusauthmechanism: Expose client reject reason as a new + vfunc + +We can do this because `gdbusauthmechanism.h` is a private header. + +Hook it up to the existing `reject_reason` code in each +`GDBusAuthMechanism` implementation, as all three implementations +currently intermingle reject reasons from the server and client code, so +there would currently be no benefit to having a separate server and +client implementation of `*_get_reject_reason()`. + +This new private API will be used in a new unit test in the following +commit. + +Signed-off-by: Philip Withnall +--- + gio/gdbusauthmechanism.c | 7 +++++++ + gio/gdbusauthmechanism.h | 2 ++ + gio/gdbusauthmechanismanon.c | 8 ++++---- + gio/gdbusauthmechanismexternal.c | 8 ++++---- + gio/gdbusauthmechanismsha1.c | 8 ++++---- + 5 files changed, 21 insertions(+), 12 deletions(-) + +diff --git a/gio/gdbusauthmechanism.c b/gio/gdbusauthmechanism.c +index 897d41496..6ed7fb546 100644 +--- a/gio/gdbusauthmechanism.c ++++ b/gio/gdbusauthmechanism.c +@@ -324,6 +324,13 @@ _g_dbus_auth_mechanism_client_data_send (GDBusAuthMechanism *mechanism, + return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_data_send (mechanism, out_data_len); + } + ++gchar * ++_g_dbus_auth_mechanism_client_get_reject_reason (GDBusAuthMechanism *mechanism) ++{ ++ g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL); ++ return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_get_reject_reason (mechanism); ++} ++ + void + _g_dbus_auth_mechanism_client_shutdown (GDBusAuthMechanism *mechanism) + { +diff --git a/gio/gdbusauthmechanism.h b/gio/gdbusauthmechanism.h +index cee87b0a2..5ab3e34c9 100644 +--- a/gio/gdbusauthmechanism.h ++++ b/gio/gdbusauthmechanism.h +@@ -97,6 +97,7 @@ struct _GDBusAuthMechanismClass + gsize data_len); + gchar *(*client_data_send) (GDBusAuthMechanism *mechanism, + gsize *out_data_len); ++ gchar *(*client_get_reject_reason) (GDBusAuthMechanism *mechanism); + void (*client_shutdown) (GDBusAuthMechanism *mechanism); + }; + +@@ -144,6 +145,7 @@ void _g_dbus_auth_mechanism_client_data_receive (GDBus + gsize data_len); + gchar *_g_dbus_auth_mechanism_client_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len); ++gchar *_g_dbus_auth_mechanism_client_get_reject_reason (GDBusAuthMechanism *mechanism); + void _g_dbus_auth_mechanism_client_shutdown (GDBusAuthMechanism *mechanism); + + +diff --git a/gio/gdbusauthmechanismanon.c b/gio/gdbusauthmechanismanon.c +index dd57826ff..ab947bba6 100644 +--- a/gio/gdbusauthmechanismanon.c ++++ b/gio/gdbusauthmechanismanon.c +@@ -54,7 +54,7 @@ static void mechanism_server_data_receive (GDBusAuthMe + gsize data_len); + static gchar *mechanism_server_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len); +-static gchar *mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism); ++static gchar *mechanism_server_or_client_get_reject_reason (GDBusAuthMechanism *mechanism); + static void mechanism_server_shutdown (GDBusAuthMechanism *mechanism); + static GDBusAuthMechanismState mechanism_client_get_state (GDBusAuthMechanism *mechanism); + static gchar *mechanism_client_initiate (GDBusAuthMechanism *mechanism, +@@ -100,12 +100,13 @@ _g_dbus_auth_mechanism_anon_class_init (GDBusAuthMechanismAnonClass *klass) + mechanism_class->server_initiate = mechanism_server_initiate; + mechanism_class->server_data_receive = mechanism_server_data_receive; + mechanism_class->server_data_send = mechanism_server_data_send; +- mechanism_class->server_get_reject_reason = mechanism_server_get_reject_reason; ++ mechanism_class->server_get_reject_reason = mechanism_server_or_client_get_reject_reason; + mechanism_class->server_shutdown = mechanism_server_shutdown; + mechanism_class->client_get_state = mechanism_client_get_state; + mechanism_class->client_initiate = mechanism_client_initiate; + mechanism_class->client_data_receive = mechanism_client_data_receive; + mechanism_class->client_data_send = mechanism_client_data_send; ++ mechanism_class->client_get_reject_reason = mechanism_server_or_client_get_reject_reason; + mechanism_class->client_shutdown = mechanism_client_shutdown; + } + +@@ -219,12 +220,11 @@ mechanism_server_data_send (GDBusAuthMechanism *mechanism, + } + + static gchar * +-mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism) ++mechanism_server_or_client_get_reject_reason (GDBusAuthMechanism *mechanism) + { + GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), NULL); +- g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL); + g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL); + + /* can never end up here because we are never in the REJECTED state */ +diff --git a/gio/gdbusauthmechanismexternal.c b/gio/gdbusauthmechanismexternal.c +index 182c57278..30adfc415 100644 +--- a/gio/gdbusauthmechanismexternal.c ++++ b/gio/gdbusauthmechanismexternal.c +@@ -57,7 +57,7 @@ static void mechanism_server_data_receive (GDBusAuthMe + gsize data_len); + static gchar *mechanism_server_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len); +-static gchar *mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism); ++static gchar *mechanism_server_or_client_get_reject_reason (GDBusAuthMechanism *mechanism); + static void mechanism_server_shutdown (GDBusAuthMechanism *mechanism); + static GDBusAuthMechanismState mechanism_client_get_state (GDBusAuthMechanism *mechanism); + static gchar *mechanism_client_initiate (GDBusAuthMechanism *mechanism, +@@ -103,12 +103,13 @@ _g_dbus_auth_mechanism_external_class_init (GDBusAuthMechanismExternalClass *kla + mechanism_class->server_initiate = mechanism_server_initiate; + mechanism_class->server_data_receive = mechanism_server_data_receive; + mechanism_class->server_data_send = mechanism_server_data_send; +- mechanism_class->server_get_reject_reason = mechanism_server_get_reject_reason; ++ mechanism_class->server_get_reject_reason = mechanism_server_or_client_get_reject_reason; + mechanism_class->server_shutdown = mechanism_server_shutdown; + mechanism_class->client_get_state = mechanism_client_get_state; + mechanism_class->client_initiate = mechanism_client_initiate; + mechanism_class->client_data_receive = mechanism_client_data_receive; + mechanism_class->client_data_send = mechanism_client_data_send; ++ mechanism_class->client_get_reject_reason = mechanism_server_or_client_get_reject_reason; + mechanism_class->client_shutdown = mechanism_client_shutdown; + } + +@@ -285,12 +286,11 @@ mechanism_server_data_send (GDBusAuthMechanism *mechanism, + } + + static gchar * +-mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism) ++mechanism_server_or_client_get_reject_reason (GDBusAuthMechanism *mechanism) + { + GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL); +- g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL); + g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL); + + /* can never end up here because we are never in the REJECTED state */ +diff --git a/gio/gdbusauthmechanismsha1.c b/gio/gdbusauthmechanismsha1.c +index 9f2d3ded6..d74f70387 100644 +--- a/gio/gdbusauthmechanismsha1.c ++++ b/gio/gdbusauthmechanismsha1.c +@@ -111,7 +111,7 @@ static void mechanism_server_data_receive (GDBusAuthMe + gsize data_len); + static gchar *mechanism_server_data_send (GDBusAuthMechanism *mechanism, + gsize *out_data_len); +-static gchar *mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism); ++static gchar *mechanism_server_or_client_get_reject_reason (GDBusAuthMechanism *mechanism); + static void mechanism_server_shutdown (GDBusAuthMechanism *mechanism); + static GDBusAuthMechanismState mechanism_client_get_state (GDBusAuthMechanism *mechanism); + static gchar *mechanism_client_initiate (GDBusAuthMechanism *mechanism, +@@ -163,12 +163,13 @@ _g_dbus_auth_mechanism_sha1_class_init (GDBusAuthMechanismSha1Class *klass) + mechanism_class->server_initiate = mechanism_server_initiate; + mechanism_class->server_data_receive = mechanism_server_data_receive; + mechanism_class->server_data_send = mechanism_server_data_send; +- mechanism_class->server_get_reject_reason = mechanism_server_get_reject_reason; ++ mechanism_class->server_get_reject_reason = mechanism_server_or_client_get_reject_reason; + mechanism_class->server_shutdown = mechanism_server_shutdown; + mechanism_class->client_get_state = mechanism_client_get_state; + mechanism_class->client_initiate = mechanism_client_initiate; + mechanism_class->client_data_receive = mechanism_client_data_receive; + mechanism_class->client_data_send = mechanism_client_data_send; ++ mechanism_class->client_get_reject_reason = mechanism_server_or_client_get_reject_reason; + mechanism_class->client_shutdown = mechanism_client_shutdown; + } + +@@ -1096,12 +1097,11 @@ mechanism_server_data_send (GDBusAuthMechanism *mechanism, + } + + static gchar * +-mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism) ++mechanism_server_or_client_get_reject_reason (GDBusAuthMechanism *mechanism) + { + GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); + + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL); +- g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL); + g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL); + + return g_strdup (m->priv->reject_reason); + +From 0c526648c0346fd7287941817641d235ddf4998a Mon Sep 17 00:00:00 2001 +From: Philip Withnall +Date: Tue, 28 Apr 2026 15:52:53 +0100 +Subject: [PATCH 4/5] tests: Add a unit test for GDBusAuthMechanismSha1 cookie + context parsing + +This checks for regressions in the fixes from the previous few commits. + +Signed-off-by: Philip Withnall +Helps: #3931 +--- + gio/gdbusauthmechanismsha1.c | 2 +- + gio/tests/gdbus-auth-mechanism-sha1.c | 177 ++++++++++++++++++++++++++ + gio/tests/meson.build | 1 + + 3 files changed, 179 insertions(+), 1 deletion(-) + create mode 100644 gio/tests/gdbus-auth-mechanism-sha1.c + +diff --git a/gio/gdbusauthmechanismsha1.c b/gio/gdbusauthmechanismsha1.c +index d74f70387..cb1a4f828 100644 +--- a/gio/gdbusauthmechanismsha1.c ++++ b/gio/gdbusauthmechanismsha1.c +@@ -1174,7 +1174,7 @@ validate_cookie_context (const char *cookie_context) + + for (i = 0; cookie_context[i] != '\0'; i++) + { +- if ((uint8_t) cookie_context[i] >= 128 || ++ if ((guint8) cookie_context[i] >= 128 || + cookie_context[i] == '/' || + cookie_context[i] == '\\' || + cookie_context[i] == ' ' || +diff --git a/gio/tests/gdbus-auth-mechanism-sha1.c b/gio/tests/gdbus-auth-mechanism-sha1.c +new file mode 100644 +index 000000000..abcdb4e3e +--- /dev/null ++++ b/gio/tests/gdbus-auth-mechanism-sha1.c +@@ -0,0 +1,177 @@ ++/* GLib testing framework examples and tests ++ * ++ * Copyright (C) 2026 Philip Withnall ++ * ++ * SPDX-License-Identifier: LGPL-2.1-or-later ++ * ++ * This library is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * This library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General ++ * Public License along with this library; if not, see . ++ * ++ * Author: Philip Withnall ++ */ ++ ++#include ++#include ++ ++#include ++#include ++ ++#include "gdbus-tests.h" ++ ++#ifdef G_OS_UNIX ++#include ++#include ++#include ++#include ++#endif ++ ++#define GIO_COMPILATION 1 ++#include "gdbusauthmechanism.h" ++#include "gdbusauthmechanismsha1.h" ++ ++/* Vfunc wrappers copied from gdbusauthmechanism.c as they are not public. */ ++static gboolean ++dbus_auth_mechanism_is_supported (GDBusAuthMechanism *mechanism) ++{ ++ return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->is_supported (mechanism); ++} ++ ++static GDBusAuthMechanismState ++dbus_auth_mechanism_client_get_state (GDBusAuthMechanism *mechanism) ++{ ++ return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_get_state (mechanism); ++} ++ ++static gchar * ++dbus_auth_mechanism_client_initiate (GDBusAuthMechanism *mechanism, ++ GDBusConnectionFlags conn_flags, ++ size_t *out_initial_response_len) ++{ ++ return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_initiate (mechanism, ++ conn_flags, ++ out_initial_response_len); ++} ++ ++static void ++dbus_auth_mechanism_client_data_receive (GDBusAuthMechanism *mechanism, ++ const char *data, ++ size_t data_len) ++{ ++ G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_data_receive (mechanism, data, data_len); ++} ++ ++static char * ++dbus_auth_mechanism_client_get_reject_reason (GDBusAuthMechanism *mechanism) ++{ ++ return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_get_reject_reason (mechanism); ++} ++ ++static void ++dbus_auth_mechanism_client_shutdown (GDBusAuthMechanism *mechanism) ++{ ++ G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_shutdown (mechanism); ++} ++ ++static void ++test_server_challenge_validation (void) ++{ ++ const struct ++ { ++ const char *server_challenge; ++ const char *expected_reject_reason_prefix; ++ } ++ vectors[] = { ++ { "valid_context 123 456", "Problems looking up entry in keyring" }, ++ { "invalid/context 123 456", "Malformed cookie_context" }, ++ { "invalid.context 123 456", "Malformed cookie_context" }, ++ { " 123 456", "Malformed cookie_context" }, ++ { "😀 123 456", "Malformed cookie_context" }, ++ { "invalid\ncontext 123 456", "Malformed cookie_context" }, ++ { "invalid\rcontext 123 456", "Malformed cookie_context" }, ++ { "invalid\tcontext 123 456", "Malformed cookie_context" }, ++ { "invalid\\context 123 456", "Malformed cookie_context" }, ++ { "valid_context 456", "Malformed cookie_id" }, ++ { "valid_context 123notanumber 456", "Malformed cookie_id" }, ++ { "valid_context -1 456", "Malformed cookie_id" }, ++ { "valid_context 4294967296 456", "Malformed cookie_id" }, ++ { "valid_context 123 ", "Malformed data" }, ++ { "valid_context ", "Malformed data" }, ++ }; ++ GType mechanism_type; ++ GDBusConnection *connection = NULL; ++ ++ g_test_summary ("Test that GDBusAuthMechanismSha1 rejects various malformed server data lines"); ++ ++ /* Briefly connect to the actual bus to ensure the GDBusAuth mechanisms are ++ * all registered. */ ++ session_bus_up (); ++ ++ connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); ++ g_assert_nonnull (connection); ++ g_clear_object (&connection); ++ ++ session_bus_down (); ++ ++ /* Check that we now have the type ID for GDBusAuthMechanismSha1 */ ++ mechanism_type = g_type_from_name ("GDBusAuthMechanismSha1"); ++ g_assert_cmpint (mechanism_type, !=, 0); ++ ++ for (size_t i = 0; i < G_N_ELEMENTS (vectors); i++) ++ { ++ GDBusAuthMechanism *mechanism = NULL; ++ char *data = NULL; ++ size_t data_len = 0; ++ char *reject_reason = NULL; ++ ++ mechanism = g_object_new (mechanism_type, NULL); ++ ++ if (!dbus_auth_mechanism_is_supported (mechanism)) ++ { ++ g_test_skip ("Mechanism not supported"); ++ g_clear_object (&mechanism); ++ return; ++ } ++ ++ data = dbus_auth_mechanism_client_initiate (mechanism, ++ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, ++ &data_len); ++ g_free (data); ++ ++ dbus_auth_mechanism_client_data_receive (mechanism, vectors[i].server_challenge, strlen (vectors[i].server_challenge)); ++ ++ g_assert_cmpint (dbus_auth_mechanism_client_get_state (mechanism), ==, G_DBUS_AUTH_MECHANISM_STATE_REJECTED); ++ ++ reject_reason = dbus_auth_mechanism_client_get_reject_reason (mechanism); ++ g_assert_true (g_str_has_prefix (reject_reason, vectors[i].expected_reject_reason_prefix)); ++ g_free (reject_reason); ++ ++ dbus_auth_mechanism_client_shutdown (mechanism); ++ ++ g_clear_object (&mechanism); ++ } ++} ++ ++int ++main (int argc, ++ char *argv[]) ++{ ++ setlocale (LC_ALL, "C"); ++ ++ g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); ++ ++ g_test_dbus_unset (); ++ ++ g_test_add_func ("/gdbus/auth-mechanism-sha1/server-challenge-validation", test_server_challenge_validation); ++ ++ return g_test_run (); ++} +diff --git a/gio/tests/meson.build b/gio/tests/meson.build +index 5dbfb8e60..c5380c16a 100644 +--- a/gio/tests/meson.build ++++ b/gio/tests/meson.build +@@ -294,6 +294,7 @@ if host_machine.system() != 'windows' + 'suite' : ['slow'], + }, + 'gdbus-auth' : {'extra_sources' : extra_sources}, ++ 'gdbus-auth-mechanism-sha1': {'extra_sources' : extra_sources}, + 'gdbus-bz627724' : {'extra_sources' : extra_sources}, + 'gdbus-close-pending' : {'extra_sources' : extra_sources}, + 'gdbus-connection' : {'extra_sources' : extra_sources}, + +From 8c11c462af6ca9338e95ed3e4ce78d59df1c7474 Mon Sep 17 00:00:00 2001 +From: RHEL Packaging Agent +Date: Mon, 20 Jul 2026 07:10:32 +0000 +Subject: [PATCH 5/5] Fix: use GLib types instead of stdint.h types for + portability + +Replace int64_t with gint64 and UINT32_MAX with G_MAXUINT32 to avoid +dependency on which may not be included in older GLib builds. +--- + gio/gdbusauthmechanismsha1.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/gio/gdbusauthmechanismsha1.c b/gio/gdbusauthmechanismsha1.c +index cb1a4f828..007c6b901 100644 +--- a/gio/gdbusauthmechanismsha1.c ++++ b/gio/gdbusauthmechanismsha1.c +@@ -1196,7 +1196,7 @@ mechanism_client_data_receive (GDBusAuthMechanism *mechanism, + GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); + gchar **tokens; + const gchar *cookie_context; +- int64_t cookie_id; ++ gint64 cookie_id; + const gchar *server_challenge; + gchar *client_challenge; + gchar *endp; +@@ -1231,7 +1231,7 @@ mechanism_client_data_receive (GDBusAuthMechanism *mechanism, + } + + cookie_id = g_ascii_strtoll (tokens[1], &endp, 10); +- if (*endp != '\0' || endp == tokens[1] || cookie_id < 0 || cookie_id > UINT32_MAX) ++ if (*endp != '\0' || endp == tokens[1] || cookie_id < 0 || cookie_id > G_MAXUINT32) + { + g_free (m->priv->reject_reason); + m->priv->reject_reason = g_strdup_printf ("Malformed cookie_id '%s'", tokens[1]); diff --git a/SOURCES/mingw-glib2-2.70.1-CVE-2026-58016.patch b/SOURCES/mingw-glib2-2.70.1-CVE-2026-58016.patch new file mode 100644 index 0000000..c045ed5 --- /dev/null +++ b/SOURCES/mingw-glib2-2.70.1-CVE-2026-58016.patch @@ -0,0 +1,87 @@ +From 0cf6b5ef06f44a4b259a014870cdc02780cc8ba4 Mon Sep 17 00:00:00 2001 +From: Philip Withnall +Date: Thu, 16 Apr 2026 15:27:37 +0100 +Subject: [PATCH] gdbusintrospection: Fix XML parser state handling for + element nesting + +The check for whether a `` element in D-Bus introspection XML was +nested correctly was broken. `` elements can only be at the top +level, or nested immediately within another `` element. + +Fix the check and add some unit tests for it. + +Spotted by linhlhq as #YWH-PGM9867-204. The fix is mine, and the unit test +uses example XML strings adapted from their report. + +Signed-off-by: Philip Withnall + +Fixes: #3932 +--- + gio/gdbusintrospection.c | 2 +- + gio/tests/gdbus-introspection.c | 33 +++++++++++++++++++++++++++++++++ + 2 files changed, 34 insertions(+), 1 deletion(-) + +diff --git a/gio/gdbusintrospection.c b/gio/gdbusintrospection.c +index d6aa445d5..1e0cb81de 100644 +--- a/gio/gdbusintrospection.c ++++ b/gio/gdbusintrospection.c +@@ -1270,7 +1270,7 @@ parser_start_element (GMarkupParseContext *context, + /* ---------------------------------------------------------------------------------------------------- */ + if (strcmp (element_name, "node") == 0) + { +- if (!(g_slist_length (stack) >= 1 || strcmp (stack->next->data, "node") != 0)) ++ if (stack->next != NULL && strcmp (stack->next->data, "node") != 0) + { + g_set_error_literal (error, + G_MARKUP_ERROR, +diff --git a/gio/tests/gdbus-introspection.c b/gio/tests/gdbus-introspection.c +index 50c0cc721..e2cdf0a25 100644 +--- a/gio/tests/gdbus-introspection.c ++++ b/gio/tests/gdbus-introspection.c +@@ -297,6 +297,38 @@ test_extra_data (void) + g_dbus_node_info_unref (info); + } + ++static void ++test_invalid (void) ++{ ++ const struct ++ { ++ const char *xml; ++ GMarkupError expected_error_code; ++ } ++ vectors[] = ++ { ++ { "", G_MARKUP_ERROR_EMPTY }, ++ { "", G_MARKUP_ERROR_INVALID_CONTENT }, ++ { "", G_MARKUP_ERROR_INVALID_CONTENT }, ++ { "", G_MARKUP_ERROR_INVALID_CONTENT }, ++ { "", G_MARKUP_ERROR_INVALID_CONTENT }, ++ }; ++ ++ for (size_t i = 0; i < G_N_ELEMENTS (vectors); i++) ++ { ++ GDBusNodeInfo *node; ++ GError *local_error = NULL; ++ ++ g_test_message ("Testing parsing of %s gives an error", vectors[i].xml); ++ ++ node = g_dbus_node_info_new_for_xml (vectors[i].xml, &local_error); ++ g_assert_error (local_error, G_MARKUP_ERROR, (int) vectors[i].expected_error_code); ++ g_assert_null (node); ++ ++ g_clear_error (&local_error); ++ } ++} ++ + /* ---------------------------------------------------------------------------------------------------- */ + + int +@@ -314,6 +346,7 @@ main (int argc, + g_test_add_func ("/gdbus/introspection-generate", test_generate); + g_test_add_func ("/gdbus/introspection-default-direction", test_default_direction); + g_test_add_func ("/gdbus/introspection-extra-data", test_extra_data); ++ g_test_add_func ("/gdbus/introspection-invalid", test_invalid); + + ret = session_bus_run (); + diff --git a/SPECS/mingw-glib2.spec b/SPECS/mingw-glib2.spec index e500b2a..b3564cb 100644 --- a/SPECS/mingw-glib2.spec +++ b/SPECS/mingw-glib2.spec @@ -5,7 +5,7 @@ Name: mingw-glib2 Version: 2.70.1 -Release: 1%{?dist} +Release: 9%{?dist} Summary: MinGW Windows GLib2 library License: LGPLv2+ @@ -55,6 +55,31 @@ Patch1: 0001-Use-CreateFile-on-Win32-to-make-sure-g_unlink-always.patch # https://bugzilla.gnome.org/show_bug.cgi?id=698118 Patch2: glib-prefer-constructors-over-DllMain.patch +# https://gitlab.gnome.org/GNOME/glib/-/merge_requests/5172 +Patch3: mingw-glib2-2.70.1-CVE-2026-58015.patch + +# CVE-2026-58014: one-byte heap under-read with g_key_file_get_locale_string_list() +# https://gitlab.gnome.org/GNOME/glib/-/merge_requests/5171 +Patch4: mingw-glib2-2.70.1-CVE-2026-58014.patch + +# https://github.com/GNOME/glib/commit/c9da977c178f +Patch5: mingw-glib2-2.70.1-CVE-2026-58016.patch + +# https://github.com/GNOME/glib/commit/31f82e22e21bae520b7228f7f57d357fb20df8a4 +Patch6: mingw-glib2-2.70.1-CVE-2025-14087.patch + +# https://gitlab.gnome.org/GNOME/glib/-/issues/3918 +Patch7: mingw-glib2-2.70.1-CVE-2026-58012.patch + +# https://gitlab.gnome.org/GNOME/glib/-/work_items/3925 +Patch8: mingw-glib2-2.70.1-CVE-2026-58013.patch + +# https://gitlab.gnome.org/GNOME/glib/-/issues/3917 +Patch9: mingw-glib2-2.70.1-CVE-2026-58011.patch + +# https://gitlab.gnome.org/GNOME/glib/-/issues/3915 +Patch10: mingw-glib2-2.70.1-CVE-2026-58010.patch + %description MinGW Windows Glib2 library. @@ -102,6 +127,14 @@ Static version of the MinGW Windows GLib2 library. %setup -q -n glib-%{version} %patch1 -p1 %patch2 -p1 +%patch3 -p1 +%patch4 -p1 +%patch5 -p1 +%patch6 -p1 +%patch7 -p1 +%patch8 -p1 +%patch9 -p1 +%patch10 -p1 %build %mingw_meson --default-library=both \ @@ -279,6 +312,38 @@ find $RPM_BUILD_ROOT -name "*.la" -delete %changelog +* Wed Jul 22 2026 RHEL Packaging Agent - 2.70.1-9 +- Fix CVE-2026-58010: off-by-one error in gvs_tuple_is_normal() + Resolves: RHEL-212164 + +* Wed Jul 22 2026 RHEL Packaging Agent - 2.70.1-8 +- Fix CVE-2026-58011: g_date_time_add_full() range validation + Resolves: RHEL-212184 + +* Wed Jul 22 2026 RHEL Packaging Agent - 2.70.1-7 +- Fix CVE-2026-58013: memcmp buffer over-read in giochannel + Resolves: RHEL-212234 + +* Wed Jul 22 2026 RHEL Packaging Agent - 2.70.1-6 +- Fix CVE-2026-58012: buffer overflow in gregex substitutions + Resolves: RHEL-212200 + +* Wed Jul 22 2026 RHEL Packaging Agent - 2.70.1-5 +- Fix CVE-2025-14087: integer overflow in GVariant parser + Resolves: RHEL-154707 + +* Wed Jul 22 2026 RHEL Packaging Agent - 2.70.1-4 +- Fix CVE-2026-58016: D-Bus introspection XML node nesting check + Resolves: RHEL-190617 + +* Wed Jul 22 2026 RHEL Packaging Agent - 2.70.1-3 +- Fix CVE-2026-58014: one-byte heap under-read in mingw-glib2 + Resolves: RHEL-190609 + +* Mon Jul 20 2026 RHEL Packaging Agent - 2.70.1-2 +- Fix CVE-2026-58015: D-Bus cookie context path traversal + Resolves: RHEL-212246 + * Thu Dec 23 2021 Yan Vugenfirer 2.70.1-1 - Update to 2.70.1 - Resolves: rhbz#2034959