fix: enforce ifd_value_size limits and detect PackBits under-decode

Two input validation fixes:

- vec_with_capacity now checks raw IFD value bytes against
  ifd_value_size before allocating. Previously only the in-memory
  Value representation was bounded (by decoding_buffer_size), so a
  crafted IFD with a large count of small-typed values could allocate
  based on raw byte count without limit checking. Also fix ASCII value
  parsing to check ifd_value_size instead of decoding_buffer_size.

- LogLuv PackBits decoder now verifies that the expected number of
  pixels was actually decoded per plane, returning UnexpectedEof if
  the RLE stream ends short.
This commit is contained in:
Lilith River committed 2026-04-07 02:53:43 -06:00
1 parent 6a3282e9f2
commit e4c3c1fcfd
2 files changed
+34 -16

No files matched your search

+26 -16
View File
@@ -583,19 +583,19 @@ impl Entry {
match self.type_ { match self.type_ {
Type::BYTE | Type::UNDEFINED => { Type::BYTE | Type::UNDEFINED => {
v = Self::vec_with_capacity(self.count, limits)?; v = Self::vec_with_capacity(self.count, self.type_, limits)?;
self.decode_values(self.count, self.type_, reader, |bytes| { self.decode_values(self.count, self.type_, reader, |bytes| {
v.extend(bytes.iter().copied().map(Byte)) v.extend(bytes.iter().copied().map(Byte))
}) })
} }
Type::SBYTE => { Type::SBYTE => {
v = Self::vec_with_capacity(self.count, limits)?; v = Self::vec_with_capacity(self.count, self.type_, limits)?;
self.decode_values(self.count, self.type_, reader, |bytes| { self.decode_values(self.count, self.type_, reader, |bytes| {
v.extend(bytes.iter().copied().map(|v| SignedByte(v as i8))) v.extend(bytes.iter().copied().map(|v| SignedByte(v as i8)))
}) })
} }
Type::SHORT => { Type::SHORT => {
v = Self::vec_with_capacity(self.count, limits)?; v = Self::vec_with_capacity(self.count, self.type_, limits)?;
self.decode_values(self.count, self.type_, reader, |bytes| { self.decode_values(self.count, self.type_, reader, |bytes| {
v.extend( v.extend(
bytes bytes
@@ -605,7 +605,7 @@ impl Entry {
}) })
} }
Type::SSHORT => { Type::SSHORT => {
v = Self::vec_with_capacity(self.count, limits)?; v = Self::vec_with_capacity(self.count, self.type_, limits)?;
self.decode_values(self.count, self.type_, reader, |bytes| { self.decode_values(self.count, self.type_, reader, |bytes| {
v.extend( v.extend(
bytes bytes
@@ -615,7 +615,7 @@ impl Entry {
}) })
} }
Type::LONG => { Type::LONG => {
v = Self::vec_with_capacity(self.count, limits)?; v = Self::vec_with_capacity(self.count, self.type_, limits)?;
self.decode_values(self.count, self.type_, reader, |bytes| { self.decode_values(self.count, self.type_, reader, |bytes| {
v.extend( v.extend(
bytes bytes
@@ -625,7 +625,7 @@ impl Entry {
}) })
} }
Type::SLONG => { Type::SLONG => {
v = Self::vec_with_capacity(self.count, limits)?; v = Self::vec_with_capacity(self.count, self.type_, limits)?;
self.decode_values(self.count, self.type_, reader, |bytes| { self.decode_values(self.count, self.type_, reader, |bytes| {
v.extend( v.extend(
bytes bytes
@@ -635,7 +635,7 @@ impl Entry {
}) })
} }
Type::FLOAT => { Type::FLOAT => {
v = Self::vec_with_capacity(self.count, limits)?; v = Self::vec_with_capacity(self.count, self.type_, limits)?;
self.decode_values(self.count, self.type_, reader, |bytes| { self.decode_values(self.count, self.type_, reader, |bytes| {
v.extend( v.extend(
bytes bytes
@@ -645,7 +645,7 @@ impl Entry {
}) })
} }
Type::DOUBLE => { Type::DOUBLE => {
v = Self::vec_with_capacity(self.count, limits)?; v = Self::vec_with_capacity(self.count, self.type_, limits)?;
self.decode_values(self.count, self.type_, reader, |bytes| { self.decode_values(self.count, self.type_, reader, |bytes| {
v.extend( v.extend(
bytes bytes
@@ -655,7 +655,7 @@ impl Entry {
}) })
} }
Type::RATIONAL => { Type::RATIONAL => {
v = Self::vec_with_capacity(self.count, limits)?; v = Self::vec_with_capacity(self.count, self.type_, limits)?;
self.decode_values(self.count, self.type_, reader, |bytes| { self.decode_values(self.count, self.type_, reader, |bytes| {
v.extend(bytes.chunks_exact(8).map(|ch| { v.extend(bytes.chunks_exact(8).map(|ch| {
Rational( Rational(
@@ -666,7 +666,7 @@ impl Entry {
}) })
} }
Type::SRATIONAL => { Type::SRATIONAL => {
v = Self::vec_with_capacity(self.count, limits)?; v = Self::vec_with_capacity(self.count, self.type_, limits)?;
self.decode_values(self.count, self.type_, reader, |bytes| { self.decode_values(self.count, self.type_, reader, |bytes| {
v.extend(bytes.chunks_exact(8).map(|ch| { v.extend(bytes.chunks_exact(8).map(|ch| {
SRational( SRational(
@@ -677,7 +677,7 @@ impl Entry {
}) })
} }
Type::LONG8 => { Type::LONG8 => {
v = Self::vec_with_capacity(self.count, limits)?; v = Self::vec_with_capacity(self.count, self.type_, limits)?;
self.decode_values(self.count, self.type_, reader, |bytes| { self.decode_values(self.count, self.type_, reader, |bytes| {
v.extend( v.extend(
bytes bytes
@@ -687,7 +687,7 @@ impl Entry {
}) })
} }
Type::SLONG8 => { Type::SLONG8 => {
v = Self::vec_with_capacity(self.count, limits)?; v = Self::vec_with_capacity(self.count, self.type_, limits)?;
self.decode_values(self.count, self.type_, reader, |bytes| { self.decode_values(self.count, self.type_, reader, |bytes| {
v.extend( v.extend(
bytes bytes
@@ -697,7 +697,7 @@ impl Entry {
}) })
} }
Type::IFD => { Type::IFD => {
v = Self::vec_with_capacity(self.count, limits)?; v = Self::vec_with_capacity(self.count, self.type_, limits)?;
self.decode_values(self.count, self.type_, reader, |bytes| { self.decode_values(self.count, self.type_, reader, |bytes| {
v.extend( v.extend(
bytes bytes
@@ -707,7 +707,7 @@ impl Entry {
}) })
} }
Type::IFD8 => { Type::IFD8 => {
v = Self::vec_with_capacity(self.count, limits)?; v = Self::vec_with_capacity(self.count, self.type_, limits)?;
self.decode_values(self.count, self.type_, reader, |bytes| { self.decode_values(self.count, self.type_, reader, |bytes| {
v.extend( v.extend(
bytes bytes
@@ -719,7 +719,7 @@ impl Entry {
Type::ASCII => { Type::ASCII => {
let n = usize::try_from(self.count)?; let n = usize::try_from(self.count)?;
if n > limits.decoding_buffer_size { if n > limits.ifd_value_size {
return Err(TiffError::LimitsExceeded); return Err(TiffError::LimitsExceeded);
} }
@@ -846,10 +846,20 @@ impl Entry {
fn vec_with_capacity( fn vec_with_capacity(
value_count: u64, value_count: u64,
type_: Type,
limits: &super::Limits, limits: &super::Limits,
) -> Result<Vec<Value>, TiffError> { ) -> Result<Vec<Value>, TiffError> {
let value_count = usize::try_from(value_count)?; // Check raw file bytes against ifd_value_size.
if !type_
.value_bytes(value_count)
.and_then(|b| usize::try_from(b).map_err(|_| TiffError::LimitsExceeded))
.is_ok_and(|raw_len| raw_len <= limits.ifd_value_size)
{
return Err(TiffError::LimitsExceeded);
}
// Check in-memory Value representation against decoding_buffer_size.
let value_count = usize::try_from(value_count)?;
if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() { if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
return Err(TiffError::LimitsExceeded); return Err(TiffError::LimitsExceeded);
} }
+8
View File
@@ -87,6 +87,7 @@ fn packbits_decode<R: Read, const N: usize>(
reader: &mut R, reader: &mut R,
buf: &mut [[u8; N]], buf: &mut [[u8; N]],
) -> std::io::Result<()> { ) -> std::io::Result<()> {
let expected = buf.len();
for offset in (0..N).rev() { for offset in (0..N).rev() {
let mut i = 0; let mut i = 0;
let mut code = [0u8; 1]; let mut code = [0u8; 1];
@@ -124,6 +125,13 @@ fn packbits_decode<R: Read, const N: usize>(
i += usize::from(repeat); i += usize::from(repeat);
} }
} }
if i < expected {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"PackBits RLE decoded fewer pixels than expected",
));
}
} }
Ok(()) Ok(())