Sitelet https://github.com/RustCrypto/formats/pull/2450
Skip to content

der: fix infinite recursion in ValueOrd for ContextSpecific - #2450

Merged
tarcieri merged 1 commit into
RustCrypto:masterfrom
yuxi-liu-wired:fix/der-context-specific-value-ord
Oct 2, 2026
Merged

tarcieri merged 1 commit into
RustCrypto:masterfrom
yuxi-liu-wired:fix/der-context-specific-value-ord

Conversation

@yuxi-liu-wired

Copy link
Copy Markdown
Contributor

ValueOrd for ContextSpecific<T> recurses into itself forever. The same impl, generated by the same macro, is used for Application<T> and Private<T>:

fn value_cmp(&self, other: &Self) -> Result<Ordering, Error> {
    match self.tag_mode {
        TagMode::Explicit => self.der_cmp(other),   // der_cmp -> headers equal -> self.value_cmp
        TagMode::Implicit => self.value_cmp(other), // calls itself
    }
}

Comparing two fields with the same header overflows the stack:

let x = ContextSpecific { tag_number: TagNumber(1), tag_mode: TagMode::Implicit, value: 5u8 };
let y = ContextSpecific { tag_number: TagNumber(1), tag_mode: TagMode::Implicit, value: 6u8 };
x.der_cmp(&y); // thread 'main' has overflowed its stack

Sorting a SetOfVec<ContextSpecific<_>> hits this too.

The fix compares the contents octets: self.value.der_cmp(&other.value) (the inner TLV) for EXPLICIT, and self.value.value_cmp(&other.value) for IMPLICIT.

The bug goes back to the original *Ord traits (#190) and was carried into the class macro in #1819.

Test: context_specific::tests::value_ord covers both tag modes with value_cmp/der_cmp. On master it aborts with a stack overflow.

This PR is independent of the three SET OF ordering PRs (tag octet order, UtcTime, Option::der_cmp) and merges cleanly with each.

This PR was produced by AI agents (Claude) while auditing every DerOrd/ValueOrd impl in der after differential fuzzing of der/x509-cert found the tag-order issue.

`ValueOrd for ContextSpecific<T>` (and `Application<T>`, `Private<T>`,
generated by the same macro) called `self.der_cmp(other)` for EXPLICIT and
`self.value_cmp(other)` for IMPLICIT. `der_cmp` compares the headers and
then calls `value_cmp` on `self` again, so both arms recurse until the
stack overflows whenever two fields with the same header are compared,
e.g. when sorting a `SetOfVec<ContextSpecific<u8>>`.

Compare the contents octets instead: the inner TLV (`der_cmp` on the
value) for EXPLICIT, and the inner value (`value_cmp`) for IMPLICIT.

The bug dates back to the original `*Ord` traits (RustCrypto#190) and was carried
into the macro in RustCrypto#1819.
@dishmaker

Copy link
Copy Markdown
Contributor

Thanks! I did hit this bug, but forgot to fix after few iterations of PRs.

@tarcieri

tarcieri commented Oct 2, 2026

Copy link
Copy Markdown
Member

Hmm, this seems potentially security-critical (DoS).

@tarcieri
tarcieri merged commit 2c0b0f9 into RustCrypto:master Oct 2, 2026
117 checks passed
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.

3 participants