Skip to content

fix(time): return None on ASN1Time + Duration overflow instead of panicking#252

Merged
chifflier merged 1 commit into
rusticata:masterfrom
scadastrangelove:fix/asn1time-add-checked-add
Jul 22, 2026
Merged

fix(time): return None on ASN1Time + Duration overflow instead of panicking#252
chifflier merged 1 commit into
rusticata:masterfrom
scadastrangelove:fix/asn1time-add-checked-add

Conversation

@scadastrangelove

Copy link
Copy Markdown

fix(time): return None on ASN1Time + Duration overflow instead of panicking

What

impl Add<Duration> for ASN1Time (src/time.rs) has return type Option<ASN1Time>, but the
body was Some(ASN1Time::new(self.time + rhs)). OffsetDateTime + Duration panics on overflow
(internally checked_add(...).expect(...)), so add aborts the thread in exactly the case its
Option signature exists to report. The sibling impl Sub<ASN1Time> right below already guards
and returns None.

     fn add(self, rhs: Duration) -> Option<ASN1Time> {
-        Some(ASN1Time::new(self.time + rhs))
+        self.time.checked_add(rhs).map(ASN1Time::new)
     }

Why it matters

self.time can come from a parsed certificate — e.g. a notAfter of 99991231235959Z
(RFC 5280 §4.1.2.5's conventional "no well-defined expiration"), which parses to a max-year
date. Downstream code doing cert.validity().not_after + grace_period on an untrusted
certificate — a natural "is this cert valid N days from now?" pattern — then panics instead of
getting the None the API promises.

I'd frame this as a correctness/robustness fix rather than a security vulnerability:
x509-parser itself never calls Add (its time_to_expiration uses the checked Sub), so
the impact is confined to downstream callers that use the operator. Filed alongside issue #251.

Test

Adds time::tests::test_add_duration_overflow_returns_none:

  • ASN1Time::from(datetime!(9999-12-31 23:59:59 UTC)) + Duration::days(1)None (was: panic);
  • a normal date + Duration::days(1)Some(..) (unchanged).

Verified locally: cargo test --lib time::3 passed; 0 failed.

Found & fixed with the rust-in-peace security pipeline.

…icking

`impl Add<Duration> for ASN1Time` returns `Option<ASN1Time>`, but the body was
`Some(ASN1Time::new(self.time + rhs))` and `OffsetDateTime + Duration` panics on
overflow (internally `checked_add(...).expect(...)`). So `add` aborted the thread
where its signature promises `None`; the sibling `Sub` impl already guards and
returns `None`.

Use `checked_add` so an out-of-range result yields `None`. Add a regression test
(max-year notAfter + Duration -> None; a normal date still -> Some).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chifflier
chifflier self-requested a review July 22, 2026 07:55
@chifflier
chifflier merged commit 4bec84e into rusticata:master Jul 22, 2026
17 of 19 checks passed
@chifflier

Copy link
Copy Markdown
Member

Applied, thanks!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants