This is an automated email from the ASF dual-hosted git repository.

swebb2066 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git


The following commit(s) were added to refs/heads/master by this push:
     new 9fd389fa Prevent out-of-bounds reads in SimpleDateFormat name 
formatting (#709)
9fd389fa is described below

commit 9fd389faac75dfcfae36d4db92430fd4892dbe33
Author: metsw24-max <[email protected]>
AuthorDate: Fri Jun 5 06:20:05 2026 +0530

    Prevent out-of-bounds reads in SimpleDateFormat name formatting (#709)
---
 src/main/cpp/simpledateformat.cpp                 |  28 ++++-
 src/test/cpp/helpers/CMakeLists.txt               |   1 +
 src/test/cpp/helpers/simpledateformattestcase.cpp | 135 ++++++++++++++++++++++
 3 files changed, 159 insertions(+), 5 deletions(-)

diff --git a/src/main/cpp/simpledateformat.cpp 
b/src/main/cpp/simpledateformat.cpp
index 708fa038..cc1e898d 100644
--- a/src/main/cpp/simpledateformat.cpp
+++ b/src/main/cpp/simpledateformat.cpp
@@ -61,6 +61,24 @@ namespace SimpleDateFormatImpl
 {
 typedef void (*incrementFunction)(tm& time, apr_time_exp_t& apr_time);
 
+/**
+ * Append \c names[index] only when \c index is within bounds.
+ *
+ * The day/month/AM-PM name tables have a fixed size, but the index comes
+ * directly from an apr_time_exp_t field (tm_wday, tm_mon, tm_hour). APR's
+ * time-explosion arithmetic can overflow and yield an out-of-range field for
+ * extreme apr_time_t inputs, in which case the previous code performed an
+ * out-of-bounds std::vector read (std::vector::operator[] is unchecked).
+ */
+static inline void appendName(LogString& s,
+       const std::vector<LogString>& names, int index)
+{
+       if (0 <= index && static_cast<std::vector<LogString>::size_type>(index) 
< names.size())
+       {
+               s.append(names[index]);
+       }
+}
+
 /**
  * Abstract inner class representing one format token
  * (one or more instances of a character).
@@ -302,7 +320,7 @@ class AbbreviatedMonthNameToken : public PatternToken
 
                void format(LogString& s, const apr_time_exp_t& tm, Pool& /* p 
*/ ) const
                {
-                       s.append( names[tm.tm_mon] );
+                       appendName( s, names, tm.tm_mon );
                }
 
        private:
@@ -321,7 +339,7 @@ class FullMonthNameToken : public PatternToken
 
                void format( LogString& s, const apr_time_exp_t& tm, Pool& /* p 
*/ ) const
                {
-                       s.append( names[tm.tm_mon] );
+                       appendName( s, names, tm.tm_mon );
                }
 
        private:
@@ -415,7 +433,7 @@ class AbbreviatedDayNameToken : public PatternToken
 
                void format( LogString& s, const apr_time_exp_t& tm, Pool& /* p 
*/ ) const
                {
-                       s.append( names[tm.tm_wday] );
+                       appendName( s, names, tm.tm_wday );
                }
 
        private:
@@ -435,7 +453,7 @@ class FullDayNameToken : public PatternToken
 
                void format( LogString& s, const apr_time_exp_t& tm, Pool& /* p 
*/ ) const
                {
-                       s.append( names[tm.tm_wday] );
+                       appendName( s, names, tm.tm_wday );
                }
 
        private:
@@ -551,7 +569,7 @@ class AMPMToken : public PatternToken
 
                void format( LogString& s, const apr_time_exp_t& tm, Pool& /* p 
*/ ) const
                {
-                       s.append( names[tm.tm_hour / 12] );
+                       appendName( s, names, tm.tm_hour / 12 );
                }
 
        private:
diff --git a/src/test/cpp/helpers/CMakeLists.txt 
b/src/test/cpp/helpers/CMakeLists.txt
index 97ce7454..7c887e42 100644
--- a/src/test/cpp/helpers/CMakeLists.txt
+++ b/src/test/cpp/helpers/CMakeLists.txt
@@ -31,6 +31,7 @@ set(HELPER_TESTS
     optionconvertertestcase
     propertiestestcase
     relativetimedateformattestcase
+    simpledateformattestcase
     stringhelpertestcase
     stringtokenizertestcase
     timezonetestcase
diff --git a/src/test/cpp/helpers/simpledateformattestcase.cpp 
b/src/test/cpp/helpers/simpledateformattestcase.cpp
new file mode 100644
index 00000000..d435381a
--- /dev/null
+++ b/src/test/cpp/helpers/simpledateformattestcase.cpp
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define __STDC_CONSTANT_MACROS
+#include <log4cxx/logstring.h>
+#include <log4cxx/helpers/simpledateformat.h>
+#include "../logunit.h"
+#include <log4cxx/helpers/pool.h>
+#include "../insertwide.h"
+#include <apr.h>
+#include <apr_time.h>
+
+using namespace log4cxx;
+using namespace log4cxx::helpers;
+
+#define LOG4CXX_TEST 1
+#include <log4cxx/private/log4cxx_private.h>
+
+#if LOG4CXX_HAS_STD_LOCALE
+       #include <locale>
+       #define MAKE_LOCALE(ptr, id)     \
+               std::locale loco(id);        \
+               std::locale* ptr = &loco;
+#else
+       #define MAKE_LOCALE(ptr, id)     \
+               std::locale* ptr = NULL;
+#endif
+
+/**
+ * Unit test for {@link SimpleDateFormat}.
+ */
+LOGUNIT_CLASS(SimpleDateFormatTestCase)
+{
+       LOGUNIT_TEST_SUITE( SimpleDateFormatTestCase );
+       LOGUNIT_TEST( testNameFields );
+       LOGUNIT_TEST( testExtremeTimesDoNotReadOutOfBounds );
+       LOGUNIT_TEST_SUITE_END();
+
+#define MICROSECONDS_PER_DAY APR_INT64_C(86400000000)
+
+public:
+       /**
+        * The day-name, month-name and AM/PM tokens index fixed-size name 
tables.
+        * Verify that ordinary formatting of those fields is unchanged after 
the
+        * bounds-checking added to guard against out-of-range time fields.
+        */
+       void testNameFields()
+       {
+               // 02 Jan 2004 00:00:00 GMT is a Friday.
+               apr_time_t jan2_2004 = MICROSECONDS_PER_DAY * 12419;
+               MAKE_LOCALE(localeUS, "C");
+
+               {
+                       SimpleDateFormat fmt(LOG4CXX_STR("EEE"), localeUS);
+                       fmt.setTimeZone(TimeZone::getGMT());
+                       LogString actual;
+                       fmt.format(actual, jan2_2004);
+                       LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("Fri")), 
actual);
+               }
+               {
+                       SimpleDateFormat fmt(LOG4CXX_STR("MMM"), localeUS);
+                       fmt.setTimeZone(TimeZone::getGMT());
+                       LogString actual;
+                       fmt.format(actual, jan2_2004);
+                       LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("Jan")), 
actual);
+               }
+               {
+                       SimpleDateFormat fmt(LOG4CXX_STR("a"), localeUS);
+                       fmt.setTimeZone(TimeZone::getGMT());
+                       LogString actual;
+                       fmt.format(actual, jan2_2004);
+                       LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("AM")), 
actual);
+               }
+       }
+
+       /**
+        * Extreme apr_time_t values can make APR's time-explosion arithmetic
+        * overflow and produce an out-of-range tm_wday / tm_mon / tm_hour. The
+        * day-name, month-name and AM/PM tokens previously indexed their 
fixed-size
+        * name tables with those unchecked values, which is an out-of-bounds
+        * std::vector read (a heap-buffer-overflow flagged by 
AddressSanitizer).
+        *
+        * This exercises the affected tokens with such values and asserts the 
format
+        * completes with a bounded result rather than reading out of bounds.
+        */
+       void testExtremeTimesDoNotReadOutOfBounds()
+       {
+               MAKE_LOCALE(localeUS, "C");
+
+               const apr_time_t times[] =
+               { (apr_time_t) APR_INT64_MAX
+               , (apr_time_t) APR_INT64_MIN
+               , (apr_time_t)(APR_INT64_MAX - 1)
+               , (apr_time_t) APR_INT64_C(-19503744426494601)
+               , (apr_time_t) APR_INT64_C(9000000000000000000)
+               , (apr_time_t) APR_INT64_C(-9000000000000000000)
+               };
+
+               const logchar* patterns[] =
+               { LOG4CXX_STR("EEE")
+               , LOG4CXX_STR("EEEE")
+               , LOG4CXX_STR("MMM")
+               , LOG4CXX_STR("MMMM")
+               , LOG4CXX_STR("a")
+               , LOG4CXX_STR("EEEE, d MMMM yyyy a")
+               };
+
+               for (auto t : times)
+               {
+                       for (auto p : patterns)
+                       {
+                               SimpleDateFormat fmt(LogString(p), localeUS);
+                               LogString actual;
+                               fmt.format(actual, t);          // must not 
read out of bounds
+                               LOGUNIT_ASSERT(actual.length() < 1000);
+                       }
+               }
+       }
+};
+
+LOGUNIT_TEST_SUITE_REGISTRATION(SimpleDateFormatTestCase);

Reply via email to