diff --git a/Cargo.lock b/Cargo.lock index 45034b0..e75593a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,7 +91,7 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" dependencies = [ "crabgrind", "web-time", diff --git a/Cargo.toml b/Cargo.toml index 7228320..b6d43d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" edition = "2021" rust-version = "1.60" license = "MIT OR Apache-2.0" diff --git a/src/lib.rs b/src/lib.rs index 7d50fff..5284cd9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,7 @@ //! This crate provides types for representing X.509 certificates, keys and other types as //! commonly used in the rustls ecosystem. It is intended to be used by crates that need to work //! with such X.509 types, such as [rustls](https://crates.io/crates/rustls), -//! [rustls-webpki](https://crates.io/crates/rustls-webpki), -//! [rustls-pemfile](https://crates.io/crates/rustls-pemfile), and others. +//! [rustls-webpki](https://crates.io/crates/rustls-webpki), and others. //! //! Some of these crates used to define their own trivial wrappers around DER-encoded bytes. //! However, in order to avoid inconvenient dependency edges, these were all disconnected. By @@ -787,7 +786,7 @@ impl SubjectPublicKeyInfoDer<'_> { } /// A TLS-encoded Encrypted Client Hello (ECH) configuration list (`ECHConfigList`); as specified in -/// [draft-ietf-tls-esni-18 §4](https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-18#section-4) +/// [RFC 9849 §4](https://datatracker.ietf.org/doc/html/rfc9849#section-4) #[derive(Clone, Eq, Hash, PartialEq)] pub struct EchConfigListBytes<'a>(BytesInner<'a>); diff --git a/src/pem.rs b/src/pem.rs index 3bee401..473392f 100644 --- a/src/pem.rs +++ b/src/pem.rs @@ -331,11 +331,18 @@ fn read( if section.is_some() { b64buf.extend(line); + if b64buf.len() > MAX_PEM_SECTION_SIZE { + return Err(Error::SectionTooLarge); + } } Ok(ControlFlow::Continue(())) } +// We've seen CRLs of 100MB (DER) / ~135MB (PEM) in the wild. +// 256MB seems like an OK upper bound. +const MAX_PEM_SECTION_SIZE: usize = 256 * 1024 * 1024; + enum SectionLabel { Known(SectionKind), Unknown(Vec), @@ -493,6 +500,9 @@ pub enum Error { /// No items found of desired type NoItemsFound, + + /// PEM section exceeds maximum allowed size of 256 MB + SectionTooLarge, } impl fmt::Display for Error { @@ -508,6 +518,7 @@ impl fmt::Display for Error { #[cfg(feature = "std")] Self::Io(e) => write!(f, "I/O error: {e}"), Self::NoItemsFound => write!(f, "no items found"), + Self::SectionTooLarge => write!(f, "PEM section exceeds maximum allowed size of 10 MB"), } } } diff --git a/tests/pem.rs b/tests/pem.rs index d982180..ce0bc86 100644 --- a/tests/pem.rs +++ b/tests/pem.rs @@ -352,3 +352,44 @@ impl Read for ErrorReader { Err(io::Error::new(io::ErrorKind::Other, "read error")) } } + +/// Reproduction for . +/// +/// We should stop reading if a PEM section is too long. +#[test] +fn section_too_large() { + let reader = UnboundedReader { + header: b"-----BEGIN CERTIFICATE-----\n", + read: 0, + }; + + let mut buf_reader = io::BufReader::new(reader); + let result = pem::from_buf(&mut buf_reader); + assert!(matches!(result, Err(pem::Error::SectionTooLarge))); + let reader = buf_reader.into_inner(); + assert!(reader.read < ((256 + 1) * 1024 * 1024), "{}", reader.read); +} + +struct UnboundedReader { + header: &'static [u8], + read: usize, +} + +impl Read for UnboundedReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if !self.header.is_empty() { + let n = self.header.len().min(buf.len()); + buf[..n].copy_from_slice(&self.header[..n]); + self.header = &self.header[n..]; + self.read += n; + return Ok(n); + } + + // Emit a base64-like line (76 chars + newline, standard PEM width) + let line = b"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB\n"; + let n = line.len().min(buf.len()); + buf[..n].copy_from_slice(&line[..n]); + self.read += n; + Ok(n) + } +}