Package: release.debian.org
Severity: normal
Tags: bookworm
X-Debbugs-Cc: [email protected]
Control: affects -1 + src:glib2.0
User: [email protected]
Usertags: pu

[ Reason ]
A number of security issues has been identified in glib (src:glib2.0).
As part of the Debian LTS efforts, I've taken responsibility to address
these in bullseye (LTS) and while doing so I've offered the Debian Gnome
Team to also handle these issues in stable (trixie) and old-stable
(bookworm) which was on their todo list.
While discussing this with Debian Gnome Team they also asked to adress
#1119919 which is a timezone parsing issue, which I thus also included
the patch for.

[ Impact ]
This update mainly addresses security issues that is on the Debian LTS
Security Team radar. It includes an additional fix for a timezone
parsing issue, as requested by the Debian Gnome Team.

[ Tests ]
The shipped tests in the package passes. Including newly added
tests in CVE-2026-1489-4.patch and CVE-2026-0988.patch

[ Risks ]
All changes has already been shipped in unstable/testing without issues.
The fixes have been cherry-picked from upstream.
The risk of regressions in this update should be low.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

[ Changes ]
- #1119919 : timezone parsing fix
- #1125752 : CVE-2026-0988
- #1126551 : CVE-2026-1484
- #1126550 : CVE-2026-1485
- #1126549 : CVE-2026-1489

See also debian/changelog and security-tracker.debian.org

[ Other info ]
I have also filed a similar issue for SPU (trixie) with the same
patches.

