-
-
Notifications
You must be signed in to change notification settings - Fork 10.6k
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
base: master
Are you sure you want to change the base?
Conversation
Some arithmetics using long when this type is 32 bit and time_t is 64 bit might break after 2038.
Would it make sense to implement our own non-floating point version of |
normally I would simply use time_t, but that does not work because of the frozen ABI. |
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
consider #17658 where the internal type was changed from long to time_t in 3.0 |
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 inunsigned 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:
openssl/ssl/statem/extensions_srvr.c
Line 796 in d2d2401
|| !PACKET_get_net_4(&cookie, &tm) |
and here:
openssl/include/internal/packet.h
Lines 217 to 228 in d2d2401
__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.
There was a problem hiding this comment.
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 betime_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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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...
I put an alternative up in #17778 that uses time_t internally for |
I think the version in #17778 is clearer but I'm okay approving either. |
Possibly a function to compare two
I'd suspect that one of the offsets would always be 0, as in the case of checking for a finish time:
Use this (or similar) for all time calculations, and a lot of issues go away. I did something similar in #8687 |
Alternative proposal: #17786 (it's a WIP, and could subsume this work). |
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. |
Let's see what the alternative ends up, it looks most promising IMO. |
Perhaps we should fork off an issue for discussion of what we are actually trying to fix.
|
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 |
That thought had come to me too. Possibly use an |
The approval is stale, this needs to be rebased or abandoned. |
@bernd-edlinger please rebase this or close if you are no longer interested in this PR. |
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). |
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). |
Some arithmetics using long when this type is 32
bit and time_t is 64 bit might break after 2038.