Commit ff916a19 for libheif
commit ff916a19e9d3dc330d0a8c0bfeb7987ea93614c5
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Sun May 3 01:40:51 2026 +0200
tili: reject tile offset/size that overflow the configured field width
Box_tilC encodes the tile offset table with offset and size fields whose
widths (32/40/48/64 and 0/24/32/64) are configured in the box flags.
writevec() blindly stored the low N bytes of a uint64_t, so an offset
past the configured field width was silently truncated, leaving the
written offset table pointing into nonsense in the iloc data. At decode
time the truncated offset would point at random bytes, which the
generic-compression decompressor (zlib/brotli) then reported as
"invalid stored block lengths" - reproducible by zooming to the highest
pyramid layer of a tili image larger than the offset field can address.
set_tild_tile_range now returns an Error when the value does not fit,
and write_offset_table re-checks each row as a defense-in-depth before
emitting the table. The existing add_image_tile call site propagates
the new error.
Existing files written with truncated offsets remain corrupt on disk;
this only prevents producing such files going forward.
diff --git a/libheif/image-items/tiled.cc b/libheif/image-items/tiled.cc
index aaccdabd..6ffefaa2 100644
--- a/libheif/image-items/tiled.cc
+++ b/libheif/image-items/tiled.cc
@@ -440,11 +440,33 @@ std::pair<uint32_t, uint32_t> TiledHeader::get_tile_offset_table_range_to_read(u
}
-void TiledHeader::set_tild_tile_range(uint32_t tile_x, uint32_t tile_y, uint64_t offset, uint32_t size)
+Error TiledHeader::set_tild_tile_range(uint32_t tile_x, uint32_t tile_y, uint64_t offset, uint32_t size)
{
+ // Offset and size are written into bit-fields of the configured widths;
+ // silently truncating here produces files where the offset table points to
+ // garbage. Reject the value so the caller knows to widen the fields.
+ uint8_t off_bits = m_parameters.offset_field_length;
+ uint8_t sz_bits = m_parameters.size_field_length;
+
+ if (off_bits < 64 && offset >> off_bits) {
+ std::stringstream sstr;
+ sstr << "Tile offset " << offset << " does not fit in the configured "
+ << static_cast<int>(off_bits) << "-bit offset field. Use a wider "
+ "offset_field_length (40/48/64) when encoding the tili image.";
+ return {heif_error_Encoding_error, heif_suberror_Unspecified, sstr.str()};
+ }
+
+ if (sz_bits != 0 && sz_bits < 32 && size >> sz_bits) {
+ std::stringstream sstr;
+ sstr << "Tile size " << size << " does not fit in the configured "
+ << static_cast<int>(sz_bits) << "-bit size field.";
+ return {heif_error_Encoding_error, heif_suberror_Unspecified, sstr.str()};
+ }
+
uint64_t idx = uint64_t{tile_y} * nTiles_h(m_parameters) + tile_x;
m_offsets[idx].offset = offset;
m_offsets[idx].size = size;
+ return Error::Ok;
}
@@ -475,7 +497,23 @@ Result<std::vector<uint8_t>> TiledHeader::write_offset_table()
size_t idx = 0;
+ uint8_t off_bits = m_parameters.offset_field_length;
+ uint8_t sz_bits = m_parameters.size_field_length;
+
for (const auto& offset: m_offsets) {
+ if (off_bits < 64 && offset.offset >> off_bits) {
+ std::stringstream sstr;
+ sstr << "Tile offset " << offset.offset << " does not fit in the "
+ "configured " << static_cast<int>(off_bits) << "-bit offset field.";
+ return Error{heif_error_Encoding_error, heif_suberror_Unspecified, sstr.str()};
+ }
+ if (sz_bits != 0 && sz_bits < 32 && offset.size >> sz_bits) {
+ std::stringstream sstr;
+ sstr << "Tile size " << offset.size << " does not fit in the "
+ "configured " << static_cast<int>(sz_bits) << "-bit size field.";
+ return Error{heif_error_Encoding_error, heif_suberror_Unspecified, sstr.str()};
+ }
+
writevec(data.data(), idx, offset.offset, m_parameters.offset_field_length / 8);
if (m_parameters.size_field_length != 0) {
@@ -784,7 +822,9 @@ Error ImageItem_Tiled::add_image_tile(uint32_t tile_x, uint32_t tile_y,
if (dataSize > 0xFFFFFFFF) {
return {heif_error_Encoding_error, heif_suberror_Unspecified, "Compressed tile size exceeds maximum tile size."};
}
- header.set_tild_tile_range(tile_x, tile_y, offset, static_cast<uint32_t>(dataSize));
+ if (Error err = header.set_tild_tile_range(tile_x, tile_y, offset, static_cast<uint32_t>(dataSize))) {
+ return err;
+ }
set_next_tild_position(offset + encodeResult->bitstream.size());
auto tilC = get_property<Box_tilC>();
diff --git a/libheif/image-items/tiled.h b/libheif/image-items/tiled.h
index 6ced023a..debc60f5 100644
--- a/libheif/image-items/tiled.h
+++ b/libheif/image-items/tiled.h
@@ -107,7 +107,10 @@ public:
std::string dump() const;
- void set_tild_tile_range(uint32_t tile_x, uint32_t tile_y, uint64_t offset, uint32_t size);
+ // Returns an error if `offset` does not fit in offset_field_length or
+ // `size` does not fit in size_field_length. Catches the encoder-side
+ // overflow that would otherwise silently truncate the field at write time.
+ Error set_tild_tile_range(uint32_t tile_x, uint32_t tile_y, uint64_t offset, uint32_t size);
size_t get_header_size() const;