No Time for Chrono

October 2021

TL;DR: Time 0.1 and 0.2 have a security notice about use of the localtime_r libc function, and Chrono has a related notice, both issued in November 2020. While Time has a mitigation in place, Chrono doesn’t and doesn’t plan to. The security issue is very specific and can be mitigated through your own efforts; there’s also some controversy on if it is an issue at all. The Time crate has evolved a lot in the past few years and its 0.3 release has a lot of APIs that Chrono is used for; thus it is possible for many, but not all, Chrono users to switch to Time 0.3; this could have some additional benefit.

The security issue

From the advisory:

Unix-like operating systems may segfault due to dereferencing a dangling pointer in specific circumstances. This requires an environment variable to be set in a different thread than the affected functions. This may occur without the user’s knowledge, notably in a third-party library.

Non-Unix targets (including Windows and wasm) are unaffected.

Advisory notices

localtime_r and setenv

From the manpage, edited for length:

The localtime_r() function converts the calendar time timep to broken-down time representation, expressed relative to the user’s specified timezone. The function acts as if it called tzset(3) and provides information about the current timezone, the difference between Coordinated Universal Time (UTC) and local standard time in seconds, and whether daylight savings time rules apply during some part of the year.

Meanwhile, setenv:

…adds the variable name to the environment with the value value

The issue occurs when the environment of a program is modified (chiefly with setenv) at the same time that localtime_r is used; in those cases, a segfault may be observed.

localtime_r is a fairly complex beast, which interfaces with the system’s timezone files and settings to provide localtime information and conversion. It is a very useful function that is used pretty much everywhere that needs to interact with local/utc time and timezones. Handling timezones is largely regarded as all programmers’ bane; replacing this function with one that does not have the behaviour is a potentially massive endeavour. Over on IRLO, Tony Arcieri provides a summary of the Rust side of this issue.

Time has mitigated the issue by removing the calls to localtime_r, returning errors in 0.2 and 0.3 unless a custom cfg is passed to the compiler. That does mean that if you do want that information, you’re out of luck unless you (as an application) or all your end-users (as a library) enable that custom configuration.

The counter view

Rich Felker (author of musl) has another view. He argues that the issue is not in calling the localtime_r function, but in modifying the environment. The environment ought to be immutable, and it is somewhat well known in other large projects that the footgun exists:

This issue is even known to the Rust project, with a documentation PR in 2015(!) adding cautionary language to the std::env::set_var function.

I don’t have nearly the same amount of knowledge in this issue, but for the record, and despite the sections below, I do agree with the view that the environment should be considered read-only. Perhaps a clippy lint could be added.

Replacing Chrono

Regardless of the previous discussion, there are other issues around usage of Chrono.

Chronic pains

Its last release at writing, 0.4.19, was more than a year ago. Issues are piling up. It’s still on edition 2015 (which to be clear isn’t really an issue, but more of an indicator).

It could just be that the crate is considered finished (the docs do describe it as “feature-complete”).

Or it may be that maintainers have mostly checked out. (No fault on the maintainers! I’ve done the same with Notify, once.)

If you’re fine with this, and you’re confident that you (and your dependencies) aren’t writing to the environment, then you can keep on using Chrono. There is, however, a viable alternative now:

Time 0.3

Time’s 0.3 release adds many APIs, which cover a large amount of the surface that Chrono is used for:

  • No-alloc mode
  • The Month type
  • Calendar/Ordinal/ISO/Julian conversions
  • Large dates (beyond +/- 9999 years)
  • Parsing and serde support

There are also some features which are only supported by newer Time, not by Chrono:

  • const functions
  • the datetime! macro for constructing datetimes at compile-time
  • Serialising non-ISO8601 representations
  • Random dates/times
  • QuickCheck support

Therefore, you can now reasonable replace Chrono with Time!

(In the future I hope to provide a “quick migration guide” here. For now, it’s left to the reader!)