Commit 94f96a7d for libheif

commit 94f96a7d033d1fdcb2bfb1f2367a888829b73eae
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Tue Aug 25 20:30:06 2026 +0200

    hardening: check heif_region_item_add_region_polygon() parameters as far as we can

diff --git a/libheif/api/libheif/heif_regions.cc b/libheif/api/libheif/heif_regions.cc
index 6ae402c4..c77dd993 100644
--- a/libheif/api/libheif/heif_regions.cc
+++ b/libheif/api/libheif/heif_regions.cc
@@ -627,51 +627,67 @@ heif_error heif_region_item_add_region_ellipse(heif_region_item* item,
 }


-heif_error heif_region_item_add_region_polygon(heif_region_item* item,
-                                               const int32_t* pts, int nPoints,
-                                               heif_region** out_region)
+// Shared implementation of heif_region_item_add_region_polygon() and
+// heif_region_item_add_region_polyline(). `pts` holds 2*nPoints values in the
+// order X1, Y1, X2, Y2, ...
+static heif_error add_region_poly(heif_region_item* item,
+                                  const int32_t* pts, int nPoints,
+                                  bool closed,
+                                  heif_region** out_region)
 {
-  auto region = std::make_shared<RegionGeometry_Polygon>();
-  region->points.resize(nPoints);
+  if (out_region) {
+    *out_region = nullptr;
+  }

-  for (int i = 0; i < nPoints; i++) {
-    region->points[i].x = pts[2 * i + 0];
-    region->points[i].y = pts[2 * i + 1];
+  // We cannot verify that the caller's array really holds 2*nPoints values, that
+  // is part of the API contract (GHSA-prcj-g5xh-rw95). What we can check is that
+  // the arguments are self-consistent: a negative count would be converted to a
+  // huge size_t in resize() below and throw std::length_error through the C API,
+  // and a NULL array with a non-zero count would be dereferenced.
+  if (nPoints < 0) {
+    return {heif_error_Usage_error, heif_suberror_Invalid_parameter_value,
+            "Number of polygon points must not be negative"};
   }

-  region->closed = true;
+  if (nPoints > 0 && pts == nullptr) {
+    return heif_error_null_pointer_argument;
+  }

-  item->region_item->add_region(region);
+  return exception_guard([&]() -> heif_error {
+    auto region = std::make_shared<RegionGeometry_Polygon>();
+    region->points.resize(nPoints);

-  if (out_region) {
-    *out_region = create_region(region, item);
-  }
+    for (int i = 0; i < nPoints; i++) {
+      region->points[i].x = pts[2 * i + 0];
+      region->points[i].y = pts[2 * i + 1];
+    }

-  return heif_error_success;
-}
+    region->closed = closed;

+    item->region_item->add_region(region);

-heif_error heif_region_item_add_region_polyline(heif_region_item* item,
-                                                const int32_t* pts, int nPoints,
-                                                heif_region** out_region)
-{
-  auto region = std::make_shared<RegionGeometry_Polygon>();
-  region->points.resize(nPoints);
+    if (out_region) {
+      *out_region = create_region(region, item);
+    }

-  for (int i = 0; i < nPoints; i++) {
-    region->points[i].x = pts[2 * i + 0];
-    region->points[i].y = pts[2 * i + 1];
-  }
+    return heif_error_success;
+  });
+}

-  region->closed = false;

-  item->region_item->add_region(region);
+heif_error heif_region_item_add_region_polygon(heif_region_item* item,
+                                               const int32_t* pts, int nPoints,
+                                               heif_region** out_region)
+{
+  return add_region_poly(item, pts, nPoints, true, out_region);
+}

-  if (out_region) {
-    *out_region = create_region(region, item);
-  }

-  return heif_error_success;
+heif_error heif_region_item_add_region_polyline(heif_region_item* item,
+                                                const int32_t* pts, int nPoints,
+                                                heif_region** out_region)
+{
+  return add_region_poly(item, pts, nPoints, false, out_region);
 }


diff --git a/libheif/api/libheif/heif_regions.h b/libheif/api/libheif/heif_regions.h
index eaa9c170..29883463 100644
--- a/libheif/api/libheif/heif_regions.h
+++ b/libheif/api/libheif/heif_regions.h
@@ -722,6 +722,12 @@ heif_error heif_region_item_add_region_ellipse(heif_region_item* region_item,
  * The points are provided as pairs of X,Y coordinates, in the order X<sub>1</sub>,
  * Y<sub>1</sub>, X<sub>2</sub>, Y<sub>2</sub>, ..., X<sub>n</sub>, Y<sub>n</sub>.
  *
+ * @c pts_array must point to at least `2 * nPoints` values. The library cannot
+ * verify the size of the array, so passing a shorter array reads beyond its end.
+ * @c nPoints must not be negative and @c pts_array must not be `NULL` when
+ * @c nPoints is greater than zero. Otherwise the function fails without
+ * modifying the region item.
+ *
  * @param region_item the region item that holds this polygon region
  * @param pts_array the array of points in X,Y order (see above)
  * @param nPoints the number of points
@@ -745,6 +751,12 @@ heif_error heif_region_item_add_region_polygon(heif_region_item* region_item,
  * The points are provided as pairs of X,Y coordinates, in the order X<sub>1</sub>,
  * Y<sub>1</sub>, X<sub>2</sub>, Y<sub>2</sub>, ..., X<sub>n</sub>, Y<sub>n</sub>.
  *
+ * @c pts_array must point to at least `2 * nPoints` values. The library cannot
+ * verify the size of the array, so passing a shorter array reads beyond its end.
+ * @c nPoints must not be negative and @c pts_array must not be `NULL` when
+ * @c nPoints is greater than zero. Otherwise the function fails without
+ * modifying the region item.
+ *
  * @param region_item the region item that holds this polyline region
  * @param pts_array the array of points in X,Y order (see above)
  * @param nPoints the number of points
diff --git a/tests/region.cc b/tests/region.cc
index e32bd952..4c8777de 100644
--- a/tests/region.cc
+++ b/tests/region.cc
@@ -974,3 +974,102 @@ TEST_CASE("inline mask region data length must match region geometry") {
   heif_image_handle_release(readbackHandle);
   heif_context_free(readbackCtx);
 }
+
+
+TEST_CASE("polygon and polyline point arguments are validated") {
+  // Regression test for GHSA-prcj-g5xh-rw95.
+  //
+  // heif_region_item_add_region_polygon() and heif_region_item_add_region_polyline()
+  // read 2*nPoints values from the caller's array. The library has no way to check
+  // the real size of that array (that is the caller's responsibility, as documented
+  // in heif_regions.h), but it must reject the inconsistent argument combinations
+  // it can detect instead of crashing: a negative point count used to be converted
+  // to a huge size_t in std::vector::resize() and aborted the process with an
+  // uncaught std::length_error, and a NULL array was dereferenced.
+
+  auto context = get_context_for_test_file("rainbow-451x461.heic");
+  heif_image_handle* handle = get_primary_image_handle(context);
+
+  heif_region_item* region_item;
+  heif_error err = heif_image_handle_add_region_item(handle, 451, 461, &region_item);
+  REQUIRE(err.code == heif_error_Ok);
+  REQUIRE(heif_region_item_get_number_of_regions(region_item) == 0);
+
+  int32_t pts[6] = {10, 20, 30, 20, 30, 40};
+
+  // On failure, `out_region` must be reset to NULL so that callers never see a
+  // dangling or uninitialized pointer. Start from a non-NULL sentinel to verify this.
+  heif_region sentinel;
+  heif_region* region;
+
+  // --- negative point count ---
+
+  region = &sentinel;
+  err = heif_region_item_add_region_polygon(region_item, pts, -1, &region);
+  REQUIRE(err.code == heif_error_Usage_error);
+  REQUIRE(err.subcode == heif_suberror_Invalid_parameter_value);
+  REQUIRE(region == nullptr);
+
+  region = &sentinel;
+  err = heif_region_item_add_region_polyline(region_item, pts, -1, &region);
+  REQUIRE(err.code == heif_error_Usage_error);
+  REQUIRE(err.subcode == heif_suberror_Invalid_parameter_value);
+  REQUIRE(region == nullptr);
+
+  region = &sentinel;
+  err = heif_region_item_add_region_polygon(region_item, pts, INT32_MIN, &region);
+  REQUIRE(err.code == heif_error_Usage_error);
+  REQUIRE(err.subcode == heif_suberror_Invalid_parameter_value);
+  REQUIRE(region == nullptr);
+
+  // --- NULL point array with a non-zero count ---
+
+  region = &sentinel;
+  err = heif_region_item_add_region_polygon(region_item, nullptr, 3, &region);
+  REQUIRE(err.code == heif_error_Usage_error);
+  REQUIRE(err.subcode == heif_suberror_Null_pointer_argument);
+  REQUIRE(region == nullptr);
+
+  region = &sentinel;
+  err = heif_region_item_add_region_polyline(region_item, nullptr, 2, &region);
+  REQUIRE(err.code == heif_error_Usage_error);
+  REQUIRE(err.subcode == heif_suberror_Null_pointer_argument);
+  REQUIRE(region == nullptr);
+
+  // The out_region argument is optional, the checks must also work without it.
+  err = heif_region_item_add_region_polygon(region_item, nullptr, 3, nullptr);
+  REQUIRE(err.code == heif_error_Usage_error);
+  err = heif_region_item_add_region_polygon(region_item, pts, -1, nullptr);
+  REQUIRE(err.code == heif_error_Usage_error);
+
+  // None of the failed calls may have added a region.
+  REQUIRE(heif_region_item_get_number_of_regions(region_item) == 0);
+
+  // --- valid calls still work and the points round-trip ---
+
+  region = nullptr;
+  err = heif_region_item_add_region_polygon(region_item, pts, 3, &region);
+  REQUIRE(err.code == heif_error_Ok);
+  REQUIRE(region != nullptr);
+  REQUIRE(heif_region_get_type(region) == heif_region_type_polygon);
+  REQUIRE(heif_region_get_polygon_num_points(region) == 3);
+  int32_t out[6] = {0};
+  err = heif_region_get_polygon_points(region, out);
+  REQUIRE(err.code == heif_error_Ok);
+  REQUIRE(memcmp(out, pts, sizeof(pts)) == 0);
+  heif_region_release(region);
+
+  region = nullptr;
+  err = heif_region_item_add_region_polyline(region_item, pts, 2, &region);
+  REQUIRE(err.code == heif_error_Ok);
+  REQUIRE(region != nullptr);
+  REQUIRE(heif_region_get_type(region) == heif_region_type_polyline);
+  REQUIRE(heif_region_get_polyline_num_points(region) == 2);
+  heif_region_release(region);
+
+  REQUIRE(heif_region_item_get_number_of_regions(region_item) == 2);
+
+  heif_region_item_release(region_item);
+  heif_image_handle_release(handle);
+  heif_context_free(context);
+}