Skip to content

Comments

Fix use of time_t internally and APIs#17786

Closed
tmshort wants to merge 5 commits intoopenssl:masterfrom
tmshort:fix-time_t-apis
Closed

Fix use of time_t internally and APIs#17786
tmshort wants to merge 5 commits intoopenssl:masterfrom
tmshort:fix-time_t-apis

Conversation

@tmshort
Copy link
Contributor

@tmshort tmshort commented Mar 1, 2022

WIP: to get initial feedback on how to handle time.

This WIP PR changes internal use of time to use time_t. Replacement public APIs (for backwards compatibility) are introduced to allow time_t through the system.

A new function OPENSSL_time_t_compare() is defined that accepts time_t values and offsets and can be used anywhere comparison of time is necessary.

There's still work to be done, but it's a proposal. Tests and documentation will also be updated.

Checklist
  • documentation is added or updated
  • tests are added or updated

@tmshort
Copy link
Contributor Author

tmshort commented Mar 1, 2022

I'm expecting most of the checks to fail, since this is incomplete.

This changes internal times to use time_t. Replacement public APIs
(for backwards compatibility) are introduced to allow time_t throughout
the system.

A new function OPENSSL_time_t_compare() is defined that accepts time_t
values and offsets and can be used anywhere comparison of time is
necessary.
@tmshort tmshort changed the title WIP: Fix use of time_t internally and APIs Fix use of time_t internally and APIs Mar 2, 2022
@tmshort
Copy link
Contributor Author

tmshort commented Mar 2, 2022

I still need to update util/missingcrypto.txt, but will wait on that if there are additional changes... as everything passes checks now. :D

size_t rawlen, hmaclen, hrrlen, ciphlen;
unsigned long tm, now;
time_t now;
unsigned long tm;
Copy link
Contributor Author

@tmshort tmshort Mar 2, 2022

Choose a reason for hiding this comment

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

PACKET_get_net_4() has an unsigned long * argument, which can be either 32-bit or 64-bit depending on the system; so a temporary variable is needed. That function (and related functions) should probably be changed to always use a sized-specified (e.g. 32-bit) type. A temporary is needed regardless as time_t could be 64-bit or 32-bit.

@kroeckx
Copy link
Member

kroeckx commented Mar 2, 2022

#17778 contains some of the same changes.

We now seem to have 3 PRs that want to do something about the Y2038 problem.

I'm currently not convinced that using time_t itself is the way to go ahead. My understanding is that at least glibc requires you to set a define to change from a 32 to 64 bit time_t on platforms that started as 32 bit. We probably want to change all platforms to 64 bit at some point. But if we use time_t in our public APIs, that's an ABI change. The only solution that I currently see is that we create our own type that's guaranteed to be 64 bit in the public APIs and deprecate the others.

@tmshort
Copy link
Contributor Author

tmshort commented Mar 2, 2022

#17778 contains some of the same changes.

Yes, this includes the changes from that PR.

This is more comprehensive, adding new APIs to use time_t-types, so it maintains ABI compatibility. It would be easy to update the new APIs to move from time_t to e.g. openssl_time_t, defined as a 64-bit type.

The question becomes, does OpenSSL want to standardize on a time-type, or keep the APIs the current mish-mash that they are. The ASN.1 and X509 APIs are fairly consistent in their use of time_t

@tmshort tmshort added approval: otc review pending approval: review pending This pull request needs review by a committer branch: master Applies to master branch labels Mar 4, 2022
Copy link
Member

@bernd-edlinger bernd-edlinger left a comment

Choose a reason for hiding this comment

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

I think this approach is way too complicated

