Commit e7e8a0b5 for libheif
commit e7e8a0b52e7aeabb2af59f233c290fabe649871d
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Thu Dec 25 20:45:12 2025 +0100
[BSD3] check that number of samples in various boxes is consistent (fixes #1645)
diff --git a/libheif/sequences/chunk.cc b/libheif/sequences/chunk.cc
index 0339b58f..967a1c78 100644
--- a/libheif/sequences/chunk.cc
+++ b/libheif/sequences/chunk.cc
@@ -90,6 +90,7 @@ Chunk::Chunk(HeifContext* ctx, uint32_t track_id,
range.size = stsz->get_fixed_sample_size();
}
else {
+ assert(first_sample + i < stsz->num_samples());
range.size = stsz->get_sample_sizes()[first_sample + i];
}
diff --git a/libheif/sequences/seq_boxes.cc b/libheif/sequences/seq_boxes.cc
index 5170f4d1..3fadfa35 100644
--- a/libheif/sequences/seq_boxes.cc
+++ b/libheif/sequences/seq_boxes.cc
@@ -665,6 +665,17 @@ uint64_t Box_stts::get_total_duration(bool include_last_frame_duration)
}
+uint32_t Box_stts::get_number_of_samples() const
+{
+ uint32_t total = 0;
+ for (const auto& entry : m_entries) {
+ total += entry.sample_count;
+ }
+
+ return total;
+}
+
+
Error Box_ctts::parse(BitstreamRange& range, const heif_security_limits* limits)
{
parse_full_box_header(range);
@@ -762,6 +773,17 @@ int32_t Box_ctts::compute_min_offset() const
}
+uint32_t Box_ctts::get_number_of_samples() const
+{
+ uint32_t total = 0;
+ for (const auto& entry : m_entries) {
+ total += entry.sample_count;
+ }
+
+ return total;
+}
+
+
Error Box_ctts::write(StreamWriter& writer) const
{
size_t box_start = reserve_box_header_space(writer);
diff --git a/libheif/sequences/seq_boxes.h b/libheif/sequences/seq_boxes.h
index 3f61be58..c1eaf279 100644
--- a/libheif/sequences/seq_boxes.h
+++ b/libheif/sequences/seq_boxes.h
@@ -351,6 +351,8 @@ public:
uint64_t get_total_duration(bool include_last_frame_duration);
+ uint32_t get_number_of_samples() const;
+
protected:
Error parse(BitstreamRange& range, const heif_security_limits*) override;
@@ -389,6 +391,8 @@ public:
int32_t compute_min_offset() const;
+ uint32_t get_number_of_samples() const;
+
protected:
Error parse(BitstreamRange& range, const heif_security_limits*) override;
diff --git a/libheif/sequences/track.cc b/libheif/sequences/track.cc
index 20eb6e5d..42257c6b 100644
--- a/libheif/sequences/track.cc
+++ b/libheif/sequences/track.cc
@@ -55,7 +55,7 @@ TrackOptions& TrackOptions::operator=(const TrackOptions& src)
SampleAuxInfoHelper::SampleAuxInfoHelper(bool interleaved)
- : m_interleaved(interleaved)
+ : m_interleaved(interleaved)
{
m_saiz = std::make_shared<Box_saiz>();
m_saio = std::make_shared<Box_saio>();
@@ -71,9 +71,11 @@ void SampleAuxInfoHelper::set_aux_info_type(uint32_t aux_info_type, uint32_t aux
Error SampleAuxInfoHelper::add_sample_info(const std::vector<uint8_t>& data)
{
if (data.size() > 0xFF) {
- return {heif_error_Encoding_error,
- heif_suberror_Unspecified,
- "Encoded sample auxiliary information exceeds maximum size"};
+ return {
+ heif_error_Encoding_error,
+ heif_suberror_Unspecified,
+ "Encoded sample auxiliary information exceeds maximum size"
+ };
}
m_saiz->add_sample_size(static_cast<uint8_t>(data.size()));
@@ -123,7 +125,7 @@ SampleAuxInfoReader::SampleAuxInfoReader(std::shared_ptr<Box_saiz> saiz,
uint64_t offset = saio->get_sample_offset(0);
auto nSamples = saiz->get_num_samples();
- for (uint32_t i=0;i<nSamples;i++) {
+ for (uint32_t i = 0; i < nSamples; i++) {
m_contiguous_offsets.push_back(offset);
offset += saiz->get_sample_size(i);
}
@@ -142,7 +144,7 @@ heif_sample_aux_info_type SampleAuxInfoReader::get_type() const
}
-Result<std::vector<uint8_t>> SampleAuxInfoReader::get_sample_info(const HeifFile* file, uint32_t idx)
+Result<std::vector<uint8_t> > SampleAuxInfoReader::get_sample_info(const HeifFile* file, uint32_t idx)
{
uint64_t offset;
if (m_contiguous) {
@@ -292,6 +294,32 @@ Error Track::load(const std::shared_ptr<Box_trak>& trak_box)
};
}
+ // --- check that number of samples in various boxes are consistent
+
+ if (m_stts->get_number_of_samples() != m_stsz->num_samples()) {
+ return {
+ heif_error_Invalid_input,
+ heif_suberror_Unspecified,
+ "Number of samples in 'stts' and 'stsz' is inconsistent."
+ };
+ }
+
+ if (m_ctts && m_ctts->get_number_of_samples() != m_stsz->num_samples()) {
+ return {
+ heif_error_Invalid_input,
+ heif_suberror_Unspecified,
+ "Number of samples in 'ctts' and 'stsz' is inconsistent."
+ };
+ }
+
+ if (m_stsc->get_number_of_samples() != m_stsz->num_samples()) {
+ return {
+ heif_error_Invalid_input,
+ heif_suberror_Unspecified,
+ "Number of samples in 'stsc' and 'stsz' is inconsistent."
+ };
+ }
+
const std::vector<uint32_t>& chunk_offsets = m_stco->get_offsets();
assert(chunk_offsets.size() <= (size_t) std::numeric_limits<uint32_t>::max()); // There cannot be more than uint32_t chunks.
@@ -354,8 +382,8 @@ Error Track::load(const std::shared_ptr<Box_trak>& trak_box)
// --- read sample auxiliary information boxes
- std::vector<std::shared_ptr<Box_saiz>> saiz_boxes = stbl->get_child_boxes<Box_saiz>();
- std::vector<std::shared_ptr<Box_saio>> saio_boxes = stbl->get_child_boxes<Box_saio>();
+ std::vector<std::shared_ptr<Box_saiz> > saiz_boxes = stbl->get_child_boxes<Box_saiz>();
+ std::vector<std::shared_ptr<Box_saio> > saio_boxes = stbl->get_child_boxes<Box_saio>();
for (const auto& saiz : saiz_boxes) {
uint32_t aux_info_type = saiz->get_aux_info_type();
@@ -485,7 +513,7 @@ Track::Track(HeifContext* ctx, uint32_t track_id, const TrackOptions* options, u
auto dinf = std::make_shared<Box_dinf>();
auto dref = std::make_shared<Box_dref>();
- auto url = std::make_shared<Box_url>();
+ auto url = std::make_shared<Box_url>();
m_minf->append_child_box(dinf);
dinf->append_child_box(dref);
dref->append_child_box(url);
@@ -560,7 +588,7 @@ Track::Track(HeifContext* ctx, uint32_t track_id, const TrackOptions* options, u
}
-Result<std::shared_ptr<Track>> Track::alloc_track(HeifContext* ctx, const std::shared_ptr<Box_trak>& trak)
+Result<std::shared_ptr<Track> > Track::alloc_track(HeifContext* ctx, const std::shared_ptr<Box_trak>& trak)
{
auto mdia = trak->get_child_box<Box_mdia>();
if (!mdia) {
@@ -676,24 +704,30 @@ uint32_t Track::get_first_cluster_sample_entry_type() const
Result<std::string> Track::get_first_cluster_urim_uri() const
{
if (m_stsd->get_num_sample_entries() == 0) {
- return Error{heif_error_Invalid_input,
- heif_suberror_Unspecified,
- "This track has no sample entries."};
+ return Error{
+ heif_error_Invalid_input,
+ heif_suberror_Unspecified,
+ "This track has no sample entries."
+ };
}
std::shared_ptr<const Box> sampleEntry = m_stsd->get_sample_entry(0);
auto urim = std::dynamic_pointer_cast<const Box_URIMetaSampleEntry>(sampleEntry);
if (!urim) {
- return Error{heif_error_Usage_error,
- heif_suberror_Unspecified,
- "This cluster is no 'urim' sample entry."};
+ return Error{
+ heif_error_Usage_error,
+ heif_suberror_Unspecified,
+ "This cluster is no 'urim' sample entry."
+ };
}
std::shared_ptr<const Box_uri> uri = urim->get_child_box<const Box_uri>();
if (!uri) {
- return Error{heif_error_Invalid_input,
- heif_suberror_Unspecified,
- "The 'urim' box has no 'uri' child box."};
+ return Error{
+ heif_error_Invalid_input,
+ heif_suberror_Unspecified,
+ "The 'urim' box has no 'uri' child box."
+ };
}
return uri->get_uri();
@@ -716,7 +750,8 @@ Error Track::finalize_track()
// first sample in chunk? -> write chunk offset
- if (true) { // m_stsc->last_chunk_empty()) {
+ if (true) {
+ // m_stsc->last_chunk_empty()) {
// TODO: we will have to call this at the end of a chunk to dump the current SAI queue
// if auxiliary data is interleaved, write it between the chunks
@@ -823,16 +858,18 @@ Error Track::write_sample_data(const std::vector<uint8_t>& raw_data, uint32_t sa
m_stsc->increase_samples_in_chunk(1);
- m_stsz->append_sample_size((uint32_t)raw_data.size());
+ m_stsz->append_sample_size((uint32_t) raw_data.size());
if (is_sync_sample) {
m_stss->add_sync_sample(m_next_sample_to_be_output + 1);
}
if (sample_duration == 0) {
- return {heif_error_Usage_error,
- heif_suberror_Unspecified,
- "Sample duration may not be 0"};
+ return {
+ heif_error_Usage_error,
+ heif_suberror_Unspecified,
+ "Sample duration may not be 0"
+ };
}
m_stts->append_sample_duration(sample_duration);
@@ -853,9 +890,11 @@ Error Track::write_sample_data(const std::vector<uint8_t>& raw_data, uint32_t sa
m_aux_helper_tai_timestamps->add_nonpresent_sample();
}
else {
- return {heif_error_Encoding_error,
- heif_suberror_Unspecified,
- "Mandatory TAI timestamp missing"};
+ return {
+ heif_error_Encoding_error,
+ heif_suberror_Unspecified,
+ "Mandatory TAI timestamp missing"
+ };
}
}
@@ -871,12 +910,16 @@ Error Track::write_sample_data(const std::vector<uint8_t>& raw_data, uint32_t sa
if (err) {
return err;
}
- } else if (m_track_info.with_sample_content_ids == heif_sample_aux_info_presence_optional) {
+ }
+ else if (m_track_info.with_sample_content_ids == heif_sample_aux_info_presence_optional) {
m_aux_helper_content_ids->add_nonpresent_sample();
- } else {
- return {heif_error_Encoding_error,
- heif_suberror_Unspecified,
- "Mandatory ContentID missing"};
+ }
+ else {
+ return {
+ heif_error_Encoding_error,
+ heif_suberror_Unspecified,
+ "Mandatory ContentID missing"
+ };
}
}
@@ -908,7 +951,7 @@ void Track::init_sample_timing_table()
uint64_t current_decoding_time = 0;
uint32_t current_chunk = 0;
- for (uint32_t i = 0; i<m_num_samples; i++) {
+ for (uint32_t i = 0; i < m_num_samples; i++) {
SampleTiming timing;
timing.sampleIdx = i;
timing.media_decoding_time = current_decoding_time;
@@ -964,9 +1007,11 @@ Result<heif_raw_sequence_sample*> Track::get_next_sample_raw_data(const heif_dec
}
if (m_next_sample_to_be_output >= num_output_samples) {
- return Error{heif_error_End_of_sequence,
- heif_suberror_Unspecified,
- "End of sequence"};
+ return Error{
+ heif_error_End_of_sequence,
+ heif_suberror_Unspecified,
+ "End of sequence"
+ };
}
const auto& sampleTiming = m_presentation_timeline[m_next_sample_to_be_output % m_presentation_timeline.size()];