[PATCH] time: Fix integer truncation in strftime (bug 34538)

Florian Weimer fweimer@redhat.com
Tue Aug 18 16:37:10 GMT 2026


Extremely large time zone names were not processed correctly.

---
 time/Makefile        |  1 +
 time/strftime_l.c    | 18 +++++++-------
 time/tst-strftime5.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 79 insertions(+), 9 deletions(-)

diff --git a/time/Makefile b/time/Makefile
index b28471cc9e..be92398045 100644
--- a/time/Makefile
+++ b/time/Makefile
@@ -75,6 +75,7 @@ tests := \
   tst-strftime2 \
   tst-strftime3 \
   tst-strftime4 \
+  tst-strftime5 \
   tst-strptime \
   tst-strptime-whitespace \
   tst-strptime2 \
diff --git a/time/strftime_l.c b/time/strftime_l.c
index 12bc5794be..9f2917134a 100644
--- a/time/strftime_l.c
+++ b/time/strftime_l.c
@@ -198,7 +198,7 @@ static const CHAR_T zeroes[16] = /* "0000000000000000" */
 
 # define memset_space(P, Len) \
   do {									      \
-    int _len = (Len);							      \
+    size_t _len = (Len);						      \
 									      \
     do									      \
       {									      \
@@ -206,12 +206,12 @@ static const CHAR_T zeroes[16] = /* "0000000000000000" */
 	(P) = MEMPCPY ((P), spaces, _this * sizeof (CHAR_T));		      \
 	_len -= _this;							      \
       }									      \
-    while (_len > 0);							      \
+    while (_len != 0);							      \
   } while (0)
 
 # define memset_zero(P, Len) \
   do {									      \
-    int _len = (Len);							      \
+    size_t _len = (Len);						      \
 									      \
     do									      \
       {									      \
@@ -219,7 +219,7 @@ static const CHAR_T zeroes[16] = /* "0000000000000000" */
 	(P) = MEMPCPY ((P), zeroes, _this * sizeof (CHAR_T));		      \
 	_len -= _this;							      \
       }									      \
-    while (_len > 0);							      \
+    while (_len != 0);							      \
   } while (0)
 #else
 # ifdef COMPILE_WIDE
@@ -234,14 +234,14 @@ static const CHAR_T zeroes[16] = /* "0000000000000000" */
 #define add(n, f)							      \
   do									      \
     {									      \
-      int _n = (n);							      \
-      int _delta = width - _n;						      \
-      int _incr = _n + (_delta > 0 ? _delta : 0);			      \
-      if ((size_t) _incr >= maxsize - i)				      \
+      size_t _n = (n);							      \
+      size_t _delta = (width < 0 || (size_t) width < _n) ? 0 : width - _n;    \
+      size_t _incr = _n + _delta;					      \
+      if (_incr >= maxsize - i)						      \
 	return 0;							      \
       if (p)								      \
 	{								      \
-	  if (_delta > 0)						      \
+	  if (_delta != 0)						      \
 	    {								      \
 	      if (pad == L_('0'))					      \
 		memset_zero (p, _delta);				      \
diff --git a/time/tst-strftime5.c b/time/tst-strftime5.c
new file mode 100644
index 0000000000..e625312731
--- /dev/null
+++ b/time/tst-strftime5.c
@@ -0,0 +1,69 @@
+/* Test strftime with a large time zone name.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C 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.
+
+   The GNU C 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 the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <time.h>
+
+#include <libc-diag.h>
+#include <limits.h>
+#include <stddef.h>
+#include <support/blob_repeat.h>
+#include <support/check.h>
+
+static int
+do_test (void)
+{
+  /* 6 is chosen so that the truncated value triggered the original bug.  */
+  enum { size = (size_t) INT_MAX + 6 };
+  struct support_blob_repeat repeat = support_blob_repeat_allocate
+    ("X", 1, size);
+  if (repeat.start == NULL)
+    FAIL_UNSUPPORTED ("could not allocate buffer for time zone name");
+  char *tzname = repeat.start;
+  tzname[size - 1] = '\0';
+
+  /* Time at the epoch with a fake time zone name.  */
+  struct tm tmbuf =
+    {
+      .tm_mday = 1,
+      .tm_zone = tzname,
+    };
+
+  char buf[10];
+  buf[9] = 'A';
+
+  TEST_COMPARE (strftime (buf, 9, "%Z", &tmbuf), 0);
+  TEST_COMPARE (buf[9], 'A');
+
+  /* GCC complains about using a width (3) below with %Z, but it is
+     supported in the glibc implementation.  */
+  DIAG_PUSH_NEEDS_COMMENT;
+  DIAG_IGNORE_NEEDS_COMMENT (0, "-Wformat");
+
+  TEST_COMPARE (strftime (buf, 9, "%3Z", &tmbuf), 0);
+  TEST_COMPARE (buf[9], 'A');
+
+  TEST_COMPARE (strftime (buf, 9, "%03Z", &tmbuf), 0);
+  TEST_COMPARE (buf[9], 'A');
+
+  DIAG_POP_NEEDS_COMMENT;
+
+  support_blob_repeat_free (&repeat);
+  return 0;
+}
+
+#include <support/test-driver.c>

base-commit: a6bf80129e24bd782919a3325e672587ab6ff406



More information about the Libc-alpha mailing list