if (tm > now || (now - tm) > 600) {
now = time(NULL);
if (OPENSSL_time_t_compare(now, 0, (time_t)tm, 0) < 0
|| OPENSSL_time_t_compare(now, 0, (time_t)tm, 600) > 0) {
Copy link
Member

Choose a reason for hiding this comment

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

This can't work because this comparison needs to be done in 32 bit arithmetic.

Copy link
Contributor Author

@tmshort tmshort Mar 8, 2022

Choose a reason for hiding this comment

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

No, it doesn't; it should be done in 64-bit arithmetic, regardless of time_t size.
Can you please explain why 32-bit arithmetic needs to be used?

Copy link
Contributor

@kaduk kaduk Mar 8, 2022

Choose a reason for hiding this comment

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

The time stored in the cookie is currently a uint32_t. But we could easily change that...

Copy link
Contributor Author

@tmshort tmshort Mar 8, 2022

Choose a reason for hiding this comment

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

The variable tm is a long, on 64-bit system, but stores only a 32-bit value, because of this:

PACKET_get_net_4(&cookie, &tm)

The PACKET_get_net_4() function really ought to only use an int32_t* or uint32_t*, but takes a unsigned long* which is 64-bits. So, the cookie time value is put into a 64-bit value on a 64-bit system.

Copy link
Contributor Author

@tmshort tmshort Mar 8, 2022

Choose a reason for hiding this comment

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

Ok, the original code was likely also problematic, because on a 64-bit system only 4 bytes of the 8-byte unsigned long were filled in, and thus the 32-bit cookie time value was not sign-extended. Because it's not sign-extended, the code treats times pre-1970 as post-2038 (EDIT: on 32-bit systems).

Copy link
Contributor Author

@tmshort tmshort Mar 8, 2022

Choose a reason for hiding this comment

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

The time stored in the cookie is currently a uint32_t. But we could easily change that...

Yeah, it probably should be a uint64_t, but that would make this much more complicated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The time is put into the cookie as:

WPACKET_put_bytes_u32(pkt, (unsigned int)time(NULL))

Past 2038, for a 64-bit time_t, when it's extracted out, it will continue to be a treated properly, because it's not sign-extended when it's extracted. This effectively gives OpenSSL's cookie an 68 extra years (until 2106).

Past 2038, for a 32-bit time_t, there are problems, as we don't know what the behavior of time(NULL) is, it could wrap, it could give -1, it could give MAX_INT.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The value in tm is always going to be only 32-bits, regardless of sizeof(tm).
Common situations:
If sizeof(unsigned long) == sizeof(time_t) == 4 then the system can only safely handle time until 2038; as the time value put into the cookie is unknown.
If sizeof(unsigned long) == sizeof(time_t) == 8 then the code can only safely handle time until 2106. The unsigned tm is not sign-extended when converted to a time_t. The subsequent 64-bit arithmetic works.
Less common situations:
If sizeof(unsigned long) == 4 && sizeof(time_t) == 8 then the code can only safely handle time until 2106. The unsigned tm is not signed extended when converted to a time_t. The subsequent 64-bit arithmetic works.
if sizeof(unsigned long) == 8 && sizeof(time_t) == 4, then the system can only safely handle time until 2038; as the time value put into the cookie is unknown.

Copy link
Member

Choose a reason for hiding this comment

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

The sizeof(unsigned long) == 4 && sizeof(time_t) == 8 is actually common for "recent" 32 bit architecture.

Is there a reason we can't just change tm to an uint64_t?

@tmshort
Copy link
Contributor Author

tmshort commented Mar 8, 2022

I think this approach is way too complicated

This approach provides a consistent mechanism for using time_t.
It makes all internal time calculations use the standard time_t.

I disagree with the complexity. Admittedly, it is "big", but OpenSSL ought to move to using proper and consistent types for time (e.g. time_t), rather than mixing different types.

@rsbeckerca
Copy link
Contributor

rsbeckerca commented Mar 8, 2022

I think this approach is way too complicated

This approach provides a consistent mechanism for using time_t. It makes all internal time calculations use the standard time_t.

I disagree with the complexity. Admittedly, it is "big", but OpenSSL ought to move to using proper and consistent types for time (e.g. time_t), rather than mixing different types.

FWIW: Some platforms use time64_t (z/OS, NonStop) for their 64-bit times for historical reasons. Some (z/OS) use time64_t time64(NULL) for fetching 64-bit time values with a _LARGE_TIME_API knob. There is no decided solution for getting 64-bit times yet on NonStop although one could be made to work with time64_t. AFIAK, these types are signed.

@tmshort
Copy link
Contributor Author

tmshort commented Mar 8, 2022

FWIW: Some platforms use time64_t (z/OS, NonStop) for their 64-bit times for historical reasons. Some (z/OS) use time64_t time64(NULL) for fetching 64-bit time values with a _LARGE_TIME_API knob. There is no decided solution for getting 64-bit times yet on NonStop although one could be made to work with time64_t. AFIAK, these types are signed.

I think that if OpenSSL were to adopt it's own 64-bit time type and APIs, it would use time64_t where appropriate. Linux doesn't have a time64_t as an example, so this ends up being an abstraction layer. Now... that would be complicated.

@rsbeckerca
Copy link
Contributor

FWIW: Some platforms use time64_t (z/OS, NonStop) for their 64-bit times for historical reasons. Some (z/OS) use time64_t time64(NULL) for fetching 64-bit time values with a _LARGE_TIME_API knob. There is no decided solution for getting 64-bit times yet on NonStop although one could be made to work with time64_t. AFIAK, these types are signed.

I think that if OpenSSL were to adopt it's own 64-bit time type and APIs, it would use time64_t where appropriate. Linux doesn't have a time64_t as an example, so this ends up being an abstraction layer. Now... that would be complicated.

It's worse. On some 64-bit platforms, sizeof(unsigned long) == 4 and sizeof(unsigned long long) == 8.

@tmshort
Copy link
Contributor Author

tmshort commented Mar 8, 2022

It's worse. On some 64-bit platforms, sizeof(unsigned long) == 4 and sizeof(unsigned long long) == 8.

You mean... Windows? (LLP64)

In this case, we aren't dealing with unsigned long long... I did evaluate the sizes of the types in question for one of the code comments above.

@rsbeckerca
Copy link
Contributor

It's worse. On some 64-bit platforms, sizeof(unsigned long) == 4 and sizeof(unsigned long long) == 8.

You mean... Windows? (LLP64)

In this case, we aren't dealing with unsigned long long... I did evaluate the sizes of the types in question for one of the code comments above.

From my testing, the defaults on z/OS and NonStop also are like that, with long being 4 bytes. So more than just Windows.

@kroeckx
Copy link
Member

kroeckx commented Mar 8, 2022

Linux doesn't have a time64_t as an example

glibc has a __time64_t. But you should always use time_t with the APIs that use a time_t.

glibc sets __TIMESIZE == 32 for architectures that have both 32 and 64 bit time_t, and __TIMESIZE == 64 for those that always have a 64 bit time_t.

You can change time_t to be 64 bit by using _TIME_BITS=64 (also requires _FILE_OFFSET_BITS=64). This is supported since glibc 2.34.

That means that if internally we want to use a 64 bit time_t, we would need to build with -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64. But if we also have time_t in our public API, applications need to have the same defines set, or things will break. We probably want to use a fixed 64 bit type for public APIs.

@rsbeckerca
Copy link
Contributor

Linux doesn't have a time64_t as an example

glibc has a __time64_t. But you should always use time_t with the APIs that use a time_t.

glibc sets __TIMESIZE == 32 for architectures that have both 32 and 64 bit time_t, and __TIMESIZE == 64 for those that always have a 64 bit time_t.

You can change time_t to be 64 bit by using _TIME_BITS=64 (also requires _FILE_OFFSET_BITS=64). This is supported since glibc 2.34.

That means that if internally we want to use a 64 bit time_t, we would need to build with -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64. But if we also have time_t in our public API, applications need to have the same defines set, or things will break. We probably want to use a fixed 64 bit type for public APIs.

Not all platforms have glibc 2.34 - mine certainly does not. I think we are limited to 2.12 or something near there. There are incompatible dependencies that were added forcing gcc that are not available (as in gcc does not port).

@tmshort
Copy link
Contributor Author

tmshort commented Mar 9, 2022

We probably want to use a fixed 64 bit type for public APIs.

That is what I was thinking. An openssl-specific time type (e.g. openssl_time_t).

@tmshort
Copy link
Contributor Author

tmshort commented Mar 9, 2022

From my testing, the defaults on z/OS and NonStop also are like that, with long being 4 bytes. So more than just Windows.

Yes, but Windows has just a few more deployments than those OS's.

@rsbeckerca
Copy link
Contributor

From my testing, the defaults on z/OS and NonStop also are like that, with long being 4 bytes. So more than just Windows.

Yes, but Windows has just a few more deployments than those OS's.

Only a few orders of magnitude ;)

Copy link
Contributor

@kaduk kaduk left a comment

Choose a reason for hiding this comment

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

Without getting too much into the details of the implementation, it seems like the key question for us to answer is whether we want to use time_t in our public APIs.
From a standardization perspective, time_t is clearly the right time to use, but the glibc behavior of making its ABI dependent on a preprocessor symbol, and the decision of some platforms to fix time_t at 32-bits and only offer a 64-bit time64_t muddies the waters quite a bit. On the whole, like Kurt, I'm leaning towards using an int64_t for public APIs.

It does seem clear that we should use time_t internally and convert right at the public-API boundary, and the possibility for a 64-bit time_t drives the choice of int64_t as the type used for the public API.

@t8m t8m added triaged: feature The issue/pr requests/adds a feature triaged: refactor The issue/pr requests/implements refactoring labels Mar 14, 2022
@rsbeckerca
Copy link
Contributor

News: I just had a conversation with the glibc team. They appear to be moving to exclusively Linux support. Other platforms appear to be are excluded for the future. I have an email I can share privately about it. Depending on glibc is not a good idea unless OpenSSL is going down the same path.

@kaduk
Copy link
Contributor

kaduk commented Mar 14, 2022

@rsbeckerca that mail sounds juicy :)
I mention glibc only in that it's the default libc on most major linux distributions, i.e., something we cannot ignore.
I was not proposing that we rely on it as an intrinsic dependency of openssl.

