#4338 closed defect (fixed)

rtems_clock_set(): Cannot set future dates later than approximately 2105

Reported by: Frank Kuehndel Owned by: Sebastian Huber
Priority: normal Milestone: 6.1
Component: rtems Version: 6
Severity: normal Keywords: rtems_clock_set, 2514, _TOD_To_seconds, qualification
Cc: Blocked By:
Blocking:

Description

Short Problem Description

RTEMS Classic API Guide says:

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.

Change History (11)

comment:1 Changed on 03/12/21 at 14:02:46 by Joel Sherrill

Will switching to time_t instead of uint32_t largely resolve this?

I think you might have missed some Classic API methods which might overflow. I think anything using uint32_t or rtems_interval related to seconds or ticks over long intervals is suspect. I spotted these:

  • rtems_clock_get_seconds_since_epoch() uses rtems_interval.
  • rtems_clock_get_ticks_since_boot() also uses rtems_interval but I think this overflows sooner than we'd like

I think the Classic API time of day structure is OK.

comment:2 Changed on 03/12/21 at 15:30:38 by Frank Kuehndel

  • time_t - I presume 64 bit - will avoid the overflow in _TOD_To_seconds() but
    • ... there are still the not correctly handled far away leap years.
    • ... there is STATUS_INVALID_NUMBER (Should not occur when the overflow is fixed. Yet, I do not like such incorrect code even if it should not trigger.)
  • Overruns in rtems_clock_get_ticks_since_boot() are OK because the Classic API Guide says:

With a 1ms clock tick, this counter overflows after 50 days since boot. This is the historical measure of uptime in an RTEMS system. The newer service rtems_clock_get_uptime() is another and potentially more accurate way of obtaining similar information.

  • At other functions like rtems_clock_get_seconds_since_epoch() I had not have a look, yet.

comment:3 Changed on 03/12/21 at 15:51:58 by Joel Sherrill

OK. I was unclear but I was asking if it is time to move rtems_interval to 64-bits. This would address the historical ticks since boot and other intervals being 32 bits. The limit also applied to rtems_task_wake_after() and other APIs using rtems_interval for number of ticks.

comment:4 Changed on 03/12/21 at 16:25:14 by Sebastian Huber

Could we please separate the 32-bit rtems_interval issues from this ticket.

If we need a 64-bit division we should think about limiting the time of day to the year 2104. If it is just a multiplication and a couple of leap year fixes, then we should keep the full watchdog range.

comment:5 Changed on 04/21/21 at 05:19:03 by Frank Kühndel <frank.kuehndel@…>

In 7bbbe42/rtems:

clock:_TOD_To_seconds(): Fix year 2514 overflow

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.

Update #4338

comment:6 Changed on 05/06/21 at 13:20:56 by Frank Kuehndel

Update on status:

This is fixed in code. What is missing is to update the documentation (i.e. removing the 2514-05-31T01:53:03.999999999Z in favor of 2104-12-31T23:59:59.999999999Z in Classic API and may be explaining that the internal clock will run beyond this limit and POSIX API can work with dates beyond 2105). A colleague of mine promised to do that.

comment:7 Changed on 06/18/21 at 09:24:45 by Sebastian Huber

Keywords: qualification added

comment:8 Changed on 09/06/21 at 10:25:56 by Sebastian Huber <sebastian.huber@…>

In ad41c179/rtems:

score: Change TOD_LATEST_YEAR to 2099

This simplifies the implementation a bit. Declare _TOD_Days_to_date[] in
<rtems/score/todimpl.h>. Make _TOD_Days_per_month[] and
_TOD_Days_since_last_leap_year[] static.

Update #4338.

comment:9 Changed on 09/06/21 at 10:27:18 by Sebastian Huber <sebastian.huber@…>

Resolution: fixed
Status: assignedclosed

In 081ec5d/rtems-docs:

c-user: Document time of day constraints

Close #4338.

comment:10 Changed on 09/06/21 at 10:40:03 by Sebastian Huber <sebastian.huber@…>

In c0435b5e/rtems:

rtems: Document time of day constraints

Update #4338.

comment:11 Changed on 07/20/22 at 07:39:37 by Sebastian Huber <sebastian.huber@…>

In d4c21e5/rtems:

ada/sp09: The year 2100 cannot be set

Update #4338.

Note: See TracTickets for help on using tickets.