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);