From 521a88b66f0bd76c52fef0d84776478c0b0664d9 Mon Sep 17 00:00:00 2001 From: pommicket Date: Wed, 20 Sep 2023 23:38:34 -0400 Subject: - add overflow check for chunk length. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this could have resulted in debug-only panics for maliciously crafted images. - add “impossible compressed size” check which slightly mitigates the problem of a malicious image causing you to allocate a shitton of memory. --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- src/lib.rs | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..265e560 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +## 0.1.1 + +- add overflow check for chunk length. + this could have resulted in debug-only panics for maliciously crafted images. +- add “impossible compressed size” check which slightly mitigates the + problem of a malicious image causing you to allocate a shitton of memory. diff --git a/Cargo.toml b/Cargo.toml index 40ad49e..0a0916c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ categories = ["encoding", "graphics", "multimedia::images", "no-std::no-alloc"] keywords = ["png", "decoder", "image", "no_std", "no_alloc"] exclude = ["/test/large", "/benches/large.png"] license = "0BSD" -version = "0.1.0" +version = "0.1.1" edition = "2021" [dev-dependencies] diff --git a/src/lib.rs b/src/lib.rs index b17c413..9d61021 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,6 +53,10 @@ pub enum Error { NoIdat, /// Adler-32 checksum doesn't check out (invalid PNG file) BadAdlerChecksum, + /// e.g. chunk is larger than 2GB, chunk goes past end of file (invalid PNG) + BadChunkSize, + /// compressed data cannot possibly be expanded to the full image because it's too small (invalid PNG) + CompressedSizeTooSmall, } #[cold] @@ -86,6 +90,8 @@ impl Display for Error { Self::NoIdat => write!(f, "missing IDAT chunk"), Self::BadNlen => write!(f, "LEN doesn't match NLEN"), Self::BadAdlerChecksum => write!(f, "bad adler-32 checksum"), + Self::BadChunkSize => write!(f, "bad chunk size"), + Self::CompressedSizeTooSmall => write!(f, "compressed data too small"), } } } @@ -857,6 +863,11 @@ pub fn decode_png_header(bytes: &[u8]) -> Result { color_type, length: 8 + ihdr_len, }; + // in the best-case scenario, each bit can decompress to 258 bytes + // (see DEFLATE RFC especially §3.2.5). + if hdr.decompressed_size() / (8 * 258) > bytes.len() { + return Err(Error::CompressedSizeTooSmall); + } Ok(hdr) } @@ -1215,6 +1226,9 @@ fn read_non_idat_chunks( ]) .try_into() .map_err(|_| Error::TooLargeForUsize)?; + if chunk_len > 0x7FFF_FFFF || chunk_len > reader.0.len() { + return Err(Error::BadChunkSize); + } let chunk_type = [ chunk_header[4], chunk_header[5], -- cgit v1.2.3