Fix time_t arithmetic after 2038 by bernd-edlinger · Pull Request #17701 · openssl/openssl · GitHub | Latest TMZ Celebrity News & Gossip | Watch TMZ Live
Skip to content

Fix time_t arithmetic after 2038 #17701

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

bernd-edlinger
Copy link
Member

Some arithmetics using long when this type is 32
bit and time_t is 64 bit might break after 2038.

Some arithmetics using long when this type is 32
bit and time_t is 64 bit might break after 2038.
@bernd-edlinger bernd-edlinger added branch: master Merge to master branch approval: review pending This pull request needs review by a committer branch: 3.0 Merge to openssl-3.0 branch labels Feb 14, 2022
@t8m t8m added the triaged: bug The issue/pr is/fixes a bug label Feb 14, 2022
@paulidale
Copy link
Contributor

Would it make sense to implement our own non-floating point version of difftime(3)?

@bernd-edlinger
Copy link
Member Author

normally I would simply use time_t, but that does not work because of the frozen ABI.

@paulidale
Copy link
Contributor

I'm not sure I understand.

@@ -270,10 +270,10 @@ int s_time_main(int argc, char **argv)
/* Loop and time how long it takes to make connections */

bytes_read = 0;
finishtime = (long)time(NULL) + maxtime;
finishtime = (long)((unsigned long)time(NULL) + maxtime);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT the local variables finishtime and maxtime are not part of the API or ABI. Can't we change their type (and other internal variables' type) to be native time_t and only cast at the boundaries where we have to conform to existing API contracts?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that makes sense, I am also tempted to reverse the computation and store a starttime instead of the finishtime, that needs also lots of type casts, but would also be robust if time is set back.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would advise using a separate PR for the "store starttime instead of finishtime" change.

@bernd-edlinger
Copy link
Member Author

I'm not sure I understand.

consider #17658 where the internal type was changed from long to time_t in 3.0
while the public API kept long. That is just plain broken.
On the other hand 1.1.1 has long in the API and in the internal representation,
that is at least consistent, and can be fixed by using a cyclic time model.
See #17703 where the time-out computation avoids the undefined long overflow
and uses a cyclic time, instead of a linear time, with an end date.

@t8m
Copy link
Member

t8m commented Feb 28, 2022

Needs second approval.