@kaduk
Copy link
Contributor

kaduk commented Mar 14, 2022

It does seem clear that we should use time_t internally and convert right at the public-API boundary, and the possibility for a 64-bit time_t drives the choice of int64_t as the type used for the public API.

Well, hmm, that's not actually quite right. We want to use time64_t internally if available, so I should have said "a custom internal typedef that is usually time_t" here.

@tmshort
Copy link
Contributor Author

tmshort commented Mar 18, 2022

Where do we want to go with time then? The conversation has sputtered out.

@rsbeckerca
Copy link
Contributor

Where do we want to go with time then? The conversation has sputtered out.

My preference is to have something like OPENSSL_time as an explicit type, something like a standard C unsigned long long (64 bits) in some units (nanoseconds, microseconds, or seconds?) from some epoch starting point or signed long long from 1 Jan 1970. Platforms can map to/from that with a wrapper, say OPENSSL_time OPENSSL_from_time_t(time_t val) and time_t OPENSSL_to_time_t(OPENSSL_time), and OPENSSL_time OPENSSL_now() potentially controlled in the Configuration infrastructure to select the time_t style or attributes instead of having knobs for each platform. Depending on time_t being something useful from a portable standpoint does not look viable.

@tmshort
Copy link
Contributor Author

tmshort commented Mar 18, 2022

