Commit 7dde8930 for libheif
commit 7dde89307b55616a71dda198f07c2629dfd51130
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Sun Oct 4 01:44:05 2026 +0200
Check the MIAF derivation chain of auxiliary images of coded images (#1929)
check_miaf_derivation_constraints() returned from its coded-image branch before
reaching the auxiliary-image check, so the alpha of a plain coded primary image
was never validated against the MIAF derivation constraints (ISO/IEC 23000-22,
clause 7.3.11), while the same alpha attached to a grid or overlay primary was
rejected. A 'miaf' file with a coded primary and, e.g., a nested-grid alpha
therefore passed validation.
Coded images now skip only the 'dimg' walk and fall through to the auxiliary
check, so the alpha chain is validated for every item of the walk. The check
stays gated as before (the 'miaf' brand; on v1.24.x additionally the
always_apply_MIAF_derivation_constraints security limit) and does not depend on
the strict_decoding option, since the auxiliary is decoded as part of the
item's own decode.
Adds regression tests: a nested-grid alpha on a coded primary is rejected with
the 'miaf' brand and accepted without it; the same alpha on a grid primary
remains rejected.
Reported in https://github.com/strukturag/libheif/issues/1929
diff --git a/libheif/image-items/image_item.cc b/libheif/image-items/image_item.cc
index 65d83a10..3d544b1a 100644
--- a/libheif/image-items/image_item.cc
+++ b/libheif/image-items/image_item.cc
@@ -1039,41 +1039,47 @@ Error check_miaf_derivation_constraints(const ImageItem* item,
return Error::Ok; // already verified in this context
}
- // Rank budget passed to this item's own 'dimg' inputs.
- int child_max_rank;
- bool child_parent_is_iden;
+ // Rank budget passed to this item's own 'dimg' inputs. A coded image is the
+ // leaf of the derivation chain and has no 'dimg' inputs to walk, but it may
+ // still carry an auxiliary image, which is checked below.
+ bool has_derivation_inputs = true;
+ int child_max_rank = MIAF_RANK_CODED;
+ bool child_parent_is_iden = false;
if (is_iden) {
child_max_rank = max_rank; // transparent: inputs keep this position
child_parent_is_iden = true;
}
else if (rank == MIAF_RANK_OVERLAY) {
child_max_rank = MIAF_RANK_GRID; // overlay inputs: grid or below
- child_parent_is_iden = false;
}
else if (rank == MIAF_RANK_GRID) {
child_max_rank = MIAF_RANK_CODED; // grid inputs: coded (or iden -> coded)
- child_parent_is_iden = false;
}
else {
- return Error::Ok; // coded image: leaf of the derivation chain
- }
-
- auto file = item->get_file();
- auto iref = file ? file->get_iref_box() : nullptr;
- if (iref) {
- for (heif_item_id child_id : iref->get_references(id, fourcc("dimg"))) {
- auto child = item->get_context()->get_image(child_id, true);
- if (child) {
- if (Error err = check_miaf_derivation_constraints(child.get(), child_max_rank,
- child_parent_is_iden, verified)) {
- return err;
+ has_derivation_inputs = false; // coded image: leaf of the derivation chain
+ }
+
+ if (has_derivation_inputs) {
+ auto file = item->get_file();
+ auto iref = file ? file->get_iref_box() : nullptr;
+ if (iref) {
+ for (heif_item_id child_id : iref->get_references(id, fourcc("dimg"))) {
+ auto child = item->get_context()->get_image(child_id, true);
+ if (child) {
+ if (Error err = check_miaf_derivation_constraints(child.get(), child_max_rank,
+ child_parent_is_iden, verified)) {
+ return err;
+ }
}
}
}
}
// An auxiliary (e.g. alpha) image is a separate image whose own derivation
- // chain must independently satisfy MIAF, so check it as a fresh chain.
+ // chain must independently satisfy MIAF, so check it as a fresh chain. This
+ // applies to every item of the walk, including coded images (a plain coded
+ // primary image with an alpha auxiliary is the common case), since the
+ // auxiliary is decoded as part of this item's decode.
if (auto alpha = item->get_alpha_channel()) {
if (Error err = check_miaf_derivation_constraints(alpha.get(), MIAF_RANK_OVERLAY,
/*parent_is_iden=*/false, verified)) {
diff --git a/tests/parallel_grid_deadlock.cc b/tests/parallel_grid_deadlock.cc
index ec51b515..88062584 100644
--- a/tests/parallel_grid_deadlock.cc
+++ b/tests/parallel_grid_deadlock.cc
@@ -466,3 +466,67 @@ TEST_CASE("MIAF: without the 'miaf' brand a nested grid is not structurally reje
REQUIRE(completed);
REQUIRE(err.code == heif_error_Ok);
}
+
+
+// An auxiliary (alpha) image is a separate image whose own derivation chain
+// must satisfy MIAF independently of the master image. Here the alpha is a
+// nested grid (invalid under 7.3.11), attached with 'auxl' to a plain coded
+// primary image. The primary's own chain is trivially valid, so the validator
+// has to continue from the coded leaf into the auxiliary chain to notice.
+// (Previously the coded-image branch returned before the auxiliary check, so
+// this was only caught when the primary was itself a grid or overlay; GitHub
+// issue #1929.)
+static std::vector<Item> coded_primary_with_nested_grid_alpha_items() {
+ const std::vector<uint8_t> pixels(64 * 64, 0x7F);
+ std::vector<Item> items;
+ // id type w h alpha dimg auxl data
+ items.push_back({1, "mski", 64, 64, false, {}, {}, pixels}); // coded primary
+ items.push_back({2, "grid", 64, 64, true, {3}, {1}, image_grid(1, 1, 64, 64)}); // its alpha: grid over ...
+ items.push_back({3, "grid", 64, 64, false, {4}, {}, image_grid(1, 1, 64, 64)}); // ... a grid (invalid)
+ items.push_back({4, "mski", 64, 64, false, {}, {}, pixels}); // coded base of the alpha
+ return items;
+}
+
+TEST_CASE("MIAF: an invalid auxiliary chain on a coded primary is rejected with the 'miaf' brand") {
+ auto data = build_file(coded_primary_with_nested_grid_alpha_items(), /*primary=*/1,
+ /*with_miaf_brand=*/true);
+
+ heif_error err{};
+ bool completed = decode_item_with_timeout(data, /*item=*/1, std::chrono::seconds(20), err);
+
+ REQUIRE(completed);
+ REQUIRE(err.code == heif_error_Invalid_input);
+}
+
+// Without the brand, the auxiliary chain is (like the primary's own chain) not
+// subject to the MIAF constraints and the nested-grid alpha decodes.
+TEST_CASE("MIAF: without the 'miaf' brand an invalid auxiliary chain on a coded primary is accepted") {
+ auto data = build_file(coded_primary_with_nested_grid_alpha_items(), /*primary=*/1,
+ /*with_miaf_brand=*/false);
+
+ heif_error err{};
+ bool completed = decode_item_with_timeout(data, /*item=*/1, std::chrono::seconds(20), err);
+
+ REQUIRE(completed);
+ REQUIRE(err.code == heif_error_Ok);
+}
+
+// The same invalid auxiliary chain attached to a grid primary was already
+// rejected before; this pins the behavior as the counterpart of the coded case.
+TEST_CASE("MIAF: an invalid auxiliary chain on a grid primary is rejected with the 'miaf' brand") {
+ const std::vector<uint8_t> pixels(64 * 64, 0x7F);
+ std::vector<Item> items;
+ // id type w h alpha dimg auxl data
+ items.push_back({1, "grid", 64, 64, false, {5}, {}, image_grid(1, 1, 64, 64)}); // grid primary (valid)
+ items.push_back({2, "grid", 64, 64, true, {3}, {1}, image_grid(1, 1, 64, 64)}); // its alpha: grid over ...
+ items.push_back({3, "grid", 64, 64, false, {4}, {}, image_grid(1, 1, 64, 64)}); // ... a grid (invalid)
+ items.push_back({4, "mski", 64, 64, false, {}, {}, pixels}); // coded base of the alpha
+ items.push_back({5, "mski", 64, 64, false, {}, {}, pixels}); // coded tile of the primary
+ auto data = build_file(items, /*primary=*/1, /*with_miaf_brand=*/true);
+
+ heif_error err{};
+ bool completed = decode_item_with_timeout(data, /*item=*/1, std::chrono::seconds(20), err);
+
+ REQUIRE(completed);
+ REQUIRE(err.code == heif_error_Invalid_input);
+}