Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion der/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ pub enum ErrorKind {
#[cfg(feature = "std")]
Io(std::io::ErrorKind),

/// Indefinite length disallowed (or malformed when decoding BER)
/// Indefinite length invalid (disallowed in DER or malformed when decoding BER)
IndefiniteLength,

/// Incorrect length for a given field.
Expand All @@ -241,6 +241,9 @@ pub enum ErrorKind {
tag: Tag,
},

/// Too many nested productions (i.e. TLV-in-TLV) encountered.
NestingDepth,

/// Message is not canonically encoded.
Noncanonical {
/// Tag of the value which is not canonically encoded.
Expand Down Expand Up @@ -373,6 +376,7 @@ impl fmt::Display for ErrorKind {
ErrorKind::Io(err) => write!(f, "I/O error: {err:?}"),
ErrorKind::IndefiniteLength => write!(f, "indefinite length disallowed/malformed"),
ErrorKind::Length { tag } => write!(f, "incorrect length for {tag}"),
ErrorKind::NestingDepth => write!(f, "too many nested productions encountered"),
ErrorKind::Noncanonical { tag } => {
write!(f, "ASN.1 {tag} not canonically encoded as DER")
}
Expand Down
18 changes: 18 additions & 0 deletions der/src/reader/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,21 @@ pub(super) struct Position {

/// Position in the input buffer (in bytes after Base64 decoding).
position: Length,

/// Number of nested productions encountered (i.e. depth in the call stack).
depth: usize,
}

impl Position {
/// Maximum number of nested messages we tolerate (prevents stack exhaustion).
const MAX_DEPTH: usize = 64;
Comment on lines +19 to +20

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't expose this yet, but it might be a good idea


/// Create a new position tracker with the given overall length.
pub(super) fn new(input_len: Length) -> Self {
Self {
input_len,
position: Length::ZERO,
depth: 0,
}
}

Expand All @@ -44,6 +51,11 @@ impl Position {
self.position
}

/// Create new [`Error`] from the given [`ErrorKind`] which includes the current position.
pub(super) fn error(&mut self, kind: ErrorKind) -> Error {
kind.at(self.position)
}

/// Get the input length.
pub(super) fn input_len(&self) -> Length {
self.input_len
Expand All @@ -61,6 +73,11 @@ impl Position {
///
/// A [`Resumption`] value which can be used to continue parsing the outer message.
pub(super) fn split_nested(&mut self, len: Length) -> Result<Resumption> {
match self.depth.checked_add(1) {
Some(depth) if depth < Self::MAX_DEPTH => self.depth = depth,
_ => return Err(self.error(ErrorKind::NestingDepth)),
}

let nested_input_len = (self.position + len)?;

if nested_input_len > self.input_len {
Expand All @@ -77,6 +94,7 @@ impl Position {
/// Resume processing the rest of a message after processing a nested inner portion.
pub(super) fn resume_nested(&mut self, resumption: Resumption) {
self.input_len = resumption.input_len;
self.depth = self.depth.saturating_sub(1);
}
}

Expand Down
2 changes: 1 addition & 1 deletion der/src/reader/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl<'a> SliceReader<'a> {
/// error occurred.
pub fn error(&mut self, kind: ErrorKind) -> Error {
self.failed = true;
kind.at(self.position.current())
self.position.error(kind)
}

/// Did the decoding operation fail due to an error?
Expand Down
50 changes: 50 additions & 0 deletions der/tests/nesting.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//! Tests for handling of nested messages.

use der::{Decode, Error, ErrorKind, Header, Reader, SliceReader, Tag, asn1::AnyRef};

// This is the expected maximum depth.
// TODO(tarcieri): expose this as a constant in the public API?
const MAX_DEPTH: usize = 64;

fn walk<'a>(reader: &mut SliceReader<'a>) -> Result<AnyRef<'a>, Error> {
let header = Header::peek(reader)?;
if header.tag() == Tag::Sequence {
reader.sequence(|r| walk(r)) // der::read_nested recursion
} else {
AnyRef::decode(reader)
}
}

#[test]
#[allow(clippy::cast_possible_truncation, reason = "test")]
fn returns_nesting_depth_error_when_max_depth_encountered() {
let depth = MAX_DEPTH + 1;

let mut buf = vec![0x05, 0x00]; // innermost NULL
for _ in 0..depth {
let len = buf.len();
let mut next = Vec::with_capacity(len + 5);
next.push(0x30);
if len < 0x80 {
next.push(len as u8);
} else if len <= 0xFF {
next.push(0x81);
next.push(len as u8);
} else if len <= 0xFFFF {
next.push(0x82);
next.push((len >> 8) as u8);
next.push(len as u8);
} else {
next.push(0x83);
next.push((len >> 16) as u8);
next.push((len >> 8) as u8);
next.push(len as u8);
}
next.extend_from_slice(&buf);
buf = next;
}

let mut reader = SliceReader::new(&buf).unwrap();
let err = walk(&mut reader).expect_err("should return ErrorKind::NestingDepth");
assert_eq!(err.kind(), ErrorKind::NestingDepth);
}
Loading