Regards,
Andreas Henriksson
diff -Nru glib2.0-2.74.6/debian/changelog glib2.0-2.74.6/debian/changelog
--- glib2.0-2.74.6/debian/changelog     2025-12-15 15:29:38.000000000 +0100
+++ glib2.0-2.74.6/debian/changelog     2026-02-13 12:35:33.000000000 +0100
@@ -1,3 +1,19 @@
+glib2.0 (2.74.6-2+deb12u9) bookworm; urgency=medium
+
+  * Non-maintainer upload by the LTS Security Team.
+  * Add patch to fix timezone handling with Debian & Ubuntu's symlinks
+    (Closes: #1119919) (LP: #2130378)
+  * CVE-2026-0988: Missing input validation in g_buffered_input_stream_peek
+    (Closes: #1125752)
+  * CVE-2026-1484: Integer overflow in base64 encoding can cause memory 
corruption.
+    (Closes: #1126551)
+  * CVE-2026-1485: Buffer underflow vulnerability in content type parsing
+    caused by (signed) integer wrap for large inputs. (Closes: #1126550)
+  * CVE-2026-1489: Integer overflow in unicode conversion
+    can lead to memory corruption. (Closes: #1126549)
+
+ -- Andreas Henriksson <[email protected]>  Fri, 13 Feb 2026 12:35:33 +0100
+
 glib2.0 (2.74.6-2+deb12u8) bookworm; urgency=medium
 
   * Team upload.
diff -Nru glib2.0-2.74.6/debian/patches/CVE-2026-0988.patch 
glib2.0-2.74.6/debian/patches/CVE-2026-0988.patch
--- glib2.0-2.74.6/debian/patches/CVE-2026-0988.patch   1970-01-01 
01:00:00.000000000 +0100
+++ glib2.0-2.74.6/debian/patches/CVE-2026-0988.patch   2026-02-13 
12:35:33.000000000 +0100
@@ -0,0 +1,53 @@
+From: Philip Withnall <[email protected]>
+Date: Thu, 18 Dec 2025 23:12:18 +0000
+Subject: gbufferedinputstream: Fix a potential integer overflow in peek()
+
+If the caller provides `offset` and `count` arguments which overflow,
+their sum will overflow and could lead to `memcpy()` reading out more
+memory than expected.
+
+Spotted by Codean Labs.
+
+Signed-off-by: Philip Withnall <[email protected]>
+
+Fixes: #3851
+(cherry picked from commit c5766cff61ffce0b8e787eae09908ac348338e5f)
+---
+ gio/gbufferedinputstream.c        |  2 +-
+ gio/tests/buffered-input-stream.c | 10 ++++++++++
+ 2 files changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/gio/gbufferedinputstream.c b/gio/gbufferedinputstream.c
+index 55450ce..56f5ece 100644
+--- a/gio/gbufferedinputstream.c
++++ b/gio/gbufferedinputstream.c
+@@ -590,7 +590,7 @@ g_buffered_input_stream_peek (GBufferedInputStream *stream,
+ 
+   available = g_buffered_input_stream_get_available (stream);
+ 
+-  if (offset > available)
++  if (offset > available || offset > G_MAXSIZE - count)
+     return 0;
+ 
+   end = MIN (offset + count, available);
+diff --git a/gio/tests/buffered-input-stream.c 
b/gio/tests/buffered-input-stream.c
+index ee084b3..39b4daf 100644
+--- a/gio/tests/buffered-input-stream.c
++++ b/gio/tests/buffered-input-stream.c
+@@ -58,6 +58,16 @@ test_peek (void)
+   g_assert_cmpint (npeek, ==, 0);
+   g_free (buffer);
+ 
++  buffer = g_new0 (char, 64);
++  npeek = g_buffered_input_stream_peek (G_BUFFERED_INPUT_STREAM (in), buffer, 
8, 0);
++  g_assert_cmpint (npeek, ==, 0);
++  g_free (buffer);
++
++  buffer = g_new0 (char, 64);
++  npeek = g_buffered_input_stream_peek (G_BUFFERED_INPUT_STREAM (in), buffer, 
5, G_MAXSIZE);
++  g_assert_cmpint (npeek, ==, 0);
++  g_free (buffer);
++
+   g_object_unref (in);
+   g_object_unref (base);
+ }
diff -Nru glib2.0-2.74.6/debian/patches/CVE-2026-1484-1.patch 
glib2.0-2.74.6/debian/patches/CVE-2026-1484-1.patch
--- glib2.0-2.74.6/debian/patches/CVE-2026-1484-1.patch 1970-01-01 
01:00:00.000000000 +0100
+++ glib2.0-2.74.6/debian/patches/CVE-2026-1484-1.patch 2026-02-13 
12:35:33.000000000 +0100
@@ -0,0 +1,43 @@
+From: Marco Trevisan <[email protected]>
+Date: Fri, 23 Jan 2026 18:48:30 +0100
+Subject: gbase64: Use gsize to prevent potential overflow
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+
+Both g_base64_encode_step() and g_base64_encode_close() return gsize
+values, but these are summed to an int value.
+
+If the sum of these returned values is bigger than MAXINT, we overflow
+while doing the null byte write.
+
+Spotted by treeplus.
+Thanks to the Sovereign Tech Resilience programme from the Sovereign
+Tech Agency.
+
+ID: #YWH-PGM9867-168
+Closes: #3870
+
+(cherry picked from commit 6845f7776982849a2be1d8c9b0495e389092bff2)
+
+Co-authored-by: Marco Trevisan (Treviño) <[email protected]>
+(cherry picked from commit 5ba0ed9ab2c28294713bdc56a8744ff0a446b59c)
+---
+ glib/gbase64.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/glib/gbase64.c b/glib/gbase64.c
+index 3c427f8..60c8560 100644
+--- a/glib/gbase64.c
++++ b/glib/gbase64.c
+@@ -264,8 +264,9 @@ g_base64_encode (const guchar *data,
+                  gsize         len)
+ {
+   gchar *out;
+-  gint state = 0, outlen;
++  gint state = 0;
+   gint save = 0;
++  gsize outlen;
+ 
+   g_return_val_if_fail (data != NULL || len == 0, NULL);
+ 
diff -Nru glib2.0-2.74.6/debian/patches/CVE-2026-1484-2.patch 
glib2.0-2.74.6/debian/patches/CVE-2026-1484-2.patch
--- glib2.0-2.74.6/debian/patches/CVE-2026-1484-2.patch 1970-01-01 
01:00:00.000000000 +0100
+++ glib2.0-2.74.6/debian/patches/CVE-2026-1484-2.patch 2026-02-13 
12:35:33.000000000 +0100
@@ -0,0 +1,42 @@
+From: =?utf-8?b?Ik1hcmNvIFRyZXZpc2FuIChUcmV2acOxbyki?= <[email protected]>
+Date: Wed, 21 Jan 2026 20:09:44 +0100
+Subject: gbase64: Ensure that the out value is within allocated size
+
+We do not want to deference or write to it
+
+Related to: #3870
+
+(cherry picked from commit 25429bd0b22222d6986d000d62b44eebf490837d)
+---
+ glib/gbase64.c | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/glib/gbase64.c b/glib/gbase64.c
+index 60c8560..0827e83 100644
+--- a/glib/gbase64.c
++++ b/glib/gbase64.c
+@@ -267,6 +267,7 @@ g_base64_encode (const guchar *data,
+   gint state = 0;
+   gint save = 0;
+   gsize outlen;
++  gsize allocsize;
+ 
+   g_return_val_if_fail (data != NULL || len == 0, NULL);
+ 
+@@ -274,10 +275,15 @@ g_base64_encode (const guchar *data,
+      +1 is needed for trailing \0, also check for unlikely integer overflow */
+   g_return_val_if_fail (len < ((G_MAXSIZE - 1) / 4 - 1) * 3, NULL);
+ 
+-  out = g_malloc ((len / 3 + 1) * 4 + 1);
++  allocsize = (len / 3 + 1) * 4 + 1;
++  out = g_malloc (allocsize);
+ 
+   outlen = g_base64_encode_step (data, len, FALSE, out, &state, &save);
++  g_assert (outlen <= allocsize);
++
+   outlen += g_base64_encode_close (FALSE, out + outlen, &state, &save);
++  g_assert (outlen <= allocsize);
++
+   out[outlen] = '\0';
+ 
+   return (gchar *) out;
diff -Nru glib2.0-2.74.6/debian/patches/CVE-2026-1485.patch 
glib2.0-2.74.6/debian/patches/CVE-2026-1485.patch
--- glib2.0-2.74.6/debian/patches/CVE-2026-1485.patch   1970-01-01 
01:00:00.000000000 +0100
+++ glib2.0-2.74.6/debian/patches/CVE-2026-1485.patch   2026-02-13 
12:35:33.000000000 +0100
@@ -0,0 +1,39 @@
+From: Marco Trevisan <[email protected]>
+Date: Fri, 23 Jan 2026 19:05:44 +0100
+Subject: gio/gcontenttype-fdo: Do not overflow if header is longer than
+ MAXINT
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+
+In case the header size is longer than MAXINT we may read and write to
+invalid locations
+
+Spotted by treeplus.
+Thanks to the Sovereign Tech Resilience programme from the Sovereign
+Tech Agency.
+
+ID: #YWH-PGM9867-169
+Closes: #3871
+
+(cherry picked from commit aacda5b07141b944408c79e83bcbed3b2e1e6e45)
+
+Co-authored-by: Marco Trevisan (Treviño) <[email protected]>
+(cherry picked from commit ee5acb2cefc643450509374da2600cd3bf49a109)
+---
+ gio/gcontenttype.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/gio/gcontenttype.c b/gio/gcontenttype.c
+index 1e21bbd..6cf9157 100644
+--- a/gio/gcontenttype.c
++++ b/gio/gcontenttype.c
+@@ -1021,7 +1021,7 @@ tree_match_free (TreeMatch *match)
+ static TreeMatch *
+ parse_header (gchar *line)
+ {
+-  gint len;
++  size_t len;
+   gchar *s;
+   TreeMatch *match;
+ 
diff -Nru glib2.0-2.74.6/debian/patches/CVE-2026-1489-1.patch 
glib2.0-2.74.6/debian/patches/CVE-2026-1489-1.patch
--- glib2.0-2.74.6/debian/patches/CVE-2026-1489-1.patch 1970-01-01 
01:00:00.000000000 +0100
+++ glib2.0-2.74.6/debian/patches/CVE-2026-1489-1.patch 2026-02-13 
12:35:33.000000000 +0100
@@ -0,0 +1,38 @@
+From: =?utf-8?b?Ik1hcmNvIFRyZXZpc2FuIChUcmV2acOxbyki?= <[email protected]>
+Date: Wed, 21 Jan 2026 22:00:17 +0100
+Subject: guniprop: Use size_t for output_marks length
+
+The input string length may overflow, and this would lead to wrong
+behavior and invalid writes.
+
+Spotted by treeplus.
+Thanks to the Sovereign Tech Resilience programme from the Sovereign
+Tech Agency.
+
+ID: #YWH-PGM9867-171
+Closes: #3872
+(cherry picked from commit 662aa569efa65eaa4672ab0671eb8533a354cd89)
+---
+ glib/guniprop.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/glib/guniprop.c b/glib/guniprop.c
+index d1363e5..d022abc 100644
+--- a/glib/guniprop.c
++++ b/glib/guniprop.c
+@@ -772,13 +772,13 @@ get_locale_type (void)
+   return LOCALE_NORMAL;
+ }
+ 
+-static gint
++static size_t
+ output_marks (const char **p_inout,
+             char        *out_buffer,
+             gboolean     remove_dot)
+ {
+   const char *p = *p_inout;
+-  gint len = 0;
++  size_t len = 0;
+   
+   while (*p)
+     {
diff -Nru glib2.0-2.74.6/debian/patches/CVE-2026-1489-2.patch 
glib2.0-2.74.6/debian/patches/CVE-2026-1489-2.patch
--- glib2.0-2.74.6/debian/patches/CVE-2026-1489-2.patch 1970-01-01 
01:00:00.000000000 +0100
+++ glib2.0-2.74.6/debian/patches/CVE-2026-1489-2.patch 2026-02-13 
12:35:33.000000000 +0100
@@ -0,0 +1,27 @@
+From: =?utf-8?b?Ik1hcmNvIFRyZXZpc2FuIChUcmV2acOxbyki?= <[email protected]>
+Date: Wed, 21 Jan 2026 22:01:49 +0100
+Subject: guniprop: Do not convert size_t to gint
+
+We were correctly using size_t in output_special_case() since commit
+362f92b69, but then we converted the value back to int
+
+Related to: #3872
+
+(cherry picked from commit 58356619525a1d565df8cc348e9784716f020f2f)
+---
+ glib/guniprop.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/glib/guniprop.c b/glib/guniprop.c
+index d022abc..7ed770d 100644
+--- a/glib/guniprop.c
++++ b/glib/guniprop.c
+@@ -798,7 +798,7 @@ output_marks (const char **p_inout,
+   return len;
+ }
+ 
+-static gint
++static size_t
+ output_special_case (gchar *out_buffer,
+                    int    offset,
+                    int    type,
diff -Nru glib2.0-2.74.6/debian/patches/CVE-2026-1489-3.patch 
glib2.0-2.74.6/debian/patches/CVE-2026-1489-3.patch
--- glib2.0-2.74.6/debian/patches/CVE-2026-1489-3.patch 1970-01-01 
01:00:00.000000000 +0100
+++ glib2.0-2.74.6/debian/patches/CVE-2026-1489-3.patch 2026-02-13 
12:35:33.000000000 +0100
@@ -0,0 +1,286 @@
+From: =?utf-8?b?Ik1hcmNvIFRyZXZpc2FuIChUcmV2acOxbyki?= <[email protected]>
+Date: Wed, 21 Jan 2026 22:04:22 +0100
+Subject: guniprop: Ensure we do not overflow size in
+ g_utf8_{strdown,gstrup}()
+
+While this is technically not a security issue, when repeatedly adding
+to a size_t value, we can overflow and start from 0.
+
+Now, while being unlikely, technically an utf8 lower or upper string can
+have a longer size than the input value, and if the output string is
+bigger than G_MAXSIZE we'd end up cutting it silently.
+
+Let's instead assert each time we increase the output length
+
+(cherry picked from commit 170dc8c4068db4c4cbf63c7d27192e230436da21)
+---
+ glib/guniprop.c | 107 ++++++++++++++++++++++++++++++++++++--------------------
+ 1 file changed, 69 insertions(+), 38 deletions(-)
+
+diff --git a/glib/guniprop.c b/glib/guniprop.c
+index 7ed770d..f43979a 100644
+--- a/glib/guniprop.c
++++ b/glib/guniprop.c
+@@ -772,14 +772,36 @@ get_locale_type (void)
+   return LOCALE_NORMAL;
+ }
+ 
+-static size_t
+-output_marks (const char **p_inout,
+-            char        *out_buffer,
+-            gboolean     remove_dot)
++G_ALWAYS_INLINE static inline void
++increase_size (size_t *sizeptr, size_t add)
++{
++  g_assert (G_MAXSIZE - *(sizeptr) >= add);
++  *(sizeptr) += add;
++}
++
++G_ALWAYS_INLINE static inline void
++append_utf8_char_to_buffer (gunichar  c,
++                            char     *out_buffer,
++                            size_t   *in_out_len)
++{
++  gint utf8_len;
++  char *buffer;
++
++  buffer = out_buffer ? out_buffer + *(in_out_len) : NULL;
++  utf8_len = g_unichar_to_utf8 (c, buffer);
++
++  g_assert (utf8_len >= 0);
++  increase_size (in_out_len, utf8_len);
++}
++
++static void
++append_mark (const char **p_inout,
++             char        *out_buffer,
++             size_t      *in_out_len,
++             gboolean     remove_dot)
+ {
+   const char *p = *p_inout;
+-  size_t len = 0;
+-  
++
+   while (*p)
+     {
+       gunichar c = g_utf8_get_char (p);
+@@ -787,7 +809,7 @@ output_marks (const char **p_inout,
+       if (ISMARK (TYPE (c)))
+       {
+         if (!remove_dot || c != 0x307 /* COMBINING DOT ABOVE */)
+-          len += g_unichar_to_utf8 (c, out_buffer ? out_buffer + len : NULL);
++            append_utf8_char_to_buffer (c, out_buffer, in_out_len);
+         p = g_utf8_next_char (p);
+       }
+       else
+@@ -795,14 +817,14 @@ output_marks (const char **p_inout,
+     }
+ 
+   *p_inout = p;
+-  return len;
+ }
+ 
+-static size_t
+-output_special_case (gchar *out_buffer,
+-                   int    offset,
+-                   int    type,
+-                   int    which)
++static void
++append_special_case (char   *out_buffer,
++                     size_t *in_out_len,
++                     int     offset,
++                     int     type,
++                     int     which)
+ {
+   const gchar *p = special_case_table + offset;
+   gint len;
+@@ -814,10 +836,12 @@ output_special_case (gchar *out_buffer,
+     p += strlen (p) + 1;
+ 
+   len = strlen (p);
++  g_assert (len < G_MAXSIZE - *in_out_len);
++
+   if (out_buffer)
+-    memcpy (out_buffer, p, len);
++    memcpy (out_buffer + *in_out_len, p, len);
+ 
+-  return len;
++  increase_size (in_out_len, len);
+ }
+ 
+ static gsize
+@@ -858,11 +882,13 @@ real_toupper (const gchar *str,
+                 decomp_len = g_unichar_fully_decompose (c, FALSE, decomp, 
G_N_ELEMENTS (decomp));
+                 for (i=0; i < decomp_len; i++)
+                   {
++
+                     if (decomp[i] != 0x307 /* COMBINING DOT ABOVE */)
+-                      len += g_unichar_to_utf8 (g_unichar_toupper 
(decomp[i]), out_buffer ? out_buffer + len : NULL);
++                        append_utf8_char_to_buffer (g_unichar_toupper 
(decomp[i]),
++                                                    out_buffer, &len);
+                   }
+-                
+-                len += output_marks (&p, out_buffer ? out_buffer + len : 
NULL, TRUE);
++
++                  append_mark (&p, out_buffer, &len, TRUE);
+ 
+                 continue;
+               }
+@@ -875,17 +901,17 @@ real_toupper (const gchar *str,
+       if (locale_type == LOCALE_TURKIC && c == 'i')
+       {
+         /* i => LATIN CAPITAL LETTER I WITH DOT ABOVE */
+-        len += g_unichar_to_utf8 (0x130, out_buffer ? out_buffer + len : 
NULL); 
++          append_utf8_char_to_buffer (0x130, out_buffer, &len);
+       }
+       else if (c == 0x0345)   /* COMBINING GREEK YPOGEGRAMMENI */
+       {
+         /* Nasty, need to move it after other combining marks .. this would 
go away if
+          * we normalized first.
+          */
+-        len += output_marks (&p, out_buffer ? out_buffer + len : NULL, FALSE);
++          append_mark (&p, out_buffer, &len, TRUE);
+ 
+         /* And output as GREEK CAPITAL LETTER IOTA */
+-        len += g_unichar_to_utf8 (0x399, out_buffer ? out_buffer + len : 
NULL);         
++          append_utf8_char_to_buffer (0x399, out_buffer, &len);
+       }
+       else if (IS (t,
+                  OR (G_UNICODE_LOWERCASE_LETTER,
+@@ -896,8 +922,8 @@ real_toupper (const gchar *str,
+ 
+         if (val >= 0x1000000)
+           {
+-            len += output_special_case (out_buffer ? out_buffer + len : NULL, 
val - 0x1000000, t,
+-                                        t == G_UNICODE_LOWERCASE_LETTER ? 0 : 
1);
++              append_special_case (out_buffer, &len, val - 0x1000000, t,
++                                   t == G_UNICODE_LOWERCASE_LETTER ? 0 : 1);
+           }
+         else
+           {
+@@ -917,7 +943,7 @@ real_toupper (const gchar *str,
+             /* Some lowercase letters, e.g., U+000AA, FEMININE ORDINAL 
INDICATOR,
+              * do not have an uppercase equivalent, in which case val will be
+              * zero. */
+-            len += g_unichar_to_utf8 (val ? val : c, out_buffer ? out_buffer 
+ len : NULL);
++              append_utf8_char_to_buffer (val ? val : c, out_buffer, &len);
+           }
+       }
+       else
+@@ -927,7 +953,7 @@ real_toupper (const gchar *str,
+         if (out_buffer)
+           memcpy (out_buffer + len, last, char_len);
+ 
+-        len += char_len;
++          increase_size (&len, char_len);
+       }
+ 
+     }
+@@ -965,6 +991,8 @@ g_utf8_strup (const gchar *str,
+    * We use a two pass approach to keep memory management simple
+    */
+   result_len = real_toupper (str, len, NULL, locale_type);
++  g_assert (result_len < G_MAXSIZE);
++
+   result = g_malloc (result_len + 1);
+   real_toupper (str, len, result, locale_type);
+   result[result_len] = '\0';
+@@ -1022,14 +1050,15 @@ real_tolower (const gchar *str,
+             {
+               /* I + COMBINING DOT ABOVE => i (U+0069)
+                * LATIN CAPITAL LETTER I WITH DOT ABOVE => i (U+0069) */
+-              len += g_unichar_to_utf8 (0x0069, out_buffer ? out_buffer + len 
: NULL);
++              append_utf8_char_to_buffer (0x0069, out_buffer, &len);
++
+               if (combining_dot)
+                 p = g_utf8_next_char (p);
+             }
+           else
+             {
+               /* I => LATIN SMALL LETTER DOTLESS I */
+-              len += g_unichar_to_utf8 (0x131, out_buffer ? out_buffer + len 
: NULL); 
++              append_utf8_char_to_buffer (0x131, out_buffer, &len);
+             }
+         }
+       /* Introduce an explicit dot above when lowercasing capital I's and J's
+@@ -1037,19 +1066,19 @@ real_tolower (const gchar *str,
+       else if (locale_type == LOCALE_LITHUANIAN && 
+                (c == 0x00cc || c == 0x00cd || c == 0x0128))
+         {
+-          len += g_unichar_to_utf8 (0x0069, out_buffer ? out_buffer + len : 
NULL); 
+-          len += g_unichar_to_utf8 (0x0307, out_buffer ? out_buffer + len : 
NULL); 
++          append_utf8_char_to_buffer (0x0069, out_buffer, &len);
++          append_utf8_char_to_buffer (0x0307, out_buffer, &len);
+ 
+           switch (c)
+             {
+             case 0x00cc: 
+-              len += g_unichar_to_utf8 (0x0300, out_buffer ? out_buffer + len 
: NULL); 
++              append_utf8_char_to_buffer (0x0300, out_buffer, &len);
+               break;
+             case 0x00cd: 
+-              len += g_unichar_to_utf8 (0x0301, out_buffer ? out_buffer + len 
: NULL); 
++              append_utf8_char_to_buffer (0x0301, out_buffer, &len);
+               break;
+             case 0x0128: 
+-              len += g_unichar_to_utf8 (0x0303, out_buffer ? out_buffer + len 
: NULL); 
++              append_utf8_char_to_buffer (0x0303, out_buffer, &len);
+               break;
+             }
+         }
+@@ -1058,8 +1087,8 @@ real_tolower (const gchar *str,
+                 c == 'J' || c == G_UNICHAR_FULLWIDTH_J || c == 0x012e) &&
+                has_more_above (p))
+         {
+-          len += g_unichar_to_utf8 (g_unichar_tolower (c), out_buffer ? 
out_buffer + len : NULL); 
+-          len += g_unichar_to_utf8 (0x0307, out_buffer ? out_buffer + len : 
NULL); 
++          append_utf8_char_to_buffer (g_unichar_tolower (c), out_buffer, 
&len);
++          append_utf8_char_to_buffer (0x0307, out_buffer, &len);
+         }
+       else if (c == 0x03A3)   /* GREEK CAPITAL LETTER SIGMA */
+       {
+@@ -1082,7 +1111,7 @@ real_tolower (const gchar *str,
+         else
+           val = 0x3c2;        /* GREEK SMALL FINAL SIGMA */
+ 
+-        len += g_unichar_to_utf8 (val, out_buffer ? out_buffer + len : NULL);
++          append_utf8_char_to_buffer (val, out_buffer, &len);
+       }
+       else if (IS (t,
+                  OR (G_UNICODE_UPPERCASE_LETTER,
+@@ -1093,7 +1122,7 @@ real_tolower (const gchar *str,
+ 
+         if (val >= 0x1000000)
+           {
+-            len += output_special_case (out_buffer ? out_buffer + len : NULL, 
val - 0x1000000, t, 0);
++              append_special_case (out_buffer, &len, val - 0x1000000, t, 0);
+           }
+         else
+           {
+@@ -1112,7 +1141,7 @@ real_tolower (const gchar *str,
+ 
+             /* Not all uppercase letters are guaranteed to have a lowercase
+              * equivalent.  If this is the case, val will be zero. */
+-            len += g_unichar_to_utf8 (val ? val : c, out_buffer ? out_buffer 
+ len : NULL);
++              append_utf8_char_to_buffer (val ? val : c, out_buffer, &len);
+           }
+       }
+       else
+@@ -1122,7 +1151,7 @@ real_tolower (const gchar *str,
+         if (out_buffer)
+           memcpy (out_buffer + len, last, char_len);
+ 
+-        len += char_len;
++          increase_size (&len, char_len);
+       }
+ 
+     }
+@@ -1159,6 +1188,8 @@ g_utf8_strdown (const gchar *str,
+    * We use a two pass approach to keep memory management simple
+    */
+   result_len = real_tolower (str, len, NULL, locale_type);
++  g_assert (result_len < G_MAXSIZE);
++
+   result = g_malloc (result_len + 1);
+   real_tolower (str, len, result, locale_type);
+   result[result_len] = '\0';
diff -Nru glib2.0-2.74.6/debian/patches/CVE-2026-1489-4.patch 
glib2.0-2.74.6/debian/patches/CVE-2026-1489-4.patch
--- glib2.0-2.74.6/debian/patches/CVE-2026-1489-4.patch 1970-01-01 
01:00:00.000000000 +0100
+++ glib2.0-2.74.6/debian/patches/CVE-2026-1489-4.patch 2026-02-13 
12:35:33.000000000 +0100
@@ -0,0 +1,65 @@
+From: =?utf-8?b?Ik1hcmNvIFRyZXZpc2FuIChUcmV2acOxbyki?= <[email protected]>
+Date: Fri, 23 Jan 2026 17:39:34 +0100
+Subject: glib/tests/unicode: Add test debug information when parsing input
+ files
+
+On case of failures makes it easier to understand on what line of the
+source file we're at, as it might not be clear for non-ascii chars
+
+(cherry picked from commit b96966058f4291db8970ced70ee22103e63679e5)
+---
+ glib/tests/unicode.c | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+diff --git a/glib/tests/unicode.c b/glib/tests/unicode.c
+index c215891..daca4dc 100644
+--- a/glib/tests/unicode.c
++++ b/glib/tests/unicode.c
+@@ -586,6 +586,7 @@ test_casemap_and_casefold (void)
+   const char *locale;
+   const char *test;
+   const char *expected;
++  size_t line = 0;
+   char *convert;
+   char *current_locale = setlocale (LC_CTYPE, NULL);
+   char *old_lc_all, *old_lc_messages, *old_lang;
+@@ -606,6 +607,7 @@ test_casemap_and_casefold (void)
+ 
+   while (fgets (buffer, sizeof (buffer), infile))
+     {
++      line++;
+       if (buffer[0] == '#')
+         continue;
+ 
+@@ -648,6 +650,9 @@ test_casemap_and_casefold (void)
+ 
+       convert = g_utf8_strup (test, -1);
+       expected = strings[4][0] ? strings[4] : test;
++      g_test_message ("Converting '%s' => '%s' (line %" G_GSIZE_FORMAT ")",
++                      test, expected, line);
++
+       g_assert_cmpstr (convert, ==, expected);
+       g_free (convert);
+ 
+@@ -667,9 +672,11 @@ test_casemap_and_casefold (void)
+ 
+   infile = fopen (filename, "r");
+   g_assert (infile != NULL);
++  line = 0;
+ 
+   while (fgets (buffer, sizeof (buffer), infile))
+     {
++      line++;
+       if (buffer[0] == '#')
+         continue;
+ 
+@@ -679,6 +686,9 @@ test_casemap_and_casefold (void)
+       test = strings[0];
+ 
+       convert = g_utf8_casefold (test, -1);
++      g_test_message ("Converting '%s' => '%s' (line %" G_GSIZE_FORMAT ")",
++                      test, strings[1], line);
++
+       g_assert_cmpstr (convert, ==, strings[1]);
+       g_free (convert);
+ 
diff -Nru 
glib2.0-2.74.6/debian/patches/gtimezone-Handle-etc-localtime-symlink-pointing-to-anothe.patch
 
glib2.0-2.74.6/debian/patches/gtimezone-Handle-etc-localtime-symlink-pointing-to-anothe.patch
--- 
glib2.0-2.74.6/debian/patches/gtimezone-Handle-etc-localtime-symlink-pointing-to-anothe.patch
       1970-01-01 01:00:00.000000000 +0100
+++ 
glib2.0-2.74.6/debian/patches/gtimezone-Handle-etc-localtime-symlink-pointing-to-anothe.patch
       2026-02-13 12:35:33.000000000 +0100
@@ -0,0 +1,189 @@
+From: Alessandro Astone <[email protected]>
+Date: Tue, 20 Jan 2026 15:49:06 +0100
+Subject: gtimezone: Handle /etc/localtime symlink pointing to another symlink
+
+To resolve a timezone identifier from /etc/localtime we should traverse its
+symlink recursively until we find a target under $TZDIR, then the identifier
+is that target minus the $TZDIR path prefix.
+
+Bug: https://gitlab.gnome.org/GNOME/glib/-/issues/3816
+Bug-Debian: https://bugs.debian.org/1119919
+Origin: upstream, 2.87.3, commit:7073c4872d96b78bfa9396b38e18e8043308550f
+---
+ glib/gtimezone.c | 130 +++++++++++++++++++++++++++----------------------------
+ 1 file changed, 64 insertions(+), 66 deletions(-)
+
+diff --git a/glib/gtimezone.c b/glib/gtimezone.c
+index b8eaf25..5d224aa 100644
+--- a/glib/gtimezone.c
++++ b/glib/gtimezone.c
+@@ -504,6 +504,54 @@ zone_identifier_illumos (void)
+ }
+ #endif /* defined(__sun) && defined(__SRVR) */
+ 
++#define MAX_SYMLINKS 20
++
++/*
++ * Recursively resolve symlinks from @initial_path,
++ * returning the first target that is in a subtree of @zoneinfo
++ * or the first target that is a regular file,
++ * or NULL.
++ */
++static gchar *
++resolve_symlink_to_zoneinfo (const char *initial_path, const char *zoneinfo)
++{
++  char *current_path = g_strdup (initial_path);
++
++  for (int i = 0; i < MAX_SYMLINKS; i++)
++    {
++      char *link_target;
++      char *parent_dir;
++      char *next_path;
++      GError *error = NULL;
++
++      link_target = g_file_read_link (current_path, &error);
++      if (!link_target)
++        {
++          gboolean not_a_symlink = g_error_matches (error, G_FILE_ERROR, 
G_FILE_ERROR_INVAL);
++
++          g_clear_error (&error);
++          if (not_a_symlink)
++            return current_path;
++          break;
++        }
++
++      parent_dir = g_path_get_dirname (current_path);
++      next_path = g_canonicalize_filename (link_target, parent_dir);
++
++      g_free (parent_dir);
++      g_free (link_target);
++      g_free (current_path);
++      current_path = next_path;
++
++      if (g_str_has_prefix (current_path, zoneinfo))
++        return current_path;
++    }
++
++  g_free (current_path);
++  return NULL;
++}
++#undef MAX_SYMLINKS
++
+ /*
+  * returns the path to the top of the Olson zoneinfo timezone hierarchy.
+  */
+@@ -524,50 +572,16 @@ zone_identifier_unix (void)
+ {
+   gchar *resolved_identifier = NULL;
+   gsize prefix_len = 0;
+-  gchar *canonical_path = NULL;
+-  GError *read_link_err = NULL;
+   const gchar *tzdir;
+-  gboolean not_a_symlink_to_zoneinfo = FALSE;
+-  struct stat file_status;
++  GStatBuf buf;
+ 
+-  /* Resolve the actual timezone pointed to by /etc/localtime. */
+-  resolved_identifier = g_file_read_link ("/etc/localtime", &read_link_err);
+-
+-  if (resolved_identifier != NULL)
+-    {
+-      if (!g_path_is_absolute (resolved_identifier))
+-        {
+-          gchar *absolute_resolved_identifier = g_build_filename ("/etc", 
resolved_identifier, NULL);
+-          g_free (resolved_identifier);
+-          resolved_identifier = g_steal_pointer 
(&absolute_resolved_identifier);
+-        }
++  tzdir = g_getenv ("TZDIR");
++  if (tzdir == NULL)
++    tzdir = zone_info_base_dir ();
+ 
+-      if (g_lstat (resolved_identifier, &file_status) == 0)
+-        {
+-          if ((file_status.st_mode & S_IFMT) != S_IFREG)
+-            {
+-              /* Some systems (e.g. toolbox containers) make /etc/localtime 
be a symlink
+-               * to a symlink.
+-               *
+-               * Rather than try to cope with that, just ignore 
/etc/localtime and use
+-               * the fallback code to read timezone from /etc/timezone
+-               */
+-              g_clear_pointer (&resolved_identifier, g_free);
+-              not_a_symlink_to_zoneinfo = TRUE;
+-            }
+-        }
+-      else
+-        {
+-          g_clear_pointer (&resolved_identifier, g_free);
+-        }
+-    }
+-  else
+-    {
+-      not_a_symlink_to_zoneinfo = g_error_matches (read_link_err,
+-                                                   G_FILE_ERROR,
+-                                                   G_FILE_ERROR_INVAL);
+-      g_clear_error (&read_link_err);
+-    }
++  /* Resolve the actual timezone pointed to by /etc/localtime. */
++  if (g_lstat ("/etc/localtime", &buf) == 0 && S_ISLNK (buf.st_mode))
++    resolved_identifier = resolve_symlink_to_zoneinfo ("/etc/localtime", 
tzdir);
+ 
+   if (resolved_identifier == NULL)
+     {
+@@ -580,36 +594,24 @@ zone_identifier_unix (void)
+        *    as a last-ditch effort to parse the TZ= setting from within
+        *    /etc/default/init
+        */
+-      if (not_a_symlink_to_zoneinfo && (g_file_get_contents 
("/var/db/zoneinfo",
+-                                                             
&resolved_identifier,
+-                                                             NULL, NULL) ||
+-                                        g_file_get_contents ("/etc/timezone",
+-                                                             
&resolved_identifier,
+-                                                             NULL, NULL)
++      if (g_file_get_contents ("/var/db/zoneinfo",
++                               &resolved_identifier,
++                               NULL, NULL) ||
++          g_file_get_contents ("/etc/timezone",
++                               &resolved_identifier,
++                               NULL, NULL)
+ #if defined(__sun) && defined(__SVR4)
+-                                        ||
+-                                        (resolved_identifier = 
zone_identifier_illumos ())
++          || (resolved_identifier = zone_identifier_illumos ())
+ #endif
+-                                            ))
++      )
+         g_strchomp (resolved_identifier);
+       else
+         {
+           /* Error */
+           g_assert (resolved_identifier == NULL);
+-          goto out;
++          return NULL;
+         }
+     }
+-  else
+-    {
+-      /* Resolve relative path */
+-      canonical_path = g_canonicalize_filename (resolved_identifier, "/etc");
+-      g_free (resolved_identifier);
+-      resolved_identifier = g_steal_pointer (&canonical_path);
+-    }
+-
+-  tzdir = g_getenv ("TZDIR");
+-  if (tzdir == NULL)
+-    tzdir = zone_info_base_dir ();
+ 
+   /* Strip the prefix and slashes if possible. */
+   if (g_str_has_prefix (resolved_identifier, tzdir))
+@@ -624,10 +626,6 @@ zone_identifier_unix (void)
+              strlen (resolved_identifier) - prefix_len + 1  /* nul terminator 
*/);
+ 
+   g_assert (resolved_identifier != NULL);
+-
+-out:
+-  g_free (canonical_path);
+-
+   return resolved_identifier;
+ }
+ 
diff -Nru 
glib2.0-2.74.6/debian/patches/gtimezone-Use-var-db-timezone-zoneinfo-as-the-default-TZD.patch
 
glib2.0-2.74.6/debian/patches/gtimezone-Use-var-db-timezone-zoneinfo-as-the-default-TZD.patch
--- 
glib2.0-2.74.6/debian/patches/gtimezone-Use-var-db-timezone-zoneinfo-as-the-default-TZD.patch
       1970-01-01 01:00:00.000000000 +0100
+++ 
glib2.0-2.74.6/debian/patches/gtimezone-Use-var-db-timezone-zoneinfo-as-the-default-TZD.patch
       2026-02-13 12:35:33.000000000 +0100
@@ -0,0 +1,42 @@
+From: Alessandro Astone <[email protected]>
+Date: Thu, 22 Jan 2026 16:13:09 +0100
+Subject: gtimezone: Use /var/db/timezone/zoneinfo as the default TZDIR for
+ macOS
+
+macOS defines /usr/share/zoneinfo as a symlink to /var/db/timezone/zoneinfo,
+and /etc/localtime as a symlink to /var/db/timezone/zoneinfo/<identifier>.
+
+By using /usr/share/zoneinfo as TZDIR, we would break the logic that resolves
+/etc/localtime as a relative identifier by stripping the TZDIR prefix.
+An absolute path still works as identifier, but we prefer a relative one.
+
+Furthermore, by ensuring that /etc/localtime points to a subdir of TZDIR we
+correctly handle the case where /etc/localtime points to a symlink of symlink.
+
+Bug: https://gitlab.gnome.org/GNOME/glib/-/issues/3816
+Bug-Debian: https://bugs.debian.org/1119919
+Origin: upstream, 2.87.3, commit:bd04ea91dc533303c064ec1cb627844a4aa09aaf
+---
+ glib/gtimezone.c | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/glib/gtimezone.c b/glib/gtimezone.c
+index 5d224aa..6e8605d 100644
+--- a/glib/gtimezone.c
++++ b/glib/gtimezone.c
+@@ -558,10 +558,14 @@ resolve_symlink_to_zoneinfo (const char *initial_path, 
const char *zoneinfo)
+ static const gchar *
+ zone_info_base_dir (void)
+ {
+-  if (g_file_test ("/usr/share/zoneinfo", G_FILE_TEST_IS_DIR))
++  GStatBuf buf;
++
++  if (g_lstat ("/usr/share/zoneinfo", &buf) == 0 && S_ISDIR (buf.st_mode))
+     return "/usr/share/zoneinfo";     /* Most distros */
+   else if (g_file_test ("/usr/share/lib/zoneinfo", G_FILE_TEST_IS_DIR))
+     return "/usr/share/lib/zoneinfo"; /* Illumos distros */
++  else if (g_file_test ("/var/db/timezone/zoneinfo", G_FILE_TEST_IS_DIR))
++    return "/var/db/timezone/zoneinfo"; /* macOS */
+ 
+   /* need a better fallback case */
+   return "/usr/share/zoneinfo";
diff -Nru glib2.0-2.74.6/debian/patches/series 
glib2.0-2.74.6/debian/patches/series
--- glib2.0-2.74.6/debian/patches/series        2025-12-12 19:02:28.000000000 
+0100
+++ glib2.0-2.74.6/debian/patches/series        2026-02-13 12:35:33.000000000 
+0100
@@ -59,3 +59,13 @@
 gvariant-parser-Use-size_t-to-count-numbers-of-child-elem.patch
 gvariant-parser-Convert-error-handling-code-to-use-size_t.patch
 gfileattribute-Fix-integer-overflow-calculating-escaping-.patch
+gtimezone-Handle-etc-localtime-symlink-pointing-to-anothe.patch
+gtimezone-Use-var-db-timezone-zoneinfo-as-the-default-TZD.patch
+CVE-2026-0988.patch
+CVE-2026-1484-1.patch
+CVE-2026-1484-2.patch
+CVE-2026-1485.patch
+CVE-2026-1489-1.patch
+CVE-2026-1489-2.patch
+CVE-2026-1489-3.patch
+CVE-2026-1489-4.patch

Reply via email to