From 37ae3d76695e2afe9646bc6a971110b50d080f73 Mon Sep 17 00:00:00 2001
From: Philip Withnall <pwithnall@gnome.org>
Date: Tue, 18 Feb 2025 16:44:58 +0000
Subject: gdatetime: Fix integer overflow when parsing very long ISO8601 inputs

This will only happen with invalid (or maliciously invalid) potential
ISO8601 strings, but `g_date_time_new_from_iso8601()` needs to be robust
against that.

Prevent `length` overflowing by correctly defining it as a `size_t`.
Similarly for `date_length`, but additionally track its validity in a
boolean rather than as its sign.

Spotted by chamalsl as #YWH-PGM9867-43.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>
---
 glib/gdatetime.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/glib/gdatetime.c b/glib/gdatetime.c
index a43efab0b..c42cbca8c 100644
--- a/glib/gdatetime.c
+++ b/glib/gdatetime.c
@@ -1492,7 +1492,8 @@ parse_iso8601_time (const gchar *text, gsize length,
 GDateTime *
 g_date_time_new_from_iso8601 (const gchar *text, GTimeZone *default_tz)
 {
-  gint length, date_length = -1;
+  size_t length, date_length = 0;
+  gboolean date_length_set = FALSE;
   gint hour = 0, minute = 0;
   gdouble seconds = 0.0;
   GTimeZone *tz = NULL;
@@ -1503,11 +1504,14 @@ g_date_time_new_from_iso8601 (const gchar *text, GTimeZone *default_tz)
   /* Count length of string and find date / time separator ('T', 't', or ' ') */
   for (length = 0; text[length] != '\0'; length++)
     {
-      if (date_length < 0 && (text[length] == 'T' || text[length] == 't' || text[length] == ' '))
-        date_length = length;
+      if (!date_length_set && (text[length] == 'T' || text[length] == 't' || text[length] == ' '))
+        {
+          date_length = length;
+          date_length_set = TRUE;
+        }
     }
 
-  if (date_length < 0)
+  if (!date_length_set)
     return NULL;
 
   if (!parse_iso8601_time (text + date_length + 1, length - (date_length + 1),
-- 
2.30.2

From 1b9f28bb3d1900c725edcd5ff13a6257cc089839 Mon Sep 17 00:00:00 2001
From: Philip Withnall <pwithnall@gnome.org>
Date: Tue, 18 Feb 2025 16:51:36 +0000
Subject: gdatetime: Fix potential integer overflow in timezone offset handling

This one is much harder to trigger than the one in the previous commit,
but mixing `gssize` and `gsize` always runs the risk of the former
overflowing for very (very very) long input strings.

Avoid that possibility by not using the sign of the `tz_offset` to
indicate its validity, and instead using the return value of the
function.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>
---
 glib/gdatetime.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/glib/gdatetime.c b/glib/gdatetime.c
index c42cbca8c..b410fb9f9 100644
--- a/glib/gdatetime.c
+++ b/glib/gdatetime.c
@@ -1341,8 +1341,10 @@ parse_iso8601_date (const gchar *text, gsize length,
     return FALSE;
 }
 
+/* Value returned in tz_offset is valid if and only if the function return value
+ * is non-NULL. */
 static GTimeZone *
-parse_iso8601_timezone (const gchar *text, gsize length, gssize *tz_offset)
+parse_iso8601_timezone (const gchar *text, gsize length, size_t *tz_offset)
 {
   gint i, tz_length, offset_hours, offset_minutes;
   gint offset_sign = 1;
@@ -1410,11 +1412,11 @@ static gboolean
 parse_iso8601_time (const gchar *text, gsize length,
                     gint *hour, gint *minute, gdouble *seconds, GTimeZone **tz)
 {
-  gssize tz_offset = -1;
+  size_t tz_offset = 0;
 
   /* Check for timezone suffix */
   *tz = parse_iso8601_timezone (text, length, &tz_offset);
-  if (tz_offset >= 0)
+  if (*tz != NULL)
     length = tz_offset;
 
   /* hh:mm:ss(.sss) */
-- 
2.30.2

From aae5ff8dac5371e951a2592ca9c392f17e1a3892 Mon Sep 17 00:00:00 2001
From: Philip Withnall <pwithnall@gnome.org>
Date: Tue, 18 Feb 2025 16:55:18 +0000
Subject: gdatetime: Track timezone length as an unsigned size_t
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

It’s guaranteed to be in (0, length] by the calculations above.

This avoids the possibility of integer overflow through `gssize` not
being as big as `size_t`.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>
---
 glib/gdatetime.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/glib/gdatetime.c b/glib/gdatetime.c
index b410fb9f9..5d10c29bc 100644
--- a/glib/gdatetime.c
+++ b/glib/gdatetime.c
@@ -1346,7 +1346,8 @@ parse_iso8601_date (const gchar *text, gsize length,
 static GTimeZone *
 parse_iso8601_timezone (const gchar *text, gsize length, size_t *tz_offset)
 {
-  gint i, tz_length, offset_hours, offset_minutes;
+  size_t tz_length;
+  gint i, offset_hours, offset_minutes;
   gint offset_sign = 1;
   GTimeZone *tz;
 
-- 
2.30.2

From 65beb5b44ee4f51472ecb42b4c423bb1e8f57af9 Mon Sep 17 00:00:00 2001
From: Philip Withnall <pwithnall@gnome.org>
Date: Tue, 18 Feb 2025 17:07:24 +0000
Subject: gdatetime: Factor out some string pointer arithmetic
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Makes the following code a little clearer, but doesn’t introduce any
functional changes.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>
---
 glib/gdatetime.c | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/glib/gdatetime.c b/glib/gdatetime.c
index 5d10c29bc..5d02c8738 100644
--- a/glib/gdatetime.c
+++ b/glib/gdatetime.c
@@ -1350,6 +1350,7 @@ parse_iso8601_timezone (const gchar *text, gsize length, size_t *tz_offset)
   gint i, offset_hours, offset_minutes;
   gint offset_sign = 1;
   GTimeZone *tz;
+  const char *tz_start;
 
   /* UTC uses Z suffix  */
   if (length > 0 && text[length - 1] == 'Z')
@@ -1367,34 +1368,35 @@ parse_iso8601_timezone (const gchar *text, gsize length, size_t *tz_offset)
       }
   if (i < 0)
     return NULL;
+  tz_start = text + i;
   tz_length = length - i;
 
   /* +hh:mm or -hh:mm */
-  if (tz_length == 6 && text[i+3] == ':')
+  if (tz_length == 6 && tz_start[3] == ':')
     {
-      if (!get_iso8601_int (text + i + 1, 2, &offset_hours) ||
-          !get_iso8601_int (text + i + 4, 2, &offset_minutes))
+      if (!get_iso8601_int (tz_start + 1, 2, &offset_hours) ||
+          !get_iso8601_int (tz_start + 4, 2, &offset_minutes))
         return NULL;
     }
   /* +hhmm or -hhmm */
   else if (tz_length == 5)
     {
-      if (!get_iso8601_int (text + i + 1, 2, &offset_hours) ||
-          !get_iso8601_int (text + i + 3, 2, &offset_minutes))
+      if (!get_iso8601_int (tz_start + 1, 2, &offset_hours) ||
+          !get_iso8601_int (tz_start + 3, 2, &offset_minutes))
         return NULL;
     }
   /* +hh or -hh */
   else if (tz_length == 3)
     {
-      if (!get_iso8601_int (text + i + 1, 2, &offset_hours))
+      if (!get_iso8601_int (tz_start + 1, 2, &offset_hours))
         return NULL;
       offset_minutes = 0;
     }
   else
     return NULL;
 
-  *tz_offset = i;
-  tz = g_time_zone_new (text + i);
+  *tz_offset = tz_start - text;
+  tz = g_time_zone_new (tz_start);
 
   /* Double-check that the GTimeZone matches our interpretation of the timezone.
    * This can fail because our interpretation is less strict than (for example)
-- 
2.30.2

From 0df4f223046b56db6446d1458c8a3fe61b3fed00 Mon Sep 17 00:00:00 2001
From: Philip Withnall <pwithnall@gnome.org>
Date: Tue, 18 Feb 2025 17:28:33 +0000
Subject: gdatetime: Factor out an undersized variable

For long input strings, it would have been possible for `i` to overflow.
Avoid that problem by using the `tz_length` instead, so that we count up
rather than down.

This commit introduces no functional changes (outside of changing
undefined behaviour), and can be verified using the identity
`i === length - tz_length`.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>
---
 glib/gdatetime.c | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/glib/gdatetime.c b/glib/gdatetime.c
index 5d02c8738..1ecb6e0b3 100644
--- a/glib/gdatetime.c
+++ b/glib/gdatetime.c
@@ -1347,7 +1347,7 @@ static GTimeZone *
 parse_iso8601_timezone (const gchar *text, gsize length, size_t *tz_offset)
 {
   size_t tz_length;
-  gint i, offset_hours, offset_minutes;
+  gint offset_hours, offset_minutes;
   gint offset_sign = 1;
   GTimeZone *tz;
   const char *tz_start;
@@ -1360,16 +1360,15 @@ parse_iso8601_timezone (const gchar *text, gsize length, size_t *tz_offset)
     }
 
   /* Look for '+' or '-' of offset */
-  for (i = length - 1; i >= 0; i--)
-    if (text[i] == '+' || text[i] == '-')
+  for (tz_length = 1; tz_length <= length; tz_length++)
+    if (text[length - tz_length] == '+' || text[length - tz_length] == '-')
       {
-        offset_sign = text[i] == '-' ? -1 : 1;
+        offset_sign = text[length - tz_length] == '-' ? -1 : 1;
         break;
       }
-  if (i < 0)
+  if (tz_length > length)
     return NULL;
-  tz_start = text + i;
-  tz_length = length - i;
+  tz_start = text + length - tz_length;
 
   /* +hh:mm or -hh:mm */
   if (tz_length == 6 && tz_start[3] == ':')
-- 
2.30.2

