You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by sw...@apache.org on 2026/06/05 00:50:11 UTC

(logging-log4cxx) branch master updated: Prevent out-of-bounds reads in SimpleDateFormat name formatting (#709)

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 <me...@gmail.com>
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);