I think that a common time reference (i.e. POSIX use of seconds since Jan 1, 1970 0:00:00) is at least a good starting point, as it makes translation and API use easier. That would allow SSL_function(time(NULL)) to still be applicable on most (if not all) platforms.

@rsbeckerca
Copy link
Contributor

I think that a common time reference (i.e. POSIX use of seconds since Jan 1, 1970 0:00:00) is at least a good starting point, as it makes translation and API use easier. That would allow SSL_function(time(NULL)) to still be applicable on most (if not all) platforms.

I agree with that. That usage should still up-convert to any current platform representation without change. Where I am going on our platform is trying to implement a 64-bit time_t variant that will work long-term, at least until HPE comes up with their own solution in the future. I can base that off of seconds since Jan 1, 1970 0:00:00 also but run until 3000 at least - they can try to resurrect me to fix it at that point.

@rsbeckerca
Copy link
Contributor

rsbeckerca commented Mar 18, 2022

I checked with the NonStop dev team and time_t is 64 bits if the build explicitly specifies 64-bit builds, which covers 4 of our 15 standard platform builds across 1.1.1x and 3.0.x. We still have an issue with 4 of the 8 3.0.x 32-bit builds that are required for historical reasons and are not going away any time soon and will continue into the 3.1.x series. The 32-bit builds represent the bulk of what is being used on the platform and cannot be change due to a few required 32-bit system legacy libraries that are in use. We can live with the 1.1.1x series being subject to Y2038, so it really is the 4 modern 32-bit builds that are in question.

@bernd-edlinger
Copy link
Member

For our internal use (observing timeouts and so) we only need a projection from the time_t to a 32-bit value.
given two times t0 and t1 we define:

t0 < t1 := (int)((uint32_) t0 - (uint32_t) t1) < 0
t0 > t1 := (int)((uint32_t) t0 - (uint32_t) t1) > 0
t0 == t1 := (int)((uint32_t) t0 - (uint32_t) t1) == 0

that covers 68 years in the future and in the past.
Using these definitions You end up with what I did in #17701

now = (unsigned long)time(NULL);
if ((uint32_t)(now - tm) > 600) {

since (uint32_t)(now - tm) > 600
is equivalent to: now < tm || now > tm + 600
when < and > is defined in terms of the definitions above.

@mspncp
Copy link
Contributor

mspncp commented Mar 25, 2022

@rsbeckerca
Copy link
Contributor

@openssl-machine
Copy link
Collaborator

This PR is in a state where it requires action by @openssl/otc but the last update was 30 days ago

@openssl-machine
Copy link
Collaborator

This PR is in a state where it requires action by @openssl/otc but the last update was 61 days ago

@openssl-machine
Copy link
Collaborator

This PR is in a state where it requires action by @openssl/otc but the last update was 92 days ago

@openssl-machine
Copy link
Collaborator

This PR is in a state where it requires action by @openssl/otc but the last update was 123 days ago

@openssl-machine
Copy link
Collaborator

This PR is in a state where it requires action by @openssl/otc but the last update was 154 days ago

@tmshort tmshort closed this Aug 31, 2022
@rsbeckerca
Copy link
Contributor

Was this intended to be closed without resolution or did I miss something?

@tmshort
Copy link
Contributor Author

tmshort commented Aug 31, 2022

I closed it as I put up a newer version that uses OSSL_TIME internally, #19107

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approval: review pending This pull request needs review by a committer branch: master Applies to master branch severity: fips change The pull request changes FIPS provider sources triaged: feature The issue/pr requests/adds a feature triaged: refactor The issue/pr requests/implements refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants