Compare commits

...
33 Commits
Author SHA1 Message Date
Andreas Molzer b8459f413a Add fuzzing work and bugs to release notes 2021-09-22 01:20:00 +02:00
Andreas Molzer 26e760e303 Add notes on MSRV 2021-09-22 01:13:10 +02:00
Andreas Molzer a4878e9658 Release notes for 0.7.1 2021-09-22 01:05:25 +02:00
HeroicKatora 73285481cd Merge pull request #141 from 5225225/oom
Fix out of memory issues / misc other decoding bugs.
2021-09-18 15:51:33 +02:00
5225225 b065539c07 Add fuzzing instructions to readme, bump CI version to 1.48 2021-09-18 14:42:53 +01:00
5225225 1c5800daa0 Infinitely try to read off images rather than only trying 128 2021-09-18 12:14:17 +01:00
5225225 885d762def Implement cycle checking in IFD offsets 2021-09-08 18:08:10 +01:00
5225225 2c9e02d347 Merge remote-tracking branch 'origin/master' into oom 2021-09-06 17:53:16 +01:00
Jonathan Behrens b318481c55 Merge pull request #143 from Christopher22/reset_tag_values
Do not carry tag values from previous images
2021-08-15 11:43:44 -04:00
5225225 1774b362d3 x 2021-07-15 18:08:40 +01:00
Jonathan Behrens f2fec92283 Merge pull request #146 from fintelia/encode-signed-int
Support encoding signed integer formats
2021-06-20 18:39:08 -04:00
Jonathan Behrens be0619d6a1 Support encoding signed integer formats 2021-06-20 14:33:13 -04:00
Tomáš Král e92cf219a9 Fix the mandrill.tiff regression (#145)
* fix mandrill.tiff regression from upstream

* use contains_key(), added StripTileTagConflict error

* updated test_div_zero, added test_no_rows_per_strip
2021-06-17 09:22:07 -04:00
Christopher Gundler 3e5f977460 Do not carry tag values from previous images 2021-06-14 20:21:51 +02:00
5225225 f0666aebbf Fix overflow when calculating strip height for multiple images 2021-05-29 22:52:42 +01:00
5225225 65a1fbe8ae Test for short JPEG tag 2021-05-29 22:12:35 +01:00
5225225 03b6ed413b Fix out of bounds subtraction with invalid JPEG table 2021-05-29 22:12:35 +01:00
5225225 5b7814c994 Check for OOM when creating LZWReader 2021-05-29 22:12:35 +01:00
5225225 a15d619471 Fix OOM in Jpeg decoding buffer allocation 2021-05-29 22:12:35 +01:00
HeroicKatora 458a713827 Merge pull request #140 from 5225225/fuzzer
Add fuzzer with 1MB size limits decoding image
2021-05-29 23:12:04 +02:00
5225225 2f6ad07f28 Try to read multiple images in fuzzer 2021-05-29 22:00:36 +01:00
HeroicKatora 153ed48ee7 Merge pull request #136 from TomasKralCZ/padding_skip
remove padding_buffer
2021-05-29 15:13:20 +02:00
5225225 420667955a Add fuzzer with 1MB size limits decoding image 2021-05-29 13:18:35 +01:00
HeroicKatora 3bf19822a2 Merge pull request #139 from 5225225/value-bytes-overflow
Check for various arithmetic overflows
2021-05-29 13:48:03 +02:00
5225225 2bb37a4ecd Do checked multiplication for samples per strip 2021-05-29 12:37:15 +01:00
5225225 b41ac105ab Use checked multiplication when calculating buffer size 2021-05-29 12:36:59 +01:00
5225225 1e10b9803b Add check for an invalid strip count 2021-05-29 12:36:50 +01:00
5225225 2f4e491874 Check for where tag count * tag size overflows 2021-05-28 20:32:30 +01:00
HeroicKatora 285b881e27 Merge pull request #138 from 5225225/divide_by_zero
Fix division by zero issue with malformed file
2021-05-28 19:59:37 +02:00
5225225 bdfe2f55fd Check error returned from parsing invalid image 2021-05-28 15:46:55 +01:00
5225225 eda5a8ac30 Fix division by zero issue with malformed file 2021-05-28 14:44:50 +01:00
HeroicKatora bd1e37d78b Merge pull request #130 from phil-opp/release
Prepare 0.7.0 release
2021-05-25 21:03:44 +02:00
Tomáš Král ad0f75222a remove padding_buffer 2021-05-21 18:06:05 +02:00
14 changed files with 540 additions and 82 deletions

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:
+33
View File
@@ -1,3 +1,36 @@
# Version 0.7.1
New features:
* Encoding signed integer formats is now supported.
* Extensive fuzzing with `cargo fuzz`.
Changes:
* Tile decoding should be a little faster, requires one less intermediate buffer.
* Images whose IFDs form a cycle due to their offsets will now raise an error
when the cycle would be entered (jumping back should still be supported).
Fixes:
* Fixed a regression that caused conflict between strips and tile images,
causing errors in decoding some images.
* Use checked integer arithmetic in limit calculations, fixes overflows.
* IFD Tags are now always cleared between images.
* Found by fuzzing: Several memory limit overflows; JPEG now correctly
validates offsets and a minimum size of its table; Check upper limit of strip
byte size correctly;
Notes:
Our CI has warned that this version no longer builds on `1.34.2` out of the
box. We're still committed to the MSRV on this major version yet one
dependency—`flate2`—has already bumped it in a SemVer compatible version of its
own. This is out-of-our-control (cargo's dependency resolution does not allow
us to address this in a reasonable manner).
This can be address this by pinning the version of `flate2` to `1.0.21` in your
own files. However, you should understand that this puts you in considerable
maintenance debt as you will no longer receive any updates for this dependency
and any package that _requires_ a new version of the `1.0` series would be
incompatible with this requirement (cargo might yell at you very loudly).
# Version 0.7.0 # Version 0.7.0
New features: New features:
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tiff" name = "tiff"
version = "0.7.0" version = "0.7.1"
authors = [ authors = [
"ccgn", "ccgn",
"bvssvni <bvssvni@gmail.com>", "bvssvni <bvssvni@gmail.com>",
+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`.
+3
View File
@@ -0,0 +1,3 @@
target
corpus
artifacts
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "tiff-fuzz"
version = "0.0.0"
authors = ["Automatically generated"]
publish = false
edition = "2018"
[package.metadata]
cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.4"
[dependencies.tiff]
path = ".."
# Prevent this from interfering with workspaces
[workspace]
members = ["."]
[[bin]]
name = "decode_image"
path = "fuzz_targets/decode_image.rs"
test = false
doc = false
+31
View File
@@ -0,0 +1,31 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
let mut decoder = if let Ok(d) = tiff::decoder::Decoder::new(std::io::Cursor::new(data)) {
d
} else {
return;
};
let mut limits = tiff::decoder::Limits::default();
limits.decoding_buffer_size = 1_000_000;
limits.ifd_value_size = 1_000_000;
limits.intermediate_buffer_size = 1_000_000;
decoder = decoder.with_limits(limits);
loop {
if let Err(_) = decoder.read_image() {
break;
}
if !decoder.more_images() {
break;
}
if let Err(_) = decoder.next_image() {
break;
}
}
});
+9 -2
View File
@@ -349,8 +349,8 @@ impl Entry {
} }
let bo = decoder.byte_order(); let bo = decoder.byte_order();
let value_bytes = self.count
* match self.type_ { let tag_size = match self.type_ {
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1, Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
Type::SHORT | Type::SSHORT => 2, Type::SHORT | Type::SSHORT => 2,
Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4, Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4,
@@ -363,6 +363,13 @@ impl Entry {
Type::__NonExhaustive => unreachable!(), Type::__NonExhaustive => unreachable!(),
}; };
let value_bytes = match self.count.checked_mul(tag_size) {
Some(n) => n,
None => {
return Err(TiffError::LimitsExceeded);
}
};
// Case 2: there is one value. // Case 2: there is one value.
if self.count == 1 { if self.count == 1 {
// 2a: the value is 5-8 bytes and we're in BigTiff mode. // 2a: the value is 5-8 bytes and we're in BigTiff mode.
+141 -77
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};
@@ -251,7 +251,6 @@ struct TileAttributes {
padding_right: usize, padding_right: usize,
/// length of padding for bottommost tile in pixels /// length of padding for bottommost tile in pixels
padding_down: usize, padding_down: usize,
/// A simple buffer right paddding is read into
tile_samples: usize, tile_samples: usize,
/// Sample count of one row of one tile /// Sample count of one row of one tile
row_samples: usize, row_samples: usize,
@@ -294,8 +293,6 @@ struct TileDecodeState {
current_tile: usize, current_tile: usize,
tile_offsets: Vec<u64>, tile_offsets: Vec<u64>,
tile_bytes: Vec<u64>, tile_bytes: Vec<u64>,
/// Buffer used for skipping horizontal padding
padding_buffer: Vec<u8>,
/// Pixel width of one row of the decoding result (tile / whole image) /// Pixel width of one row of the decoding result (tile / whole image)
result_width: usize, result_width: usize,
} }
@@ -381,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 {
@@ -547,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()
} }
@@ -643,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),
}; };
@@ -665,57 +664,65 @@ impl<R: Read + Seek> Decoder<R> {
self.width = self.get_tag_u32(Tag::ImageWidth)?; self.width = self.get_tag_u32(Tag::ImageWidth)?;
self.height = self.get_tag_u32(Tag::ImageLength)?; self.height = self.get_tag_u32(Tag::ImageLength)?;
self.strip_decoder = None; self.strip_decoder = None;
self.tile_decoder = None;
self.tile_attributes = None;
self.photometric_interpretation = self self.photometric_interpretation = self
.find_tag_unsigned(Tag::PhotometricInterpretation)? .find_tag_unsigned(Tag::PhotometricInterpretation)?
.and_then(PhotometricInterpretation::from_u16) .and_then(PhotometricInterpretation::from_u16)
.ok_or(TiffUnsupportedError::UnknownInterpretation)?; .ok_or(TiffUnsupportedError::UnknownInterpretation)?;
if let Some(val) = self.find_tag_unsigned(Tag::Compression)? { // Try to parse both the compression method and the number, format, and bits of the included samples.
self.compression_method = CompressionMethod::from_u16(val) // If they are not explicitly specified, those tags are reset to their default values and not carried from previous images.
.ok_or(TiffUnsupportedError::UnknownCompressionMethod)?; self.compression_method = match self.find_tag_unsigned(Tag::Compression)? {
} Some(val) => CompressionMethod::from_u16(val)
if let Some(val) = self.find_tag_unsigned(Tag::SamplesPerPixel)? { .ok_or(TiffUnsupportedError::UnknownCompressionMethod)?,
self.samples = val; None => CompressionMethod::None,
} };
if let Some(vals) = self.find_tag_unsigned_vec(Tag::SampleFormat)? {
self.sample_format = vals self.samples = self.find_tag_unsigned(Tag::SamplesPerPixel)?.unwrap_or(1);
self.sample_format = match self.find_tag_unsigned_vec(Tag::SampleFormat)? {
Some(vals) => {
let sample_format: Vec<_> = vals
.into_iter() .into_iter()
.map(SampleFormat::from_u16_exhaustive) .map(SampleFormat::from_u16_exhaustive)
.collect(); .collect();
// TODO: for now, only homogenous formats across samples are supported. // TODO: for now, only homogenous formats across samples are supported.
if !self.sample_format.windows(2).all(|s| s[0] == s[1]) { if !sample_format.windows(2).all(|s| s[0] == s[1]) {
return Err(TiffUnsupportedError::UnsupportedSampleFormat( return Err(TiffUnsupportedError::UnsupportedSampleFormat(
self.sample_format.clone(), self.sample_format.clone(),
) )
.into()); .into());
} }
}
match self.samples {
1 | 3 | 4 => {
if let Some(val) = self.find_tag_unsigned_vec(Tag::BitsPerSample)? {
self.bits_per_sample = val;
}
}
_ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(self.samples).into()),
}
self.chunk_type = sample_format
match ( }
self.get_tag_u32(Tag::RowsPerStrip), None => vec![SampleFormat::Uint],
self.get_tag_u32(Tag::TileWidth), };
self.get_tag_u32(Tag::TileLength),
self.bits_per_sample = match self.samples {
1 | 3 | 4 => self
.find_tag_unsigned_vec(Tag::BitsPerSample)?
.unwrap_or_else(|| vec![1]),
_ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(self.samples).into()),
};
let ifd = self.ifd.as_ref().unwrap();
self.chunk_type = match (
ifd.contains_key(&Tag::StripByteCounts),
ifd.contains_key(&Tag::StripOffsets),
ifd.contains_key(&Tag::TileByteCounts),
ifd.contains_key(&Tag::TileOffsets),
) { ) {
(Ok(_), Err(_), Err(_)) => ChunkType::Strip, (true, true, false, false) => ChunkType::Strip,
(Err(_), Ok(_), Ok(_)) => ChunkType::Tile, (false, false, true, true) => ChunkType::Tile,
// TODO: The spec says not to use both strip-oriented fields and tile-oriented fields. (_, _, _, _) => {
// We can relax this later if it becomes a problem return Err(TiffError::FormatError(
_ => return Err(TiffError::FormatError(TiffFormatError::Format( TiffFormatError::StripTileTagConflict,
String::from( ))
"Neither strips nor tiles were found or both were used in the same file", }
),
))),
}; };
Ok(()) Ok(())
@@ -731,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 {
@@ -889,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),
}; };
@@ -1119,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.
@@ -1176,7 +1193,6 @@ impl<R: Read + Seek> Decoder<R> {
let tile_decoder = self.tile_decoder.as_mut().unwrap(); let tile_decoder = self.tile_decoder.as_mut().unwrap();
let line_samples = tile_decoder.result_width * self.bits_per_sample.len(); let line_samples = tile_decoder.result_width * self.bits_per_sample.len();
let padding_buffer = &mut tile_decoder.padding_buffer;
let mut reader = Self::create_reader( let mut reader = Self::create_reader(
&mut self.reader, &mut self.reader,
@@ -1184,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) {
@@ -1205,10 +1222,11 @@ impl<R: Read + Seek> Decoder<R> {
let row = &mut buf[(row_start * byte_len)..(row_end * byte_len)]; let row = &mut buf[(row_start * byte_len)..(row_end * byte_len)];
reader.read_exact(row)?; reader.read_exact(row)?;
// Skip horizontal padding // Skip horizontal padding
// TODO: find a better way of skipping the padding
if padding_right > 0 { if padding_right > 0 {
reader.read_exact(padding_buffer)? let len = u64::try_from(padding_right_samples * byte_len)?;
io::copy(&mut reader.by_ref().take(len), &mut io::sink())?;
} }
Self::fix_endianness(&mut buffer.subrange(row_start..row_end), self.byte_order); Self::fix_endianness(&mut buffer.subrange(row_start..row_end), self.byte_order);
@@ -1227,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)
} }
@@ -1277,7 +1298,13 @@ impl<R: Read + Seek> Decoder<R> {
return Ok(0); return Ok(0);
} }
Ok((self.height + rows_per_strip - 1) / rows_per_strip) // rows_per_strip - 1 can never fail since we know it's at least 1
let height = match self.height.checked_add(rows_per_strip - 1) {
Some(h) => h,
None => return Err(TiffError::IntSizeError),
};
Ok(height / rows_per_strip)
} }
/// Number of tiles in image /// Number of tiles in image
@@ -1309,6 +1336,14 @@ impl<R: Read + Seek> Decoder<R> {
let tile_width = usize::try_from(self.get_tag_u32(Tag::TileWidth)?)?; let tile_width = usize::try_from(self.get_tag_u32(Tag::TileWidth)?)?;
let tile_length = usize::try_from(self.get_tag_u32(Tag::TileLength)?)?; let tile_length = usize::try_from(self.get_tag_u32(Tag::TileLength)?)?;
if tile_width == 0 {
return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into());
}
if tile_length == 0 {
return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into());
}
let tiles_across = (usize::try_from(self.width)? + tile_width - 1) / tile_width; let tiles_across = (usize::try_from(self.width)? + tile_width - 1) / tile_width;
let tiles_down = (usize::try_from(self.height)? + tile_length - 1) / tile_length; let tiles_down = (usize::try_from(self.height)? + tile_length - 1) / tile_length;
@@ -1335,27 +1370,12 @@ impl<R: Read + Seek> Decoder<R> {
Ok(()) Ok(())
} }
fn update_tile_decoder( fn update_tile_decoder(&mut self, result_width: usize) -> TiffResult<()> {
&mut self,
result_width: usize,
buffer_byte_len: usize,
) -> TiffResult<()> {
let samples_per_pixel = self.bits_per_sample.len();
if self.tile_decoder.is_none() { if self.tile_decoder.is_none() {
let tile_attrs = self.tile_attributes.as_ref().unwrap();
let padding_buffer_size =
tile_attrs.padding_right * samples_per_pixel * buffer_byte_len;
if padding_buffer_size > self.limits.intermediate_buffer_size {
return Err(TiffError::LimitsExceeded);
}
self.tile_decoder = Some(TileDecodeState { self.tile_decoder = Some(TileDecodeState {
current_tile: 0, current_tile: 0,
tile_offsets: self.get_tag_u64_vec(Tag::TileOffsets)?, tile_offsets: self.get_tag_u64_vec(Tag::TileOffsets)?,
tile_bytes: self.get_tag_u64_vec(Tag::TileByteCounts)?, tile_bytes: self.get_tag_u64_vec(Tag::TileByteCounts)?,
padding_buffer: vec![0; padding_buffer_size],
result_width: 0, // needs to be updated for differently padded_tiles, see below result_width: 0, // needs to be updated for differently padded_tiles, see below
}) })
} }
@@ -1371,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),
}; };
@@ -1386,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() {
@@ -1442,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(
@@ -1536,7 +1589,13 @@ impl<R: Read + Seek> Decoder<R> {
} }
fn result_buffer(&self, width: usize, height: usize) -> TiffResult<DecodingResult> { fn result_buffer(&self, width: usize, height: usize) -> TiffResult<DecodingResult> {
let buffer_size = width * height * self.bits_per_sample.len(); let buffer_size = match width
.checked_mul(height)
.and_then(|x| x.checked_mul(self.bits_per_sample.len()))
{
Some(s) => s,
None => return Err(TiffError::LimitsExceeded),
};
let max_sample_bits = self.bits_per_sample.iter().cloned().max().unwrap_or(8); let max_sample_bits = self.bits_per_sample.iter().cloned().max().unwrap_or(8);
match self.sample_format.first().unwrap_or(&SampleFormat::Uint) { match self.sample_format.first().unwrap_or(&SampleFormat::Uint) {
@@ -1605,7 +1664,7 @@ impl<R: Read + Seek> Decoder<R> {
let tile_length = tile_attrs.tile_length - padding_down; let tile_length = tile_attrs.tile_length - padding_down;
let mut result = self.result_buffer(tile_width, tile_length)?; let mut result = self.result_buffer(tile_width, tile_length)?;
self.update_tile_decoder(tile_width, result.as_buffer(0).byte_len())?; self.update_tile_decoder(tile_width)?;
self.read_tile_to_buffer(&mut result.as_buffer(0), tile)?; self.read_tile_to_buffer(&mut result.as_buffer(0), tile)?;
@@ -1619,7 +1678,7 @@ impl<R: Read + Seek> Decoder<R> {
let mut result = self.result_buffer(width, usize::try_from(self.height)?)?; let mut result = self.result_buffer(width, usize::try_from(self.height)?)?;
self.init_tile_attributes()?; self.init_tile_attributes()?;
self.update_tile_decoder(width, result.as_buffer(0).byte_len())?; self.update_tile_decoder(width)?;
let tile_attrs = self.tile_attributes.as_ref().unwrap(); let tile_attrs = self.tile_attributes.as_ref().unwrap();
let tiles_across = tile_attrs.tiles_across; let tiles_across = tile_attrs.tiles_across;
@@ -1638,8 +1697,13 @@ impl<R: Read + Seek> Decoder<R> {
let rows_per_strip = let rows_per_strip =
usize::try_from(self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height))?; usize::try_from(self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height))?;
let samples_per_strip = let samples_per_strip = match usize::try_from(self.width)?
usize::try_from(self.width)? * rows_per_strip * self.bits_per_sample.len(); .checked_mul(rows_per_strip)
.and_then(|x| x.checked_mul(self.bits_per_sample.len()))
{
Some(s) => s,
None => return Err(TiffError::LimitsExceeded),
};
let mut result = let mut result =
self.result_buffer(usize::try_from(self.width)?, usize::try_from(self.height)?)?; self.result_buffer(usize::try_from(self.width)?, usize::try_from(self.height)?)?;
+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);
+32
View File
@@ -20,6 +20,14 @@ impl ColorType for Gray8 {
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
} }
pub struct GrayI8;
impl ColorType for GrayI8 {
type Inner = i8;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
}
pub struct Gray16; pub struct Gray16;
impl ColorType for Gray16 { impl ColorType for Gray16 {
type Inner = u16; type Inner = u16;
@@ -28,6 +36,14 @@ impl ColorType for Gray16 {
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
} }
pub struct GrayI16;
impl ColorType for GrayI16 {
type Inner = i16;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
}
pub struct Gray32; pub struct Gray32;
impl ColorType for Gray32 { impl ColorType for Gray32 {
type Inner = u32; type Inner = u32;
@@ -36,6 +52,14 @@ impl ColorType for Gray32 {
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
} }
pub struct GrayI32;
impl ColorType for GrayI32 {
type Inner = i32;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
}
pub struct Gray32Float; pub struct Gray32Float;
impl ColorType for Gray32Float { impl ColorType for Gray32Float {
type Inner = f32; type Inner = f32;
@@ -52,6 +76,14 @@ impl ColorType for Gray64 {
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
} }
pub struct GrayI64;
impl ColorType for GrayI64 {
type Inner = i64;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
}
pub struct Gray64Float; pub struct Gray64Float;
impl ColorType for Gray64Float { impl ColorType for Gray64Float {
type Inner = f64; type Inner = f64;
+4
View File
@@ -63,6 +63,8 @@ pub enum TiffFormatError {
SignedIntegerExpected(Value), SignedIntegerExpected(Value),
Format(String), Format(String),
RequiredTagEmpty(Tag), RequiredTagEmpty(Tag),
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,
@@ -113,6 +115,8 @@ 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."),
CycleInOffsets => write!(fmt, "File contained a cycle in the list of IFDs"),
__NonExhaustive => unreachable!(), __NonExhaustive => unreachable!(),
} }
} }
+239
View File
@@ -284,3 +284,242 @@ fn test_tiled_incremental() {
} }
} }
} }
#[test]
fn test_div_zero() {
use tiff::{TiffError, TiffFormatError};
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 39, 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, 158, 0, 0, 251, 67, 1, 3, 0,
1, 0, 0, 0, 40, 0, 0, 0, 66, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 178, 178, 178, 178,
178, 178, 178,
];
let err = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
match err {
TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {}
unexpected => panic!("Unexpected error {}", unexpected),
}
}
#[test]
fn test_too_many_value_bytes() {
let image = [
73, 73, 43, 0, 8, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 8, 0, 0, 0,
23, 0, 12, 0, 0, 65, 4, 0, 1, 6, 0, 0, 1, 16, 0, 1, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0,
0, 0, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 59, 73, 84, 186, 202, 83, 240, 66, 1, 53, 22, 56, 47,
0, 0, 0, 0, 0, 0, 1, 222, 4, 0, 58, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 4, 0, 0, 100, 0,
0, 89, 89, 89, 89, 89, 89, 89, 89, 96, 1, 20, 89, 89, 89, 89, 18,
];
let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
match error {
tiff::TiffError::LimitsExceeded => {}
unexpected => panic!("Unexpected error {}", unexpected),
}
}
#[test]
fn test_bad_strip_count() {
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 0, 1, 1, 4, 0, 1, 0, 0,
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 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, 2, 1, 3, 0, 0, 0, 0, 0, 246, 16, 0, 0, 22, 1, 4, 0, 1,
0, 0, 0, 40, 0, 251, 255, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 178, 178, 178, 178,
178, 178, 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::IntSizeError => {}
unexpected => panic!("Unexpected error {}", unexpected),
}
}
#[test]
fn test_too_big_buffer_size() {
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 99, 255, 255, 254, 1, 1, 4, 0, 1,
0, 0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0,
0, 0, 0, 0, 0, 17, 1, 4, 0, 9, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0,
22, 1, 4, 0, 1, 0, 0, 0, 44, 0, 0, 0, 23, 1, 4, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 1, 0, 178,
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 test_stripped_image_overflow() {
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 148, 1, 1, 4, 0, 1, 0,
0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 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, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0, 22,
1, 4, 0, 1, 0, 0, 0, 44, 0, 248, 255, 23, 1, 4, 0, 1, 0, 0, 0, 178, 178, 178, 0, 1, 178,
178, 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() {
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]
fn test_no_rows_per_strip() {
test_image_sum_u8("no_rows_per_strip.tiff", ColorType::RGB(8), 99448840);
}
Binary file not shown.