Skip to content

Fix x509_get_entries() not validating CRL entry's own SEQUENCE length is fully consumed - #10827

Open
94xhn wants to merge 1 commit into
Mbed-TLS:developmentfrom
94xhn:fix-x509-crl-entry-length-check
Open

Fix x509_get_entries() not validating CRL entry's own SEQUENCE length is fully consumed#10827
94xhn wants to merge 1 commit into
Mbed-TLS:developmentfrom
94xhn:fix-x509-crl-entry-length-check

Conversation

@94xhn

@94xhn 94xhn commented Jul 12, 2026

Copy link
Copy Markdown

Description

x509_get_entries() in library/x509_crl.c (used by
mbedtls_x509_crl_parse()/mbedtls_x509_crl_parse_der()) parses each
RevokedCertificate entry's serial number, revocation date and optional
crlEntryExtensions against end2, the bound derived from that entry's
own declared SEQUENCE length. After parsing those fields, the
function never checks that *p actually reached end2 — it only
checks *p < end, which is the bound of the whole
revokedCertificates SEQUENCE OF, not of the individual entry.

As a result, a CRL entry whose declared SEQUENCE length is larger than
what its serial/time/extensions fields actually consume has its
unaccounted trailing bytes silently reinterpreted as the start of the
next sibling entry (or, if crafted as a nested SEQUENCE, as an entirely
separate phantom entry), instead of being rejected as malformed. A
single malformed/crafted CRL entry can therefore be silently split into
multiple entries with attacker-influenced serial/date/extension
contents.

This is not a memory-safety issue: mbedtls_asn1_get_len() already
enforces that a declared length cannot exceed the bytes actually
available before the caller-supplied end, so *p can never run past
the real buffer. This is purely an input-validation/parsing-correctness
gap.

Fix

Add the missing if (*p != end2) check immediately after
x509_get_crl_entry_ext() returns, mirroring the exact idiom already
used elsewhere in the same file/module for analogous per-item length
checks:

  • x509_get_crl_entry_ext()'s own if (*p != end) check at the end of
    that function,
  • x509_crt.c's if (*p != policy_end) in
    x509_get_certificate_policies(),
  • mbedtls_x509_crl_parse_der()'s own final if (p != end) check.

Testing

Built a standalone test harness (not part of this PR) that links
against this branch's freshly built libmbedx509.a /
libtfpsacrypto.a and calls the real public
mbedtls_x509_crl_parse_der():

  • Before the fix: a 44-byte revokedCertificates block where
    entry DTLS #1 declares a SEQUENCE content length of 40 bytes, but its
    serial (3B) + UTCTime (15B) + empty crlEntryExtensions (30 00,
    2B) only consume 20 of those 40 bytes — with the remaining 20 bytes
    shaped as a second, well-formed RevokedCertificate SEQUENCE —
    parses successfully (ret == 0) and produces 2 entries instead
    of being rejected.
  • After the fix: the same input is rejected with
    MBEDTLS_ERR_ASN1_LENGTH_MISMATCH (ret == -102).
  • Regression-checked three legitimate scenarios against the patched
    build, all still parse correctly:
    • two well-formed entries → count == 2
    • one well-formed entry with no crlEntryExtensions field present
      at all (it's OPTIONAL) → count == 1
    • a CRL with no revokedCertificates field at all (also OPTIONAL)
      count == 0

Searched open/closed PRs and issues in this repo for
x509_get_entries, x509_crl entry, CRL entry length,
revokedCertificates — found no existing PR or issue addressing this.

Disclosure

Generative AI (Claude) was used to help investigate this issue and implement this fix. All changes were reviewed by me before submission.

… is fully consumed

x509_get_entries() (used by mbedtls_x509_crl_parse()/_der()) parses each
revokedCertificate entry's serial number, revocation date and optional
crlEntryExtensions against end2, the bound derived from that entry's own
declared SEQUENCE length. After parsing those fields it never checked
that *p actually reached end2 -- it only checked *p < end, the bound of
the whole revokedCertificates SEQUENCE OF.

As a result, a CRL entry whose declared SEQUENCE length is larger than
what its serial/time/extensions fields actually consume has its
unaccounted trailing bytes silently reinterpreted as the start of the
next sibling entry (or, if crafted as a nested SEQUENCE, as an entirely
separate phantom entry), instead of being rejected as malformed. This
means a single malformed/crafted entry can be silently split into
multiple entries with attacker-influenced serial/date/extension
contents.

This mirrors the same "end of my own declared length" check already
used elsewhere in the same file/module for analogous per-item length
checks: x509_get_crl_entry_ext()'s own `if (*p != end)`,
x509_crt.c's `if (*p != policy_end)` in x509_get_certificate_policies(),
and mbedtls_x509_crl_parse_der()'s own final `if (p != end)`.

Add the missing `if (*p != end2)` check immediately after
x509_get_crl_entry_ext() returns, before the existing `*p < end` check
that decides whether to allocate the next sibling entry.

Verified with a standalone test harness built against this worktree's
freshly built libmbedx509.a/libtfpsacrypto.a, calling the real public
mbedtls_x509_crl_parse_der():
  - Before this fix: a 44-byte CRL entries block where entry Mbed-TLS#1 declares
    a SEQUENCE length of 40 bytes but its serial+time+empty-extensions
    fields only consume 20, with the remaining 20 bytes shaped as a
    second well-formed RevokedCertificate SEQUENCE, parses successfully
    (ret == 0) and produces 2 entries instead of being rejected.
  - After this fix: the same input is rejected with
    MBEDTLS_ERR_ASN1_LENGTH_MISMATCH (ret == -102).
  - Regression-checked three legitimate scenarios, all still parse
    correctly: two well-formed entries (count == 2), one well-formed
    entry with no extensions field present at all (count == 1), and a
    CRL with no revokedCertificates field at all (count == 0).

## Disclosure

Generative AI (Claude) was used to help investigate this issue and
implement this fix. All changes were reviewed by me before submission.

Signed-off-by: yi chen <94xhn1@gmail.com>
@yanesca yanesca added bug component-x509 size-xs Estimated task size: extra small (a few hours at most) labels Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug component-x509 size-xs Estimated task size: extra small (a few hours at most)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants