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, ®ion_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, ®ion);
+ 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, ®ion);
+ 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, ®ion);
+ 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, ®ion);
+ 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, ®ion);
+ 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, ®ion);
+ 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, ®ion);
+ 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);
+}