You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This finding was identified during an agentic unsafe Rust code review performed by Gemini AI, followed by human review and verification.
Both of these lead to panics siince they're const-only, however it's still worth fixing since it's still technically UB.
Issue 1 (time)
This leads to a const panic because rustc_layout_scalar_valid_range_max applies checks in consts. We should not rely on that though, since it's still UB and the deranged crate uses assert_unchecked.
When parsing fractional seconds in time!(...) literals, macro parsing in src/time.rs parses the second component as an f64 and validates second < 60.0. It then computes the nanosecond field using double-precision arithmetic (second.fract() * Nanosecond::per_t::<f64>(Second)).round() as u32.
Due to the rounding behavior of .round(), input literals strictly less than 60 seconds with trailing nines (such as time!(12:00:59.99999999999999)) successfully pass the second < 60.0 validation check. However, second.fract() * 1_000_000_000.0 yields a value sufficiently close to 1 billion that .round() rounds up to 1_000_000_000.0, resulting in nanosecond == 1_000_000_000.
The emitted token stream below generates an unsafe block calling ::time::Time::__from_hms_nanos_unchecked(12, 0, 59, 1000000000). The documented # Safety contract of __from_hms_nanos_unchecked explicitly requires nanoseconds to be within the range 0..=999_999_999. In time::Time, the nanosecond field is backed by a layout-constrained type (deranged::RangedU32<0, 999_999_999>). Constructing a value with 1_000_000_000 violates the #[rustc_layout_scalar_valid_range_end(999_999_999)] compiler layout invariant, causing immediate Undefined Behavior or const-eval compile-time traps.
use time::macros::time;fnmain(){// Input strictly less than 60 seconds (59.99999999999999) passes macro validation// `second < 60.0`. However, floating-point calculation `(second.fract() * 1_000_000_000.0).round()`// rounds up to 1_000_000_000, emitting `__from_hms_nanos_unchecked(12, 0, 59, 1_000_000_000)`.// This violates layout scalar valid range invariants (0..=999_999_999), causing Undefined Behavior// or compile-time evaluation failure.let _t = time!(12:00:59.99999999999999);}
error[E0080]: `assume` called with `false`
--> src/bin/repro1.rs:9:14
|
9 | let _t = time!(12:00:59.99999999999999);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `main::{constant#0}` failed here
|
= note: this error originates in the macro `time` (in Nightly builds, run with -Z macro-backtrace for more info)
note: erroneous constant encountered
--> src/bin/repro1.rs:9:14
|
9 | let _t = time!(12:00:59.99999999999999);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this note originates in the macro `time` (in Nightly builds, run with -Z macro-backtrace for more info)
note: erroneous constant encountered
--> src/bin/repro1.rs:9:14
|
9 | let _t = time!(12:00:59.99999999999999);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
= note: this note originates in the macro `time` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.
Issue 2 (date)
This one seems to hit an assertion, but it's fragile since we're still violating the safety invariant on __from_ordinal_date_unchecked.
When parsing ISO week dates (such as date!(+999999-W52-7)), date::parse validates year.abs() <= MAX_YEAR and delegates ordinal calculation to ywd_to_yo(year, week, day) at
generates an unsafe block calling ::time::Date::__from_ordinal_date_unchecked(1000000, ...). The documented # Safety contract of __from_ordinal_date_unchecked mandates that year must be within MIN_YEAR..=MAX_YEAR. In time::Date, internal date fields are bitpacked into a core::num::NonZero<i32>. Passing out-of-bounds year values violates internal bitpacking layout constraints and safety preconditions, causing immediate Undefined Behavior or const-eval compile-time traps.
Minimal Reproduction
Minimal Reproduction
use time::macros::date;fnmain(){// When parsing ISO week dates (e.g., date!(+999999-W52-7)), date::parse validates year.abs() <= MAX_YEAR.// However, in ywd_to_yo, if week 52 day 7 belongs to the subsequent calendar year, the calculation wraps around// returning year + 1 (1000000). The macro emits ::time::Date::__from_ordinal_date_unchecked(1000000, ...).// This violates internal bitpacking contracts and safety preconditions (MIN_YEAR..=MAX_YEAR), leading to// Undefined Behavior or compile-time const-eval failure.let _d = date!(+999999-W52-7);}
error[E0080]: evaluation panicked: assertion failed: year <= MAX_YEAR
--> src/bin/repro2.rs:9:14
|
9 | let _d = date!(+999999-W52-7);
| ^^^^^^^^^^^^^^^^^^^^ evaluation of `main::{constant#0}` failed here
|
= note: this error originates in the macro `date` (in Nightly builds, run with -Z macro-backtrace for more info)
note: erroneous constant encountered
--> src/bin/repro2.rs:9:14
|
9 | let _d = date!(+999999-W52-7);
| ^^^^^^^^^^^^^^^^^^^^
|
= note: this note originates in the macro `date` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0080`.
Note
The full audit report below also contains additional minor findings (such as missing safety comments or undocumented FFI assumptions) that are probably worth fixing as well but not the primary goal of this issue. The audit report has not been human-reviewed, it may contain misleading claims.
Full Gemini Unsafe Code Audit Report
Unsafe Rust Review: time_macros (v0_2)
Overall Safety Assessment
time_macros (v0_2) is a procedural macro companion crate (#[proc_macro]) for the time crate. It provides compile-time macros such as date!, time!, datetime!, offset!, and format_description! that parse date and time string literals at compile time.
During macro execution within rustc, the crate operates entirely in safe Rust. However, to construct downstream time::Date, time::Time, and time::UtcOffset values without runtime validation overhead or Option::unwrap branching, the emitted token streams generate unsafe { ... } blocks invoking hidden unchecked constructors in time (specifically __from_ordinal_date_unchecked, __from_hms_nanos_unchecked, and __from_hms_unchecked).
Under the proof-obligation principles of unsafe Rust auditing, a procedural macro that emits unsafe code into a caller's crate assumes the formal proof obligation of verifying all safety preconditions of the emitted functions prior to code generation. The macro's compile-time input parsing and validation logic constitutes the entire safety proof.
Our audit revealed two Critical Findings where this validation logic is unsound. Specifically, floating-point rounding during fractional second parsing and boundary wrapping during ISO week date conversions allow malformed or out-of-bounds component values to be emitted into unchecked constructors. In downstream time structures, these components are backed by layout-constrained types (deranged::RangedU32 and core::num::NonZero). Emitting out-of-range values into these constructors violates #[rustc_layout_scalar_valid_range] compiler layout invariants, causing Undefined Behavior (or compile-time const-evaluation failures when evaluated inside const { ... } blocks). Additionally, none of the quote blocks generating unsafe code provide safety comments justifying their proof obligations.
Critical Findings
1. Soundness Violation via Floating-Point Rounding in time! Macro π΄ π€¦
Severity: π΄ High
Threat Vector: π€¦ Accidental Misuse
Bug Type: Scalar Range Violation
Location: src/time.rs:101-102 (parsing logic), emitted at src/time.rs:110-117
Description: When parsing fractional seconds in time!(...) literals, parse() parses the second component as an f64 and validates second < 60.0. It then computes the nanosecond field using double-precision arithmetic:
Due to IEEE 754 64-bit floating-point representation limits and rounding behavior of .round(), input literals strictly less than 60 seconds with extensive trailing nines (e.g., time!(12:00:59.99999999999999)) successfully pass the second >= 60.0 validation check. However, second.fract() * 1_000_000_000.0 yields a value sufficiently close to 1 billion that .round() rounds up to 1_000_000_000.0, resulting in nanosecond == 1_000_000_000.
Soundness Impact: The emitted token stream calls ::time::Time::__from_hms_nanos_unchecked(12, 0, 59, 1000000000). The documented # Safety contract of __from_hms_nanos_unchecked explicitly requires nanoseconds to be in the range 0..=999_999_999. In time::Time, nanosecond is stored as a deranged::RangedU32<0, 999_999_999>. Constructing a RangedU32 with 1_000_000_000 violates the #[rustc_layout_scalar_valid_range_end(999_999_999)] attribute on the underlying scalar. Creating an invalid scalar value is immediate Undefined Behavior in Rust semantics. When emitted inside Rust 2024 const { ... } blocks, rustc const-eval traps the invalid scalar construction and aborts compilation with a const-eval ICE/failure rather than a proper macro compile_error!. If emitted in non-const contexts or older compiler toolchains, it causes runtime misoptimizations and niche optimization poisoning.
2. Soundness Violation via ISO Week Date Year Wrapping in date! Macro π΄ π€¦
Severity: π΄ High
Threat Vector: π€¦ Accidental Misuse
Bug Type: Missing Boundary Validation
Location: src/helpers/mod.rs:109-123 (ywd_to_yo), emitted at src/date.rs:154-159
Description: When parsing ISO week dates (e.g., date!(2020-W01-1)), date::parse validates year.abs() <= MAX_YEAR and delegates ordinal calculation to ywd_to_yo(year, week, day). In ywd_to_yo, if week 1 day 1 belongs to the preceding calendar year, the function wraps around:
if overflow || ordinal == 0{return(year - 1,(ordinal.wrapping_add(days_in_year(year - 1))));}
If year is at the minimum representable bound (-9999 by default, or -999_999 with large-dates), year - 1 becomes -10000 (or -1_000_000). Similarly, if an ISO week date at MAX_YEAR falls into the subsequent calendar year, ywd_to_yo returns MAX_YEAR + 1.
Soundness Impact: The macro emits ::time::Date::__from_ordinal_date_unchecked(-10000, ...). The # Safety contract of __from_ordinal_date_unchecked mandates that year must be within MIN_YEAR..=MAX_YEAR. In time::Date, internal date fields are bitpacked into a core::num::NonZero<i32>. Passing out-of-bounds year values violates internal bitpacking contracts and niche assumptions, leading to invalid memory representation and Undefined Behavior.
Fishy Findings
None.
Missing Safety Comments
The crate contains four quote blocks that generate unsafe blocks. None of them include a // SAFETY: comment explaining why the emitted unsafe code satisfies its proof obligations.
src/time.rs:110: Missing safety comment for emitted call to ::time::Time::__from_hms_nanos_unchecked. π΄
Proposed Proof Comment: // SAFETY: The hour, minute, second, and nanosecond components are rigorously validated during macro parsing to be within 0..=23, 0..=59, 0..=59, and 0..=999_999_999 respectively, satisfying the exact safety preconditions of __from_hms_nanos_unchecked.
src/date.rs:154: Missing safety comment for emitted call to ::time::Date::__from_ordinal_date_unchecked. π΄
Proposed Proof Comment: // SAFETY: The year and ordinal values are validated during macro parsing to be within MIN_YEAR..=MAX_YEAR and 1..=days_in_year(year) respectively, satisfying the exact safety preconditions of __from_ordinal_date_unchecked.
src/offset.rs:88: Missing safety comment for emitted call to ::time::UtcOffset::__from_hms_unchecked. π΄
Proposed Proof Comment: // SAFETY: The hours, minutes, and seconds components are validated during macro parsing to be within -25..=25, -59..=59, and -59..=59 respectively with all components sharing the same sign, satisfying the exact safety preconditions of __from_hms_unchecked.
src/to_tokens.rs:53: Missing safety comment for emitted call to ::core::num::NonZero::<u16>::new_unchecked. π΄
Proposed Proof Comment: // SAFETY: self is a valid NonZero<u16>, ensuring self.get() > 0, which satisfies the safety requirement of NonZero::new_unchecked.
Note
This finding was identified during an agentic unsafe Rust code review performed by Gemini AI, followed by human review and verification.
Both of these lead to panics siince they're const-only, however it's still worth fixing since it's still technically UB.
Issue 1 (time)
This leads to a const panic because
rustc_layout_scalar_valid_range_maxapplies checks in consts. We should not rely on that though, since it's still UB and thederangedcrate usesassert_unchecked.When parsing fractional seconds in
time!(...)literals, macro parsing insrc/time.rsparses the second component as anf64and validatessecond < 60.0. It then computes the nanosecond field using double-precision arithmetic(second.fract() * Nanosecond::per_t::<f64>(Second)).round() as u32.time/time-macros/src/time.rs
Lines 101 to 102 in d5144cd
Due to the rounding behavior of
.round(), input literals strictly less than 60 seconds with trailing nines (such astime!(12:00:59.99999999999999)) successfully pass thesecond < 60.0validation check. However,second.fract() * 1_000_000_000.0yields a value sufficiently close to 1 billion that.round()rounds up to1_000_000_000.0, resulting innanosecond == 1_000_000_000.The emitted token stream below generates an
unsafeblock calling::time::Time::__from_hms_nanos_unchecked(12, 0, 59, 1000000000). The documented# Safetycontract of__from_hms_nanos_uncheckedexplicitly requiresnanosecondsto be within the range0..=999_999_999. Intime::Time, thenanosecondfield is backed by a layout-constrained type (deranged::RangedU32<0, 999_999_999>). Constructing a value with1_000_000_000violates the#[rustc_layout_scalar_valid_range_end(999_999_999)]compiler layout invariant, causing immediate Undefined Behavior or const-eval compile-time traps.time/time-macros/src/time.rs
Lines 110 to 117 in d5144cd
Minimal Reproduction (Miri)
Minimal Reproduction (Miri)
Issue 2 (date)
This one seems to hit an assertion, but it's fragile since we're still violating the safety invariant on
__from_ordinal_date_unchecked.When parsing ISO week dates (such as
date!(+999999-W52-7)),date::parsevalidatesyear.abs() <= MAX_YEARand delegates ordinal calculation toywd_to_yo(year, week, day)attime/time-macros/src/helpers/mod.rs
Lines 109 to 123 in d5144cd
ywd_to_yo, if the target date falls into the subsequent calendar year, the function wraps around returningyear + 1.If
yearis at the maximum representable bound (+9999by default, or+999_999withlarge-dates),year + 1becomes10000(or1000000).The emitted token stream at
time/time-macros/src/date.rs
Lines 154 to 159 in d5144cd
unsafeblock calling::time::Date::__from_ordinal_date_unchecked(1000000, ...). The documented# Safetycontract of__from_ordinal_date_uncheckedmandates thatyearmust be withinMIN_YEAR..=MAX_YEAR. Intime::Date, internal date fields are bitpacked into acore::num::NonZero<i32>. Passing out-of-bounds year values violates internal bitpacking layout constraints and safety preconditions, causing immediate Undefined Behavior or const-eval compile-time traps.Minimal Reproduction
Minimal Reproduction
Note
The full audit report below also contains additional minor findings (such as missing safety comments or undocumented FFI assumptions) that are probably worth fixing as well but not the primary goal of this issue. The audit report has not been human-reviewed, it may contain misleading claims.
Full Gemini Unsafe Code Audit Report
Unsafe Rust Review:
time_macros(v0_2)Overall Safety Assessment
time_macros(v0_2) is a procedural macro companion crate (#[proc_macro]) for thetimecrate. It provides compile-time macros such asdate!,time!,datetime!,offset!, andformat_description!that parse date and time string literals at compile time.During macro execution within rustc, the crate operates entirely in safe Rust. However, to construct downstream
time::Date,time::Time, andtime::UtcOffsetvalues without runtime validation overhead orOption::unwrapbranching, the emitted token streams generateunsafe { ... }blocks invoking hidden unchecked constructors intime(specifically__from_ordinal_date_unchecked,__from_hms_nanos_unchecked, and__from_hms_unchecked).Under the proof-obligation principles of unsafe Rust auditing, a procedural macro that emits
unsafecode into a caller's crate assumes the formal proof obligation of verifying all safety preconditions of the emitted functions prior to code generation. The macro's compile-time input parsing and validation logic constitutes the entire safety proof.Our audit revealed two Critical Findings where this validation logic is unsound. Specifically, floating-point rounding during fractional second parsing and boundary wrapping during ISO week date conversions allow malformed or out-of-bounds component values to be emitted into unchecked constructors. In downstream
timestructures, these components are backed by layout-constrained types (deranged::RangedU32andcore::num::NonZero). Emitting out-of-range values into these constructors violates#[rustc_layout_scalar_valid_range]compiler layout invariants, causing Undefined Behavior (or compile-time const-evaluation failures when evaluated insideconst { ... }blocks). Additionally, none of the quote blocks generatingunsafecode provide safety comments justifying their proof obligations.Critical Findings
1. Soundness Violation via Floating-Point Rounding in
time!Macro π΄ π€¦Severity: π΄ High
Threat Vector: π€¦ Accidental Misuse
Bug Type:
Scalar Range ViolationLocation:
src/time.rs:101-102(parsing logic), emitted atsrc/time.rs:110-117Description: When parsing fractional seconds in
time!(...)literals,parse()parses the second component as anf64and validatessecond < 60.0. It then computes the nanosecond field using double-precision arithmetic:Due to IEEE 754 64-bit floating-point representation limits and rounding behavior of
.round(), input literals strictly less than 60 seconds with extensive trailing nines (e.g.,time!(12:00:59.99999999999999)) successfully pass thesecond >= 60.0validation check. However,second.fract() * 1_000_000_000.0yields a value sufficiently close to 1 billion that.round()rounds up to1_000_000_000.0, resulting innanosecond == 1_000_000_000.Soundness Impact: The emitted token stream calls
::time::Time::__from_hms_nanos_unchecked(12, 0, 59, 1000000000). The documented# Safetycontract of__from_hms_nanos_uncheckedexplicitly requiresnanosecondsto be in the range0..=999_999_999. Intime::Time,nanosecondis stored as aderanged::RangedU32<0, 999_999_999>. Constructing aRangedU32with1_000_000_000violates the#[rustc_layout_scalar_valid_range_end(999_999_999)]attribute on the underlying scalar. Creating an invalid scalar value is immediate Undefined Behavior in Rust semantics. When emitted inside Rust 2024const { ... }blocks, rustc const-eval traps the invalid scalar construction and aborts compilation with a const-eval ICE/failure rather than a proper macrocompile_error!. If emitted in non-const contexts or older compiler toolchains, it causes runtime misoptimizations and niche optimization poisoning.2. Soundness Violation via ISO Week Date Year Wrapping in
date!Macro π΄ π€¦Severity: π΄ High
Threat Vector: π€¦ Accidental Misuse
Bug Type:
Missing Boundary ValidationLocation:
src/helpers/mod.rs:109-123(ywd_to_yo), emitted atsrc/date.rs:154-159Description: When parsing ISO week dates (e.g.,
date!(2020-W01-1)),date::parsevalidatesyear.abs() <= MAX_YEARand delegates ordinal calculation toywd_to_yo(year, week, day). Inywd_to_yo, if week 1 day 1 belongs to the preceding calendar year, the function wraps around:If
yearis at the minimum representable bound (-9999by default, or-999_999withlarge-dates),year - 1becomes-10000(or-1_000_000). Similarly, if an ISO week date atMAX_YEARfalls into the subsequent calendar year,ywd_to_yoreturnsMAX_YEAR + 1.Soundness Impact: The macro emits
::time::Date::__from_ordinal_date_unchecked(-10000, ...). The# Safetycontract of__from_ordinal_date_uncheckedmandates thatyearmust be withinMIN_YEAR..=MAX_YEAR. Intime::Date, internal date fields are bitpacked into acore::num::NonZero<i32>. Passing out-of-bounds year values violates internal bitpacking contracts and niche assumptions, leading to invalid memory representation and Undefined Behavior.Fishy Findings
None.
Missing Safety Comments
The crate contains four quote blocks that generate
unsafeblocks. None of them include a// SAFETY:comment explaining why the emitted unsafe code satisfies its proof obligations.src/time.rs:110: Missing safety comment for emitted call to::time::Time::__from_hms_nanos_unchecked. π΄// SAFETY: The hour, minute, second, and nanosecond components are rigorously validated during macro parsing to be within 0..=23, 0..=59, 0..=59, and 0..=999_999_999 respectively, satisfying the exact safety preconditions of __from_hms_nanos_unchecked.src/date.rs:154: Missing safety comment for emitted call to::time::Date::__from_ordinal_date_unchecked. π΄// SAFETY: The year and ordinal values are validated during macro parsing to be within MIN_YEAR..=MAX_YEAR and 1..=days_in_year(year) respectively, satisfying the exact safety preconditions of __from_ordinal_date_unchecked.src/offset.rs:88: Missing safety comment for emitted call to::time::UtcOffset::__from_hms_unchecked. π΄// SAFETY: The hours, minutes, and seconds components are validated during macro parsing to be within -25..=25, -59..=59, and -59..=59 respectively with all components sharing the same sign, satisfying the exact safety preconditions of __from_hms_unchecked.src/to_tokens.rs:53: Missing safety comment for emitted call to::core::num::NonZero::<u16>::new_unchecked. π΄// SAFETY: self is a valid NonZero<u16>, ensuring self.get() > 0, which satisfies the safety requirement of NonZero::new_unchecked.