diff --git a/src/bmp/decoder.rs b/src/bmp/decoder.rs index 935dd4490e..0a25e5b11b 100644 --- a/src/bmp/decoder.rs +++ b/src/bmp/decoder.rs @@ -1,6 +1,9 @@ +use std::cmp; use std::io::{Read, Seek, SeekFrom}; +use std::io; use std::iter::{Iterator, repeat, Rev}; use std::slice::ChunksMut; + use byteorder::{ReadBytesExt, LittleEndian}; use image::{ @@ -41,6 +44,9 @@ const RLE_ESCAPE_EOL: u8 = 0; const RLE_ESCAPE_EOF: u8 = 1; const RLE_ESCAPE_DELTA: u8 = 2; +/// The maximum width/height the decoder will process. +const MAX_WIDTH_HEIGHT: i32 = 65535; + #[derive(PartialEq, Copy, Clone)] enum ImageType { Palette, @@ -93,6 +99,143 @@ impl<'a> Iterator for RowIterator<'a> { } } +/// Convenience function to check if the combination of width, length and number of +/// channels would result in a buffer that would overflow. +fn check_for_overflow(width: i32, length: i32, channels: usize) -> ImageResult<()> { + num_bytes(width, length, channels).map(|_| ()).ok_or_else(|| ImageError::FormatError("Image would require a buffer that is too large to be represented!".to_owned())) +} + +/// Calculate how many many bytes a buffer holding a decoded image with these properties would +/// require. Returns `None` if the buffer size would overflow or if one of the sizes are negative. +fn num_bytes(width: i32, length: i32, channels: usize) -> Option { + if width <= 0 || length <= 0 { + None + } else { + match channels.checked_mul(width as usize) { + Some(n) => n.checked_mul(length as usize), + None => None + } + } +} + +/// The maximum starting number of pixels in the pixel buffer, might want to tweak this. +/// +/// For images that specify large sizes, we don't allocate the full buffer right away +/// to somewhat mitigate trying to make the decoder run out of memory by sending a bogus image. +/// This is somewhat of a workaroud as ideally we would check against the expected file size +/// but that's not possible through the Read and Seek traits alone and would require the encoder +/// to provided with it from the caller. +/// +/// NOTE: This is multiplied by 3 or 4 depending on the number of channels to get the maximum +/// starting buffer size. This amounts to about 134 mb for a buffer with 4 channels. +const MAX_INITIAL_PIXELS: usize = 8192 * 4096; + +/// Sets all bytes in an mutable iterator over slices of bytes to 0. +fn blank_bytes<'a, T: Iterator>(iterator: T) { + for chunk in iterator { + for b in chunk { + *b = 0; + } + } +} + +/// Extend the buffer to `full_size`, copying existing data to the end of the buffer. Returns slice +/// pointing to the part of the buffer that is not yet filled in. +/// +/// If blank is true, the bytes in the new buffer that are not filled in are set to 0. +/// This is used for rle-encoded images as the decoding process for these may not fill in all the +/// pixels. +/// +/// As BMP images are usually stored with the rows upside-down we have to write the image data +/// starting at the end of the buffer and thus we have to make sure the existing data is put at the +/// end of the buffer. +#[inline(never)] +#[cold] +fn extend_buffer(buffer: &mut Vec, full_size: usize, blank: bool) -> &mut [u8] { + let old_size = buffer.len(); + let extend = full_size - buffer.len(); + + buffer.extend(repeat(0xFF).take(extend)); + + let buffer_len = buffer.len(); + + let ret = if extend >= old_size { + // If the full buffer length is more or equal to twice the initial one, we can simply + // copy the data in the lower part of the buffer to the end of it and input from there. + { + let (lower, upper) = buffer.split_at_mut(old_size); + let upper_len = upper.len(); + let lower_len = lower.len(); + + upper[upper_len - lower_len..].copy_from_slice(lower); + } + + &mut buffer[..buffer_len - old_size] + } else { + // If the full size is less than twice the initial buffer, we have to + // copy in two steps + + // First we copy the data that fits into the bit we extended. + let (lower, upper) = buffer.split_at_mut(old_size); + let upper_len = upper.len(); + let lower_len = lower.len(); + upper.copy_from_slice(&lower[lower_len - upper_len..]); + + // Then we slide the data that hasn't been copied yet to the top of the buffer + let (new, old) = lower.split_at_mut(extend); + let new_len = new.len(); + let old_len = old.len(); + old.copy_from_slice(&new[new_len - old_len..]); + new + }; + if blank { + for b in ret.iter_mut() { + *b = 0; + } + }; + ret +} + +/// Call the provided function on each row of the provided buffer, returning Err if the provided +/// function returns an error, extends the buffer if it's not large enough. +fn with_rows(buffer: &mut Vec, width: i32, height: i32, channels: usize, top_down: bool, mut func: F) + -> io::Result<()> where F: FnMut(&mut [u8]) -> io::Result<()> { + // An overflow should already have been checked for when this is called, + // though we check anyhow, as it somehow seems to increase performance slightly. + let row_width = channels.checked_mul(width as usize).unwrap(); + let full_image_size = row_width.checked_mul(height as usize).unwrap(); + + if !top_down { + for row in buffer.chunks_mut(row_width).rev() { + try!(func(row)); + } + + // If we need more space, extend the buffer. + if buffer.len() < full_image_size { + let new_space = extend_buffer(buffer, full_image_size, false); + for row in new_space.chunks_mut(row_width).rev() { + try!(func(row)); + } + } + } else { + for row in buffer.chunks_mut(row_width) { + try!(func(row)); + } + if buffer.len() < full_image_size { + // If the image is stored in top-down order, we can simply use the extend function + // from vec to extend the buffer.. + let extend = full_image_size - buffer.len(); + buffer.extend(repeat(0xFF).take(extend)); + let len = buffer.len(); + for row in buffer[len - row_width..].chunks_mut(row_width) { + try!(func(row)); + } + }; + } + Ok(()) +} + + fn set_8bit_pixel_run<'a, T: Iterator>(pixel_iter: &mut ChunksMut, palette: &[(u8, u8, u8)], indices: T, n_pixels: usize) -> bool { @@ -364,12 +507,21 @@ impl BMPDecoder { Ok(()) } + /// Read BITMAPCOREHEADER https://msdn.microsoft.com/en-us/library/vs/alm/dd183372(v=vs.85).aspx + /// + /// returns Err if any of the values are invalid. fn read_bitmap_core_header(&mut self) ->ImageResult<()> { + // As height/width values in BMP files with core headers are only 16 bits long, + // they won't be larger than `MAX_WIDTH_HEIGHT`. self.width = try!(self.r.read_u16::()) as i32; self.height = try!(self.r.read_u16::()) as i32; - // Don't care about number of planes - try!(self.r.read_u16::()); + try!(check_for_overflow(self.width, self.height, self.num_channels())); + + // Number of planes (format specifies that this should be 1). + if try!(self.r.read_u16::()) != 1 { + return Err(ImageError::FormatError("Invalid number of planes.".to_string())); + } self.bit_count = try!(self.r.read_u16::()); self.image_type = match self.bit_count { @@ -381,28 +533,48 @@ impl BMPDecoder { Ok(()) } + /// Read BITMAPINFOHEADER https://msdn.microsoft.com/en-us/library/vs/alm/dd183376(v=vs.85).aspx + /// or BITMAPV{2|3|4|5}HEADER. + /// + /// returns Err if any of the values are invalid. fn read_bitmap_info_header(&mut self) -> ImageResult<()> { self.width = try!(self.r.read_i32::()); self.height = try!(self.r.read_i32::()); + // Width can not be negative if self.width < 0 { return Err(ImageError::FormatError("Negative width".to_string())); + } else if self.width > MAX_WIDTH_HEIGHT || self.height > MAX_WIDTH_HEIGHT { + // Limit very large image sizes to avoid OOM issues. Images with these sizes are + // unlikely to be valid anyhow. + return Err(ImageError::FormatError("Image too large".to_string())); } if self.height == i32::min_value() { return Err(ImageError::FormatError("Invalid height".to_string())); } + // A negative height indicates a top-down DIB. if self.height < 0 { self.height *= -1; self.top_down = true; } - // Don't care about number of planes - try!(self.r.read_u16::()); + try!(check_for_overflow(self.width, self.height, self.num_channels())); + + // Number of planes (format specifies that this should be 1). + if try!(self.r.read_u16::()) != 1 { + return Err(ImageError::FormatError("Invalid number of planes.".to_string())); + } self.bit_count = try!(self.r.read_u16::()); let image_type_u32 = try!(self.r.read_u32::()); + + // Top-down dibs can not be compressed. + if self.top_down && image_type_u32 != 0 && image_type_u32 != 3 { + return Err(ImageError::FormatError("Invalid image type for top-down image." + .to_string())); + } self.image_type = match image_type_u32 { 0 => match self.bit_count { 1 | 4 | 8 => ImageType::Palette, @@ -424,6 +596,7 @@ impl BMPDecoder { 32 => ImageType::Bitfields32, _ => return Err(ImageError::FormatError("Invalid bitfields bit count".to_string())), }, + // PNG and JPEG not implemented yet. _ => return Err(ImageError::UnsupportedError("Unsupported image type".to_string())), }; @@ -558,18 +731,25 @@ impl BMPDecoder { let bytes_per_color = self.bytes_per_color(); let palette_size = try!(self.get_palette_size()); - let length = palette_size * bytes_per_color; let max_length = MAX_PALETTE_SIZE * bytes_per_color; + + let length = palette_size * bytes_per_color; let mut buf = Vec::with_capacity(max_length); - buf.resize(length, 0); + // Resize and read the palette entries to the buffer. + // We limit the buffer to at most 256 colours to avoid any oom issues as + // 8-bit images can't reference more than 256 indexes anyhow. + buf.resize(cmp::min(length, max_length), 0); try!(self.r.by_ref().read_exact(&mut buf)); // Allocate 256 entries even if palette_size is smaller, to prevent corrupt files from // causing an out-of-bounds array access. if length < max_length { buf.resize(max_length, 0); - } + } else if length > max_length { + // Ignore any excess palette colors. + try!(self.r.seek(SeekFrom::Current((length - max_length) as i64))); + }; let p: Vec<(u8, u8, u8)> = (0..MAX_PALETTE_SIZE).map(|i| { let b = buf[bytes_per_color * i]; @@ -587,11 +767,23 @@ impl BMPDecoder { if self.add_alpha_channel { 4 } else { 3 } } + /// Create a buffer to hold the decoded pixel data. + /// + /// The buffer will be large enough to hold the whole image if it requires less than + /// `MAX_INITIAL_PIXELS` times the number of channels bytes (adjusted to line up with the + /// width of a row). fn create_pixel_data(&self) -> Vec { - vec![0xFF; self.num_channels() * self.width as usize * self.height as usize] + let row_width = self.num_channels() * self.width as usize; + let max_pixels = self.num_channels() * MAX_INITIAL_PIXELS; + // Make sure the maximum size is whole number of rows. + let max_starting_size = max_pixels + row_width - (max_pixels % row_width); + // The buffer has its bytes initilally set to 0xFF as the ICO decoder relies on it. + vec![0xFF; cmp::min( + row_width * self.height as usize, + max_starting_size)] } - fn rows<'a>(&self, pixel_data: &'a mut Vec) -> RowIterator<'a> { + fn rows<'a>(&self, pixel_data: &'a mut [u8]) -> RowIterator<'a> { let stride = self.width as usize * self.num_channels(); if self.top_down { RowIterator{ chunks: Chunker::FromTop(pixel_data.chunks_mut(stride)) } @@ -606,18 +798,23 @@ impl BMPDecoder { let row_byte_length = ((self.bit_count as u32 * self.width as u32 + 31) / 32 * 4) as usize; let mut indices = vec![0; row_byte_length]; let palette = self.palette.as_ref().unwrap(); + let bit_count = self.bit_count; + let reader = &mut self.r; + let width = self.width as usize; - try!(self.r.seek(SeekFrom::Start(self.data_offset))); - for row in self.rows(&mut pixel_data) { - try!(self.r.by_ref().read_exact(&mut indices)); + try!(reader.seek(SeekFrom::Start(self.data_offset))); + + try!(with_rows(&mut pixel_data, self.width, self.height, num_channels, self.top_down, |row| { + try!(reader.read_exact(&mut indices)); let mut pixel_iter = row.chunks_mut(num_channels); - match self.bit_count { + match bit_count { 1 => { set_1bit_pixel_run(&mut pixel_iter, palette, indices.iter()); }, - 4 => { set_4bit_pixel_run(&mut pixel_iter, palette, indices.iter(), self.width as usize); }, - 8 => { set_8bit_pixel_run(&mut pixel_iter, palette, indices.iter(), self.width as usize); }, + 4 => { set_4bit_pixel_run(&mut pixel_iter, palette, indices.iter(), width); }, + 8 => { set_8bit_pixel_run(&mut pixel_iter, palette, indices.iter(), width); }, _ => panic!(), - } - } + }; + Ok(()) + })); Ok(pixel_data) } @@ -631,11 +828,13 @@ impl BMPDecoder { Some(b) => b, None => self.bitfields.as_ref().unwrap() }; + let reader = &mut self.r; - try!(self.r.seek(SeekFrom::Start(self.data_offset))); - for row in self.rows(&mut pixel_data) { + try!(reader.seek(SeekFrom::Start(self.data_offset))); + + try!(with_rows(&mut pixel_data, self.width, self.height, num_channels, self.top_down, |row| { for pixel in row.chunks_mut(num_channels) { - let data = try!(self.r.read_u16::()) as u32; + let data = try!(reader.read_u16::()) as u32; pixel[0] = bitfields.r.read(data); pixel[1] = bitfields.g.read(data); @@ -644,21 +843,25 @@ impl BMPDecoder { pixel[3] = bitfields.a.read(data); } } - try!(self.r.read_exact(row_padding)); - } + reader.read_exact(row_padding) + })); Ok(pixel_data) } + /// Read image data from a reader in 32-bit formats that use bitfields. fn read_32_bit_pixel_data(&mut self) -> ImageResult> { let mut pixel_data = self.create_pixel_data(); let num_channels = self.num_channels(); + let bitfields = self.bitfields.as_ref().unwrap(); - try!(self.r.seek(SeekFrom::Start(self.data_offset))); - for row in self.rows(&mut pixel_data) { + let mut reader = &mut self.r; + try!(reader.seek(SeekFrom::Start(self.data_offset))); + + try!(with_rows(&mut pixel_data, self.width, self.height, num_channels, self.top_down, |row| { for pixel in row.chunks_mut(num_channels) { - let data = try!(self.r.read_u32::()); + let data = try!(reader.read_u32::()); pixel[0] = bitfields.r.read(data); pixel[1] = bitfields.g.read(data); @@ -667,11 +870,13 @@ impl BMPDecoder { pixel[3] = bitfields.a.read(data); } } - } + Ok(()) + })); Ok(pixel_data) } + /// Read image data from a reader where the colours are stored as 8-bit values (24 or 32-bit). fn read_full_byte_pixel_data(&mut self, format: FormatFullBytes) -> ImageResult> { let mut pixel_data = self.create_pixel_data(); let num_channels = self.num_channels(); @@ -682,41 +887,66 @@ impl BMPDecoder { let row_padding = &mut [0; 4][..row_padding_len]; try!(self.r.seek(SeekFrom::Start(self.data_offset))); - for row in self.rows(&mut pixel_data) { + + let reader = &mut self.r; + + try!(with_rows(&mut pixel_data, self.width, self.height, num_channels, self.top_down, |row| { for pixel in row.chunks_mut(num_channels) { if format == FormatFullBytes::Format888 { - try!(self.r.read_u8()); + try!(reader.read_u8()); } - let b = try!(self.r.read_u8()); - let g = try!(self.r.read_u8()); - let r = try!(self.r.read_u8()); + // Read the colour values (b, g, r). + // Reading 3 bytes and reversing them is significantly faster than reading one + // at a time. + try!(reader.read_exact(&mut pixel[0..3])); + pixel[0..3].reverse(); if format == FormatFullBytes::FormatRGB32 { - try!(self.r.read_u8()); + try!(reader.read_u8()); } - pixel[0] = r; - pixel[1] = g; - pixel[2] = b; - + // Read the alpha channel if present if format == FormatFullBytes::FormatRGBA32 { - let a = try!(self.r.read_u8()); - pixel[3] = a; + try!(reader.read_exact(&mut pixel[3..4])); } } - try!(self.r.read_exact(row_padding)); - } + reader.read_exact(row_padding) + })); Ok(pixel_data) } fn read_rle_data(&mut self, image_type: ImageType) -> ImageResult> { + // Seek to the start of the actual image data. + try!(self.r.seek(SeekFrom::Start(self.data_offset))); + + let full_image_size = try!( + num_bytes(self.width, self.height, self.num_channels()) + .ok_or_else(|| ImageError::FormatError("Image buffer would be too large!".to_owned())) + ); let mut pixel_data = self.create_pixel_data(); + let (skip_pixels, skip_rows, eof_hit) = try!(self.read_rle_data_step(&mut pixel_data, image_type, 0, 0)); + // Extend the buffer if there is still data left. + // If eof_hit is true, it means that we hit an end-of-file marker in the last step and + // we won't extend the buffer further to avoid small files with a large specified size causing memory issues. + // This is only a rudamentary check, a file could still create a large buffer, but the + // file would now have to at least have some data in it. + if pixel_data.len() < full_image_size && !eof_hit { + let new = extend_buffer(&mut pixel_data, full_image_size, true); + try!(self.read_rle_data_step(new, image_type, skip_pixels, skip_rows)); + } + Ok(pixel_data) + } + + fn read_rle_data_step(&mut self, mut pixel_data: &mut [u8], image_type: ImageType, skip_pixels: u8, skip_rows: u8) -> ImageResult<(u8, u8, bool)> { let num_channels = self.num_channels(); - try!(self.r.seek(SeekFrom::Start(self.data_offset))); + let mut delta_rows_left = 0; + let mut delta_pixels_left = skip_pixels; + let mut eof_hit = false; + // Scope the borrowing of pixel_data by the row iterator. { // Handling deltas in the RLE scheme means that we need to manually @@ -724,35 +954,60 @@ impl BMPDecoder { // deltas, we have to ensure that a single runlength doesn't straddle // two rows. let mut row_iter = self.rows(&mut pixel_data); + // If we have previously hit a delta value, + // blank the rows that are to be skipped. + blank_bytes((&mut row_iter).take(skip_rows.into())); let mut insns_iter = RLEInsnIterator{ r: &mut self.r, image_type: image_type }; let p = self.palette.as_ref().unwrap(); 'row_loop: while let Some(row) = row_iter.next() { let mut pixel_iter = row.chunks_mut(num_channels); + // Blank delta skipped pixels if any. + blank_bytes((&mut pixel_iter).take(delta_pixels_left.into())); + delta_pixels_left = 0; 'rle_loop: loop { if let Some(insn) = insns_iter.next() { match insn { RLEInsn::EndOfFile => { + blank_bytes(pixel_iter); + blank_bytes(row_iter); + eof_hit = true; break 'row_loop; }, RLEInsn::EndOfRow => { + blank_bytes(pixel_iter); break 'rle_loop; }, RLEInsn::Delta(x_delta, y_delta) => { - for _ in 0..x_delta { - if pixel_iter.next().is_none() { - // We can't go any further in this row. - break; + if y_delta > 0 { + for n in 1..y_delta { + if let Some(row) = row_iter.next() { + // The msdn site on bitmap compression doesn't specify + // what happens to the values skipped when encountering + // a delta code, however IE and the windows image + // preview seems to replace them with black pixels, + // so we stick to that. + for b in row { + *b = 0; + } + } else { + delta_pixels_left = x_delta; + // We've reached the end of the buffer. + delta_rows_left = y_delta - n; + break 'row_loop; + } } } - if y_delta > 0 { - for _ in 1..y_delta { - if row_iter.next().is_none() { - // We've reached the end of the image. - break 'row_loop; + for _ in 0..x_delta { + if let Some(pixel) = pixel_iter.next() { + for b in pixel { + *b = 0; } + } else { + // We can't go any further in this row. + break; } } }, @@ -813,7 +1068,7 @@ impl BMPDecoder { } } - Ok(pixel_data) + Ok((delta_pixels_left, delta_rows_left, eof_hit)) } fn read_image_data(&mut self) -> ImageResult> { diff --git a/src/buffer.rs b/src/buffer.rs index 6622241060..5660b1c71f 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -650,6 +650,7 @@ mod test { #[bench] #[cfg(feature = "benchmarks")] fn bench_conversion(b: &mut test::Bencher) { + use buffer::{GrayImage, Pixel, ConvertBuffer}; let mut a: RgbImage = ImageBuffer::new(1000, 1000); for mut p in a.pixels_mut() { let rgb = p.channels_mut(); diff --git a/src/ico/decoder.rs b/src/ico/decoder.rs index 2c966861cd..fdd805d52b 100644 --- a/src/ico/decoder.rs +++ b/src/ico/decoder.rs @@ -23,6 +23,7 @@ enum InnerDecoder { PNG(PNGDecoder) } + #[derive(Clone, Copy, Default)] struct DirEntry { width: u8, @@ -64,10 +65,26 @@ fn read_entry(r: &mut R) -> ImageResult { entry.width = try!(r.read_u8()); entry.height = try!(r.read_u8()); entry.color_count = try!(r.read_u8()); + // Reserved value (not used) entry.reserved = try!(r.read_u8()); + // This may be either the number of color planes (0 or 1), or the horizontal coordinate + // of the hotspot for CUR files. entry.num_color_planes = try!(r.read_u16::()); + if entry.num_color_planes > 256 { + return Err(ImageError::FormatError( + "ICO image entry has a too large color planes/hotspot value".to_string() + )); + } + + // This may be either the bit depth (may be 0 meaning unspecified), + // or the vertical coordinate of the hotspot for CUR files. entry.bits_per_pixel = try!(r.read_u16::()); + if entry.bits_per_pixel > 256 { + return Err(ImageError::FormatError( + "ICO image entry has a too large bits per pixel/hotspot value".to_string() + )); + } entry.image_length = try!(r.read_u32::()); entry.image_offset = try!(r.read_u32::()); @@ -106,6 +123,11 @@ impl DirEntry { } } + fn matches_dimensions(&self, width: u32, height: u32) -> bool { + u32::from(self.real_width()) == width && + u32::from(self.real_height()) == height + } + fn seek_to_start(&self, r: &mut R) -> ImageResult<()> { try!(r.seek(SeekFrom::Start(self.image_offset as u64))); Ok(()) @@ -167,15 +189,45 @@ impl ImageDecoder for ICODecoder { fn read_image(&mut self) -> ImageResult { match self.inner_decoder { PNG(ref mut decoder) => { + if self.selected_entry.image_length < PNG_SIGNATURE.len() as u32 { + return Err(ImageError::FormatError( + "Entry specified a length that is shorter than PNG header!".to_string() + )); + } + + // Check if the image dimensions match the ones in the image data. + let (width, height) = try!(decoder.dimensions()); + if !self.selected_entry.matches_dimensions(width, height) { + return Err(ImageError::FormatError( + "Entry and PNG dimensions do not match!".to_string()) + ); + + } + + // Embedded PNG images can only be of the 32BPP RGBA format. + // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473/ + let color_type = try!(decoder.colortype()); + if let ColorType::RGBA(8) = color_type {} else { + return Err(ImageError::FormatError( + "The PNG is not in RGBA format!".to_string() + )); + } + decoder.read_image() } BMP(ref mut decoder) => { let (width, height) = try!(decoder.dimensions()); + if !self.selected_entry.matches_dimensions(width, height) { + return Err(ImageError::FormatError( + "Entry({:?}) and BMP({:?}) dimensions do not match!".to_string() + )); + } // The ICO decoder needs an alpha chanel to apply the AND mask. if try!(decoder.colortype()) != ColorType::RGBA(8) { return Err(ImageError::UnsupportedError("Unsupported color type".to_string())) } + let mut pixel_data = match try!(decoder.read_image()) { DecodingResult::U8(v) => v, _ => unreachable!() diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index 91485d5d23..90f481c611 100644 --- a/src/imageops/sample.rs +++ b/src/imageops/sample.rs @@ -505,6 +505,7 @@ mod tests { #[bench] #[cfg(all(feature = "benchmarks", feature = "png_codec"))] fn bench_resize(b: &mut test::Bencher) { + use std::path::Path; let img = ::open(&Path::new("./examples/fractal.png")).unwrap(); b.iter(|| { test::black_box(resize(&img, 200, 200, ::Nearest )); diff --git a/tests/images/bmp/images/Bad_clrsUsed.bad_bmp b/tests/images/bmp/images/Bad_clrsUsed.bad_bmp new file mode 100644 index 0000000000..ddd3b95a04 Binary files /dev/null and b/tests/images/bmp/images/Bad_clrsUsed.bad_bmp differ diff --git a/tests/images/bmp/images/Bad_height.bad_bmp b/tests/images/bmp/images/Bad_height.bad_bmp new file mode 100644 index 0000000000..c26ef3731d Binary files /dev/null and b/tests/images/bmp/images/Bad_height.bad_bmp differ diff --git a/tests/images/bmp/images/Bad_width.bad_bmp b/tests/images/bmp/images/Bad_width.bad_bmp new file mode 100644 index 0000000000..1db72c3fba Binary files /dev/null and b/tests/images/bmp/images/Bad_width.bad_bmp differ diff --git a/tests/images/bmp/images/pal4rlecut.bmp b/tests/images/bmp/images/pal4rlecut.bmp new file mode 100644 index 0000000000..2f32d1d7ad Binary files /dev/null and b/tests/images/bmp/images/pal4rlecut.bmp differ diff --git a/tests/images/bmp/images/pal4rletrns.bmp b/tests/images/bmp/images/pal4rletrns.bmp new file mode 100644 index 0000000000..58994e92ba Binary files /dev/null and b/tests/images/bmp/images/pal4rletrns.bmp differ diff --git a/tests/reference/bmp/images/pal4rlecut.bmp.ad5aeaab.png b/tests/reference/bmp/images/pal4rlecut.bmp.ad5aeaab.png new file mode 100644 index 0000000000..585e39f40a Binary files /dev/null and b/tests/reference/bmp/images/pal4rlecut.bmp.ad5aeaab.png differ diff --git a/tests/reference/bmp/images/pal4rletrns.bmp.935d0429.png b/tests/reference/bmp/images/pal4rletrns.bmp.935d0429.png new file mode 100644 index 0000000000..9befa575fa Binary files /dev/null and b/tests/reference/bmp/images/pal4rletrns.bmp.935d0429.png differ diff --git a/tests/reference_images.rs b/tests/reference_images.rs index 61aa7c97ca..edc92abbf5 100644 --- a/tests/reference_images.rs +++ b/tests/reference_images.rs @@ -145,6 +145,19 @@ fn check_hdr_references() { } } +/// Check that BMP files with large values could cause OOM issues are rejected. +/// +/// The images are postfixed with `bad_bmp` to not be loaded by the other test. +#[test] +fn bad_bmps() { + let base_path: PathBuf = BASE_PATH.iter().collect::().join(IMAGE_DIR).join("bmp/images"); + + assert!(image::open(base_path.join("Bad_clrsUsed.bad_bmp")).is_err(), "Image with absurly large number of colors loaded."); + assert!(image::open(base_path.join("Bad_width.bad_bmp")).is_err(), "Image with absurdly large width loaded."); + assert!(image::open(base_path.join("Bad_height.bad_bmp")).is_err(), "Image with absurdly large height loaded."); + +} + const CRC_TABLE: [u32; 256] = [ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4,