in libheif/box.cc [3830:3952]
Error Box_iref::parse(BitstreamRange& range)
{
parse_full_box_header(range);
if (get_version() > 1) {
return unsupported_version_error("iref");
}
while (!range.eof()) {
Reference ref;
Error err = ref.header.parse_header(range);
if (err != Error::Ok) {
return err;
}
if (get_version() == 0) {
ref.from_item_ID = range.read16();
int nRefs = range.read16();
for (int i = 0; i < nRefs; i++) {
ref.to_item_ID.push_back(range.read16());
if (range.eof()) {
break;
}
}
}
else {
ref.from_item_ID = range.read32();
int nRefs = range.read16();
for (int i = 0; i < nRefs; i++) {
ref.to_item_ID.push_back(range.read32());
if (range.eof()) {
break;
}
}
}
m_references.push_back(ref);
}
// --- check number of total refs
size_t nTotalRefs = 0;
for (const auto& ref : m_references) {
nTotalRefs += ref.to_item_ID.size();
}
if (nTotalRefs > MAX_IREF_REFERENCES) {
return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded,
"Number of iref references exceeds security limit.");
}
// --- check for duplicate references
for (const auto& ref : m_references) {
std::set<heif_item_id> to_ids;
for (const auto to_id : ref.to_item_ID) {
if (to_ids.find(to_id) == to_ids.end()) {
to_ids.insert(to_id);
}
else {
return Error(heif_error_Invalid_input,
heif_suberror_Unspecified,
"'iref' has double references");
}
}
}
#if 0
// Note: This input sanity check does not work as expected.
// Its idea was to prevent infinite recursions while decoding when the input file
// contains cyclic references. However, apparently there are cases where cyclic
// references are actually allowed, like with images that have premultiplied alpha channels:
// | Box: iref -----
// | size: 40 (header size: 12)
// | reference with type 'auxl' from ID: 2 to IDs: 1
// | reference with type 'prem' from ID: 1 to IDs: 2
//
// TODO: implement the infinite recursion detection in a different way. E.g. by passing down
// the already processed item-ids while decoding an image and checking whether the current
// item has already been decoded before.
// --- check for cyclic references
for (const auto& ref : m_references) {
std::set<heif_item_id> reached_ids; // IDs that we have already reached in the DAG
std::set<heif_item_id> todo; // IDs that still need to be followed
todo.insert(ref.from_item_ID); // start at base item
while (!todo.empty()) {
// transfer ID into set of reached IDs
auto id = *todo.begin();
todo.erase(id);
reached_ids.insert(id);
// if this ID refers to another 'iref', follow it
for (const auto& succ_ref : m_references) {
if (succ_ref.from_item_ID == id) {
// Check whether any successor IDs has been visited yet, which would be an error.
// Otherwise, put that ID into the 'todo' set.
for (const auto& succ_ref_id : succ_ref.to_item_ID) {
if (reached_ids.find(succ_ref_id) != reached_ids.end()) {
return Error(heif_error_Invalid_input,
heif_suberror_Unspecified,
"'iref' has cyclic references");
}
todo.insert(succ_ref_id);
}
}
}
}
}
#endif
return range.get_error();
}