Merge pull request #141 from 5225225/oom

Fix out of memory issues / misc other decoding bugs.
This commit is contained in:
HeroicKatora authored and GitHub committed 2021-09-18 15:51:33 +02:00
commit 73285481cd
7 files changed
+229 -18

No files matched your search

+2 -2
View File
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
rust: ["1.34.2", stable, beta, nightly] rust: ["1.48", stable, beta, nightly]
command: [build, test] command: [build, test]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -20,7 +20,7 @@ jobs:
- name: test - name: test
run: > run: >
cargo test --tests --benches --no-default-features --features "$FEATURES" cargo test --tests --benches --no-default-features --features "$FEATURES"
if: ${{ matrix.rust != '1.34.2' }} if: ${{ matrix.rust != '1.48' }}
env: env:
FEATURES: ${{ matrix.features }} FEATURES: ${{ matrix.features }}
rustfmt: rustfmt:
+8
View File
@@ -44,3 +44,11 @@ Formats and interpretations not listed above or with empty entries are unsupport
- Baseline tags - Baseline tags
- `ExtraSamples` - `ExtraSamples`
- Extension tags - Extension tags
## Fuzzing
This crate uses [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) in order to test the image parser.
After installing it with `cargo install cargo-fuzz` on a nightly rustc, the
fuzzing harness can be run with recommended settings using
`cargo fuzz run decode_image -snone -- -timeout=5`.
+4
View File
@@ -23,5 +23,9 @@ fuzz_target!(|data: &[u8]| {
if !decoder.more_images() { if !decoder.more_images() {
break; break;
} }
if let Err(_) = decoder.next_image() {
break;
}
} }
}); });
+65 -16
View File
@@ -1,4 +1,4 @@
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::io::{self, Read, Seek}; use std::io::{self, Read, Seek};
use std::{cmp, ops::Range}; use std::{cmp, ops::Range};
@@ -378,6 +378,7 @@ where
strip_decoder: Option<StripDecodeState>, strip_decoder: Option<StripDecodeState>,
tile_decoder: Option<TileDecodeState>, tile_decoder: Option<TileDecodeState>,
tile_attributes: Option<TileAttributes>, tile_attributes: Option<TileAttributes>,
seen_ifds: HashSet<u64>,
} }
trait Wrapping { trait Wrapping {
@@ -544,6 +545,7 @@ impl<R: Read + Seek> Decoder<R> {
strip_decoder: None, strip_decoder: None,
tile_decoder: None, tile_decoder: None,
tile_attributes: None, tile_attributes: None,
seen_ifds: HashSet::new(),
} }
.init() .init()
} }
@@ -640,7 +642,7 @@ impl<R: Read + Seek> Decoder<R> {
)) ))
} }
} }
self.next_ifd = match self.read_ifd_offset()? { self.next_ifd = match self.read_ifd_offset_nonrepeating()? {
0 => None, 0 => None,
n => Some(n), n => Some(n),
}; };
@@ -736,6 +738,15 @@ impl<R: Read + Seek> Decoder<R> {
self.byte_order self.byte_order
} }
fn read_ifd_offset_nonrepeating(&mut self) -> TiffResult<u64> {
let result = self.read_ifd_offset()?;
if self.seen_ifds.insert(result) {
Ok(result)
} else {
Err(TiffError::FormatError(TiffFormatError::CycleInOffsets))
}
}
#[inline] #[inline]
pub fn read_ifd_offset(&mut self) -> Result<u64, io::Error> { pub fn read_ifd_offset(&mut self) -> Result<u64, io::Error> {
if self.bigtiff { if self.bigtiff {
@@ -894,7 +905,7 @@ impl<R: Read + Seek> Decoder<R> {
}; };
dir.insert(tag, entry); dir.insert(tag, entry);
} }
self.next_ifd = match self.read_ifd_offset()? { self.next_ifd = match self.read_ifd_offset_nonrepeating()? {
0 => None, 0 => None,
n => Some(n), n => Some(n),
}; };
@@ -1124,6 +1135,7 @@ impl<R: Read + Seek> Decoder<R> {
length, length,
buffer.len(), buffer.len(),
buffer.byte_len(), buffer.byte_len(),
self.limits.intermediate_buffer_size,
)?; )?;
// Read into output buffer. // Read into output buffer.
@@ -1188,6 +1200,7 @@ impl<R: Read + Seek> Decoder<R> {
compressed_length, compressed_length,
tile_samples, tile_samples,
byte_len, byte_len,
self.limits.intermediate_buffer_size,
)?; )?;
for row in 0..(tile_length - padding_down) { for row in 0..(tile_length - padding_down) {
@@ -1232,17 +1245,20 @@ impl<R: Read + Seek> Decoder<R> {
compressed_length: u64, compressed_length: u64,
samples: usize, // Expected chunk length in samples samples: usize, // Expected chunk length in samples
byte_len: usize, // Byte length of the samples in result buffer byte_len: usize, // Byte length of the samples in result buffer
intermediate_buffer_size: usize,
) -> TiffResult<Box<dyn Read + 'r>> { ) -> TiffResult<Box<dyn Read + 'r>> {
Ok(match compression_method { Ok(match compression_method {
CompressionMethod::None => Box::new(reader), CompressionMethod::None => Box::new(reader),
CompressionMethod::LZW => Box::new( CompressionMethod::LZW => {
LZWReader::new( let clen = usize::try_from(compressed_length)?;
reader,
usize::try_from(compressed_length)?, if samples * byte_len > intermediate_buffer_size || clen > intermediate_buffer_size
samples * byte_len, {
)? return Err(TiffError::LimitsExceeded);
.1, }
),
Box::new(LZWReader::new(reader, clen, samples * byte_len)?.1)
}
CompressionMethod::PackBits => { CompressionMethod::PackBits => {
Box::new(PackBitsReader::new(reader, usize::try_from(compressed_length)?)?.1) Box::new(PackBitsReader::new(reader, usize::try_from(compressed_length)?)?.1)
} }
@@ -1375,7 +1391,17 @@ impl<R: Read + Seek> Decoder<R> {
let jpeg_tables: Option<Vec<u8>> = match self.find_tag(Tag::JPEGTables) { let jpeg_tables: Option<Vec<u8>> = match self.find_tag(Tag::JPEGTables) {
Ok(None) => None, Ok(None) => None,
Ok(_) => Some(self.get_tag_u8_vec(Tag::JPEGTables)?), Ok(_) => {
let vec = self.get_tag_u8_vec(Tag::JPEGTables)?;
if vec.len() < 2 {
return Err(TiffError::FormatError(
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
));
}
Some(vec)
}
Err(e) => return Err(e), Err(e) => return Err(e),
}; };
@@ -1390,11 +1416,26 @@ impl<R: Read + Seek> Decoder<R> {
)); ));
} }
if offsets[0] as usize > self.limits.intermediate_buffer_size
|| bytes
.iter()
.any(|&x| x as usize > self.limits.intermediate_buffer_size)
{
return Err(TiffError::LimitsExceeded);
}
let mut res_img = Vec::with_capacity(offsets[0] as usize); let mut res_img = Vec::with_capacity(offsets[0] as usize);
for (idx, offset) in offsets.iter().enumerate() { for (idx, offset) in offsets.iter().enumerate() {
self.goto_offset(*offset)?; self.goto_offset(*offset)?;
let jpeg_reader = JpegReader::new(&mut self.reader, bytes[idx], &jpeg_tables)?; let length = bytes[idx];
if jpeg_tables.is_some() && length < 2 {
return Err(TiffError::FormatError(
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
));
}
let jpeg_reader = JpegReader::new(&mut self.reader, length, &jpeg_tables)?;
let mut decoder = jpeg::Decoder::new(jpeg_reader); let mut decoder = jpeg::Decoder::new(jpeg_reader);
match decoder.decode() { match decoder.decode() {
@@ -1446,10 +1487,18 @@ impl<R: Read + Seek> Decoder<R> {
let sized_width = usize::try_from(self.width)?; let sized_width = usize::try_from(self.width)?;
let sized_height = usize::try_from(self.height)?; let sized_height = usize::try_from(self.height)?;
// Ignore potential vertical padding on the bottommost strip let strip_height_without_padding = index
let strip_height = cmp::min(rows_per_strip, sized_height - index * rows_per_strip); .checked_mul(rows_per_strip)
.and_then(|x| sized_height.checked_sub(x))
.ok_or(TiffError::IntSizeError)?;
let buffer_size = sized_width * strip_height * self.bits_per_sample.len(); // Ignore potential vertical padding on the bottommost strip
let strip_height = rows_per_strip.min(strip_height_without_padding);
let buffer_size = sized_width
.checked_mul(strip_height)
.and_then(|x| x.checked_mul(self.bits_per_sample.len()))
.ok_or(TiffError::LimitsExceeded)?;
if buffer.len() < buffer_size { if buffer.len() < buffer_size {
return Err(TiffError::FormatError( return Err(TiffError::FormatError(
+12
View File
@@ -225,6 +225,18 @@ impl JpegReader {
match jpeg_tables { match jpeg_tables {
Some(tables) => { Some(tables) => {
assert!(
tables.len() >= 2,
"jpeg_tables, if given, must be at least 2 bytes long. Got {:?}",
tables
);
assert!(
length >= 2,
"if jpeg_tables is given, length must be at least 2 bytes long, got {}",
length
);
let mut jpeg_data = tables.clone(); let mut jpeg_data = tables.clone();
let truncated_length = jpeg_data.len() - 2; let truncated_length = jpeg_data.len() - 2;
jpeg_data.truncate(truncated_length); jpeg_data.truncate(truncated_length);
+2
View File
@@ -64,6 +64,7 @@ pub enum TiffFormatError {
Format(String), Format(String),
RequiredTagEmpty(Tag), RequiredTagEmpty(Tag),
StripTileTagConflict, StripTileTagConflict,
CycleInOffsets,
#[doc(hidden)] #[doc(hidden)]
/// Do not match against this variant. It may get removed. /// Do not match against this variant. It may get removed.
__NonExhaustive, __NonExhaustive,
@@ -115,6 +116,7 @@ impl fmt::Display for TiffFormatError {
Format(ref val) => write!(fmt, "Invalid format: {:?}.", val), Format(ref val) => write!(fmt, "Invalid format: {:?}.", val),
RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val), RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val),
StripTileTagConflict => write!(fmt, "File should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found."), StripTileTagConflict => write!(fmt, "File should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found."),
CycleInOffsets => write!(fmt, "File contained a cycle in the list of IFDs"),
__NonExhaustive => unreachable!(), __NonExhaustive => unreachable!(),
} }
} }
+136
View File
@@ -383,6 +383,142 @@ fn test_stripped_image_overflow() {
} }
} }
#[test]
fn oom() {
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0,
17, 1, 4, 0, 1, 0, 0, 0, 3, 77, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 3, 128, 0, 0, 22, 1, 4, 0, 1,
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 178, 48, 178, 178, 178, 178, 162, 178,
];
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
let err = decoder.read_image().unwrap_err();
match err {
tiff::TiffError::LimitsExceeded => {}
unexpected => panic!("Unexpected error {}", unexpected),
}
}
#[test]
fn oom_2() {
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 3, 128, 0, 0, 22, 1, 4, 0, 1,
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 0, 1, 0, 13, 13,
];
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
let err = decoder.read_image().unwrap_err();
match err {
tiff::TiffError::LimitsExceeded => {}
unexpected => panic!("Unexpected error {}", unexpected),
}
}
#[test]
fn invalid_jpeg_tag() {
use tiff::tags;
use tiff::{TiffError, TiffFormatError};
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 15, 0, 0, 254, 44, 1, 0, 0, 0, 0, 0, 32, 0, 0, 0, 1, 4, 0, 1, 0,
0, 0, 0, 1, 0, 0, 91, 1, 1, 0, 0, 0, 0, 0, 242, 4, 0, 0, 0, 22, 0, 56, 77, 0, 77, 1, 0, 0,
73, 42, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 158, 0, 0, 251, 3, 1,
3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 4, 61, 1, 18, 0, 1, 0, 0, 0, 202, 0, 0, 0, 17,
1, 100, 0, 129, 0, 0, 0, 0, 0, 0, 0, 232, 254, 252, 255, 254, 255, 255, 255, 1, 29, 0, 0,
22, 1, 3, 0, 1, 0, 0, 0, 16, 0, 0, 0, 23, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 123, 73, 254, 0,
73,
];
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
let err = decoder.read_image().unwrap_err();
match err {
TiffError::FormatError(TiffFormatError::InvalidTagValueType(tags::Tag::JPEGTables)) => {}
unexpected => panic!("Unexpected error {}", unexpected),
}
}
#[test]
fn invalid_jpeg_tag_2() {
use tiff::tags;
use tiff::{TiffError, TiffFormatError};
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 242, 0, 1, 4, 0, 1, 0,
0, 0, 0, 129, 16, 0, 1, 1, 4, 0, 1, 0, 0, 0, 214, 0, 0, 248, 253, 1, 3, 0, 1, 0, 0, 0, 64,
0, 0, 0, 3, 1, 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 64, 14, 1, 0,
2, 0, 0, 148, 0, 206, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 22, 1, 4, 0, 17, 0, 0, 201, 1, 0, 0, 0, 23, 1, 2, 0, 20, 0, 0, 0, 194, 0, 0, 0,
91, 1, 7, 0, 5, 0, 0, 0, 64, 0, 0, 0, 237, 254, 65, 255, 255, 255, 255, 255, 1, 0, 0, 0,
22, 1, 4, 0, 1, 0, 0, 0, 42, 0, 0, 0, 23, 1, 255, 255, 255, 255, 255, 36, 36, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 36, 73, 73, 0, 42, 36, 36, 36, 36, 0, 0, 8, 0,
];
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
let err = decoder.read_image().unwrap_err();
match err {
TiffError::FormatError(TiffFormatError::InvalidTagValueType(tags::Tag::JPEGTables)) => {}
unexpected => panic!("Unexpected error {}", unexpected),
}
}
#[test]
fn multiple_images_strip_height() {
use tiff::TiffError;
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 2, 0, 0, 0, 61, 1, 9, 0, 46, 22,
128, 0, 0, 0, 0, 1, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 17, 1, 4, 0, 27, 0, 0, 0, 0, 0, 0,
0, 1, 1, 3, 0, 1, 0, 0, 0, 17, 1, 0, 231, 22, 1, 1, 0, 1, 0, 0, 0, 130, 0, 0, 0, 23, 1, 4,
0, 14, 0, 0, 0, 0, 0, 0, 0, 133, 133, 133, 77, 77, 77, 0, 0, 22, 128, 0, 255, 255, 255,
255, 255,
];
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
let _ = decoder.read_image().unwrap();
let err = decoder.read_image().unwrap_err();
match err {
TiffError::IntSizeError => {}
unexpected => panic!("Unexpected error {}", unexpected),
}
}
#[test]
fn timeout() {
use tiff::{TiffError, TiffFormatError};
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 68, 0, 0, 0, 2, 0, 32, 254, 252, 0, 109,
0, 129, 0, 0, 0, 32, 0, 58, 0, 1, 4, 0, 1, 0, 6, 0, 0, 0, 8, 0, 0, 1, 73, 73, 42, 0, 8, 0,
0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 21, 0, 0, 0, 61, 1, 255, 128, 9, 0, 0, 8, 0, 1, 113, 2,
3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 112, 0, 0, 36, 0, 0,
0, 112, 56, 200, 0, 5, 0, 0, 64, 0, 0, 1, 0, 4, 0, 0, 0, 2, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0,
0, 0, 4, 17, 1, 1, 0, 93, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 3, 6, 0, 231, 22, 1,
1, 0, 1, 0, 0, 0, 2, 64, 118, 36, 23, 1, 1, 0, 43, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 4, 0, 8,
0, 0, 73, 73, 42, 0, 8, 0, 0, 0, 0, 0, 32,
];
let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
match error {
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
e => panic!("Unexpected error {:?}", e),
}
}
#[test] #[test]
fn test_no_rows_per_strip() { fn test_no_rows_per_strip() {
test_image_sum_u8("no_rows_per_strip.tiff", ColorType::RGB(8), 99448840); test_image_sum_u8("no_rows_per_strip.tiff", ColorType::RGB(8), 99448840);