@@ -803,7 +803,7 @@ int tls_parse_ctos_cookie(SSL *s, PACKET *pkt, unsigned int context, X509 *x,

/* We tolerate a cookie age of up to 10 minutes (= 60 * 10 seconds) */
now = (unsigned long)time(NULL);
if (tm > now || (now - tm) > 600) {
if ((uint32_t)(now - tm) > 600) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this cast needed? now - tm is already evaluated in unsigned long, and I don't see that casting the result of the subtraction is needed in order to compare against 600.

Copy link
Contributor

@tmshort tmshort Feb 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Internally, we should really be using time_t and not casting to unsigned long or uint32_t unless it's being returned from a public API.

consider #17658 where the internal type was changed from long to time_t in 3.0 while the public API kept long. That is just plain broken.

Yes and no. The public API is certainly broken; the internal use of time_t isn't. I don't think there was a will to change public APIs to use time_t in the 3.0 release, due to backwards compatibility concerns. Although we did change the options to use uint64_t...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Examples such as #8687 updated the types for SSL_SESSION to be time_t; as an example of how to deal with this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this cast needed? now - tm is already evaluated in unsigned long, and I don't see that casting the result of the subtraction is needed in order to compare against 600.

although tm is of type unsigned long it is only a 4 byte value, see here:

|| !PACKET_get_net_4(&cookie, &tm)

and here:
__owur static ossl_inline int PACKET_peek_net_4(const PACKET *pkt,
unsigned long *data)
{
if (PACKET_remaining(pkt) < 4)
return 0;
*data = ((unsigned long)(*pkt->curr)) << 24;
*data |= ((unsigned long)(*(pkt->curr + 1))) << 16;
*data |= ((unsigned long)(*(pkt->curr + 2))) << 8;
*data |= *(pkt->curr + 3);
return 1;

Well, it will not break in 2038, but in 2106, but it will eventually break.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Examples such as #8687 updated the types for SSL_SESSION to be time_t; as an example of how to deal with this.

This would only be okay if the API changes as well. If the API cannot change, this simply breaks the API.
When the API cannot change, we should make the internal time representation a cyclic time model.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

although tm is of type unsigned long it is only a 4 byte value, see here:

Yes, I saw that before I commented.

Please spell out a plausible scenario where the result of evaluating (uint32_t)(now - tm) > 600 and the result of evaluating (now - tm) > 600 will be different. We're measuring the delay between when we issued the cookie and when we're processing the copy sent back to us. If our clock went backwards, both expressions will produce a large value for the LHS since the nominally negative value "wraps around" in unsigned arithmetic, and it seems physically implausible for us to wait more than 2^32 seconds between issuing and receiving the cookie.

I don't see a part of this particular codeline that is using absolute time and thus subject to 2038 or 2106 issues -- I only see unsigned arithmetic that's comparing time differences against a duration.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I really thought that would be obvious:
let unsigned long be 64 byte long, and time_t be 64 byte long as well,
and the year after 2106.
Then (now - tm) will always be > 2^32.
QED.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it will not break in 2038, but in 2106, but it will eventually break.

This is a flawed assumption. 32-bit time_t is signed, it does not support any date beyond 2038, nor before 1902. you cannot cast a signed time_t to an unsigned type and assume it will work beyond 2038 (as you are ignoring dates before 1970). Most software in a 32-bit time_t system will be completely broken in 2038.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I am of course talking about systems using 64 bit time_t, and this silly code will break, because long is a compiler dependent thing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I really thought that would be obvious:
let unsigned long be 64 byte long, and time_t be 64 byte long as well,
and the year after 2106.

I suppose it probably would have been obvious if I had been primed to think about 2106. But the PR title and the commit's commit message only mentioned 2038...

@kaduk
Copy link
Contributor

kaduk commented Feb 28, 2022

I put an alternative up in #17778 that uses time_t internally for s_time and switches BIO_SSL to using time_t as well.
@bernd-edlinger I'd be interested to hear why you prefer this approach over that one.

@paulidale
Copy link
Contributor

I think the version in #17778 is clearer but I'm okay approving either.
Just so long as both aren't merged :)

@paulidale paulidale added approval: done This pull request has the required number of approvals and removed approval: review pending This pull request needs review by a committer labels Mar 1, 2022
@tmshort
Copy link
Contributor

tmshort commented Mar 1, 2022

Would it make sense to implement our own non-floating point version of difftime(3)?

Possibly a function to compare two time_ts with an offset. e.g.

/* 
 * Returns:
 * -1 if a+offset_a < b+offset_b
 * 0 if a+offset_a == b+offset_b
 * 1 if a+offset_a > b+offset_b
 */
OSSL_time_compare(time_t a, time_t offset_a, time_t b, time_t offset_b)

I'd suspect that one of the offsets would always be 0, as in the case of checking for a finish time:

if (OSSL_time_compare(starttime, duration, time(NULL), 0) <= 0)

Use this (or similar) for all time calculations, and a lot of issues go away. I did something similar in #8687

@tmshort
Copy link
Contributor

tmshort commented Mar 1, 2022

Alternative proposal: #17786 (it's a WIP, and could subsume this work).

@openssl-machine
Copy link
Collaborator

24 hours has passed since 'approval: done' was set, but as this PR has been updated in that time the label 'approval: ready to merge' is not being automatically set. Please review the updates and set the label manually.

@paulidale
Copy link
Contributor

Let's see what the alternative ends up, it looks most promising IMO.

@kaduk
Copy link
Contributor

kaduk commented Mar 2, 2022

Perhaps we should fork off an issue for discussion of what we are actually trying to fix.
We might want to handle any/all of:

  • the system time_t is fine but our code uses narrower types that only hold 32 bits (signed and unsigned to be handled separately, though we might care about both or just signed)
  • the system time_t is signed 32 bit and we want to avoid undefined or implementation defined behavior in our code for times after 2038 (or 2106)
  • the system time_t is 32-bit but the system also provides a time64_t that is 64 bit
  • our code uses non-fixed-width types (e.g., unsigned long) but puts values in them that are constrained to a fixed-width representation (as for the TLS 1.3 Cookie's timestamp)
  • ...

@tmshort
Copy link
Contributor

tmshort commented Mar 2, 2022

  • our code uses non-fixed-width types (e.g., unsigned long) but puts values in them that are constrained to a fixed-width representation (as for the TLS 1.3 Cookie's timestamp)

I tried to fix one of these types of situations; but because of 64-bit longs, it didn't work. The function extracted 4 bytes, so one would think uint32_t* would be an appropriate type, but instead, the argument is unsigned long *. In many of my previous projects, for networking code, we would use explicit uintXX_t types where appropriate. This is generally missing within OpenSSL.

@tmshort
Copy link
Contributor

tmshort commented Mar 2, 2022

  • the system time_t is 32-bit but the system also provides a time64_t that is 64 bit

That thought had come to me too. Possibly use an openssl_time_t type (which could be public), but it then it complicates the APIs. It would make sense to always use 64-bit time regardless of the system, and do appropriate translation. But again, APIs.

@t8m t8m removed the approval: done This pull request has the required number of approvals label Oct 24, 2022
@t8m
Copy link
Member

t8m commented Oct 24, 2022

The approval is stale, this needs to be rebased or abandoned.

@t8m t8m added the branch: 3.1 Merge to openssl-3.1 label Oct 24, 2022
@t8m t8m added the branch: 3.2 Merge to openssl-3.2 label Oct 26, 2023
@t8m
Copy link
Member

t8m commented Apr 30, 2025

@bernd-edlinger please rebase this or close if you are no longer interested in this PR.

@t8m t8m added stalled: awaiting contributor response This pull request is awaiting a response by the contributor branch: 3.3 Merge to openssl-3.3 branch: 3.4 Merge to openssl-3.4 branch: 3.5 Merge to openssl-3.5 and removed branch: 3.1 Merge to openssl-3.1 labels Apr 30, 2025
@openssl-machine
Copy link
Collaborator

This PR is waiting for the creator to make requested changes but it has not been updated for 30 days. If you have made changes or commented to the reviewer please make sure you re-request a review (see icon in the 'reviewers' section).

@openssl-machine
Copy link
Collaborator

This PR is waiting for the creator to make requested changes but it has not been updated for 61 days. If you have made changes or commented to the reviewer please make sure you re-request a review (see icon in the 'reviewers' section).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
branch: master Merge to master branch branch: 3.0 Merge to openssl-3.0 branch branch: 3.2 Merge to openssl-3.2 branch: 3.3 Merge to openssl-3.3 branch: 3.4 Merge to openssl-3.4 branch: 3.5 Merge to openssl-3.5 stalled: awaiting contributor response This pull request is awaiting a response by the contributor triaged: bug The issue/pr is/fixes a bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants

TMZ Celebrity News – Breaking Stories, Videos & Gossip

Looking for the latest TMZ celebrity news? You've come to the right place. From shocking Hollywood scandals to exclusive videos, TMZ delivers it all in real time.

Whether it’s a red carpet slip-up, a viral paparazzi moment, or a legal drama involving your favorite stars, TMZ news is always first to break the story. Stay in the loop with daily updates, insider tips, and jaw-dropping photos.

🎥 Watch TMZ Live

TMZ Live brings you daily celebrity news and interviews straight from the TMZ newsroom. Don’t miss a beat—watch now and see what’s trending in Hollywood.