[PATCH] clock:_TOD_To_seconds(): Fix year 2514 overflow

Frank Kuehndel frank.kuehndel at embedded-brains.de
Thu Apr 8 13:41:15 UTC 2021


From: Frank Kühndel <frank.kuehndel at embedded-brains.de>

This patch fixes issue #4338 by changing _TOD_Validate()
to only accept years till 2105. This requires another patch
to change the documentation of rtems_clock_set() and other
affected API functions (indicating the end date is 2105 not 2514).

I tried to support till year 2514 but it turned out that
this needs changing the Timer Manager too. That in turn
would mean to change _TOD_Seconds_since_epoch( void )
from 32 to 64 bit. Sebastian pointed out that a naive extension
leads to trouble with 32 bit processors. He deemed a safe
re-implementation too costly performance wise considering
that year 2106 is far away and current binaries using RTEMS
Classic API are unlikely to be in use by 2106.

The constant TOD_SECONDS_AT_2100_03_01_00_00 in
cpukit/rtems/src/clocktodtoseconds.c happens to be wrong by
1 hour. When setting the date 2100-Feb-28 23:59:59 and then
reading the date again you will find yourself in 2100-Feb-27.

Original Bug #4338 Text:

== Short Problem Description ==

[https://docs.rtems.org/branches/master/c-user/clock/directives.html

> RTEMS can represent time points of this clock in nanoseconds
> ranging from 1988-01-01T00:00:00.000000000Z to
> 2514-05-31T01:53:03.999999999Z.

* Yet, years larger than roughly 2105 to 2110 cannot be set
  (or at least the date should be wrong but this never
  occured in my tests).

> The possible RETURN VALUES are RTEMS_SUCCESSFUL,
> RTEMS_INVALID_ADDRESS, RTEMS_INVALID_CLOCK

* Yet, rtems_clock_set() can return status RTEMS_INVALID_NUMBER,
  too. (Only for such dates in the far future.)

== How To Replicate? ==

Call rtems_clock_set() with this time_of-day:
{
  year = 2514,
  month = 5,
  day = 31,
  hour = 1,
  minute = 53,
  second = 3,
  ticks = 995
}
The return value will be RTEMS_INVALID_NUMBER.

== Bugs in _TOD_To_seconds() ==

cpukit/rtems/src/clockset.c: rtems_clock_set() calls

* cpukit/rtems/src/clocktodvalidate.c: _TOD_Validate() and
* cpukit/rtems/src/clocktodtoseconds.c: _TOD_To_seconds() and
* cpukit/score/src/coretodset.c: _TOD_Set()

First issue:

_TOD_To_seconds() converts the time_of_day structure into
seconds using a variable `time` of type `uint32_t`. This simply
overflows when in comes close to year 2110.

Debugger output at the end of _TOD_To_seconds():

    (gdb) print the_tod->year
    $16 = 2104
    (gdb) print time
    $17 = 4233686400

with a higher year:

    (gdb) print the_tod->year
    $28 = 2514
    (gdb) print *the_tod
    $31 = {
      year = 2514,
      month = 5,
      day = 31,
      hour = 1,
      minute = 53,
      second = 3,
      ticks = 995
    }
    (gdb) print time
    $29 = 192272

Second issue:

_TOD_To_seconds() can (most likely) not handle the leap year
issues of the years 2200, 2300, 2400, 2500 because it has
dedicated code for the 2100 case only:

   /* The year 2100 is not a leap year */
   if ( time
       >= (TOD_SECONDS_AT_2100_03_01_00_00 -
          TOD_SECONDS_1970_THROUGH_1988)) {
     time -= TOD_SECONDS_PER_DAY;
   }

== _TOD_Check_time_of_day_and_run_hooks() causes STATUS_INVALID_NUMBER ==

cpukit/score/src/coretodset.c: _TOD_Set() calls

* cpukit/score/src/coretodset.c:
  _TOD_Check_time_of_day_and_run_hooks()

in this code snippet (note `return status`):

    status = _TOD_Check_time_of_day_and_run_hooks( tod );
    if ( status != STATUS_SUCCESSFUL ) {
      _TOD_Release( lock_context );
      return status;
    }

_TOD_Check_time_of_day_and_run_hooks( tod ) can return status
STATUS_INVALID_NUMBER which is not documented for
rtems_clock_set(). The small time in seconds value 192272 from
the integer overrun discussed above triggers the middle `if`
clause:

    static Status_Control _TOD_Check_time_of_day_and_run_hooks(
      const struct timespec *tod
    )
    {
      if ( !_Watchdog_Is_valid_timespec( tod ) ) {
        return STATUS_INVALID_NUMBER;
      }

      if ( tod->tv_sec < TOD_SECONDS_1970_THROUGH_1988 ) {
        return STATUS_INVALID_NUMBER;
      }

      if ( _Watchdog_Is_far_future_timespec( tod ) ) {
        return STATUS_INVALID_NUMBER;
      }

      return _TOD_Hook_Run( TOD_ACTION_SET_CLOCK, tod );
    }

== Final Notes ==

* I found #2665 and #2548 but I do not say these are relevant here.
* `#define WATCHDOG_MAX_SECONDS 0x3ffffffff` in
  cpukit/include/rtems/score/watchdogimpl.h covers 17179869183
  seconds which are some 544 years
* _TOD_Check_time_of_day_and_run_hooks() seems to be only used by
  _TOD_Set() but _TOD_Set() has three users.
* I did not check whether the invers conversation
  (rtems_clock_get_tod())
  works for dates which are centuries in the future.

Update #4338
---
 cpukit/include/rtems/score/todimpl.h | 16 ++++++++++++++++
 cpukit/rtems/src/clocktodtoseconds.c |  2 +-
 cpukit/rtems/src/clocktodvalidate.c  |  1 +
 testsuites/sptests/sp2038/init.c     | 28 ++++++++++++++++++----------
 4 files changed, 36 insertions(+), 11 deletions(-)

diff --git a/cpukit/include/rtems/score/todimpl.h b/cpukit/include/rtems/score/todimpl.h
index 9805ec0dfd..316a56ec74 100644
--- a/cpukit/include/rtems/score/todimpl.h
+++ b/cpukit/include/rtems/score/todimpl.h
@@ -123,6 +123,22 @@ extern "C" {
  */
 #define TOD_BASE_YEAR 1988
 
+/**
+ *  @brief Latest year to which a time of day can be initialized.
+ *
+ *  The following constant defines the latest year to which an
+ *  RTEMS time of day can be set using rtems_clock_set().
+ *
+ *  32 bits can accept as latest point in time 2106-Feb-7 6:28:15
+ *  but to simplify the implementation, is was decided to only
+ *  check that the year is not greater than the year of this constant.
+ *
+ *  The internal realtime clock can run centuries longer but in
+ *  contrast to the POSIX API, the RTEMS Classic API does not
+ *  support this for efficiency reasons.
+ */
+#define TOD_LATEST_YEAR 2105
+
 /**
  * @addtogroup RTEMSScoreTOD
  *
diff --git a/cpukit/rtems/src/clocktodtoseconds.c b/cpukit/rtems/src/clocktodtoseconds.c
index 49ae257243..86e89f86eb 100644
--- a/cpukit/rtems/src/clocktodtoseconds.c
+++ b/cpukit/rtems/src/clocktodtoseconds.c
@@ -23,7 +23,7 @@
 #include <rtems/rtems/clockimpl.h>
 #include <rtems/score/todimpl.h>
 
-#define TOD_SECONDS_AT_2100_03_01_00_00 4107538800UL
+#define TOD_SECONDS_AT_2100_03_01_00_00 4107542400UL
 
 /*
  *  The following array contains the number of days in all months
diff --git a/cpukit/rtems/src/clocktodvalidate.c b/cpukit/rtems/src/clocktodvalidate.c
index d8af275d04..2685bfd6e7 100644
--- a/cpukit/rtems/src/clocktodvalidate.c
+++ b/cpukit/rtems/src/clocktodvalidate.c
@@ -52,6 +52,7 @@ bool _TOD_Validate(
       (the_tod->month  == 0)                      ||
       (the_tod->month  >  TOD_MONTHS_PER_YEAR)    ||
       (the_tod->year   <  TOD_BASE_YEAR)          ||
+      (the_tod->year   >  TOD_LATEST_YEAR)        ||
       (the_tod->day    == 0) )
      return false;
 
diff --git a/testsuites/sptests/sp2038/init.c b/testsuites/sptests/sp2038/init.c
index a3f09d4156..10850d9c4d 100644
--- a/testsuites/sptests/sp2038/init.c
+++ b/testsuites/sptests/sp2038/init.c
@@ -149,8 +149,7 @@ static const uint32_t sample_seconds [] = {
   4168736895UL,
   4200272895UL,
   4231808895UL,
-  4263431295UL,
-  4294967295UL
+  4263431295UL
 };
 
 static const rtems_time_of_day nearly_problem_2038 = {
@@ -171,8 +170,8 @@ static const rtems_time_of_day problem_2038 = {
   .second = 8
 };
 
-static const rtems_time_of_day nearly_problem_2106 = {
-  .year = 2106,
+static const rtems_time_of_day tod_to_seconds_base = {
+  .year = 0,
   .month = 2,
   .day = 7,
   .hour = 6,
@@ -180,13 +179,22 @@ static const rtems_time_of_day nearly_problem_2106 = {
   .second = 15
 };
 
+static const rtems_time_of_day nearly_problem_2106 = {
+  .year = 2105,
+  .month = 12,
+  .day = 31,
+  .hour = 23,
+  .minute = 59,
+  .second = 59
+};
+
 static const rtems_time_of_day problem_2106 = {
   .year = 2106,
-  .month = 2,
-  .day = 7,
-  .hour = 6,
-  .minute = 28,
-  .second = 16
+  .month = 1,
+  .day = 1,
+  .hour = 0,
+  .minute = 0,
+  .second = 0
 };
 
 static const rtems_time_of_day problem_2100 = {
@@ -214,7 +222,7 @@ static void test_tod_to_seconds(void)
   size_t n = sizeof(sample_seconds) / sizeof(sample_seconds [0]);
 
   for (i = 0; i < n; ++i) {
-    rtems_time_of_day tod = nearly_problem_2106;
+    rtems_time_of_day tod = tod_to_seconds_base;
     uint32_t seconds = 0;
     rtems_interval seconds_as_interval = 0;
 
-- 
2.26.2



More information about the devel mailing list