cellsToMultiPolygon: int64_t counts are narrowed to int struct fields without a bounds check

Open Beginner friendly
#1,257 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@madib06ops is already working on this.

Since Oct 14, 2026.

  • #1268 by @madib06ops — open

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
72/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
c
Domain
backend

Research direction

The three unchecked int64_t-to-int assignments are in src/h3lib/lib/cellsToMultiPoly.c, in createSortableLoop, createSortablePoly and createMultiPolygon. Compile that file with -Wshorten-64-to-32 to confirm the three warnings, then add the INT_MAX checks and explicit casts the issue proposes. Done means the warnings are gone, the existing test suite still passes, and the new branches are wrapped in NEVER(...) for coverage.

Written by the indexing model from the issue text.

Description

In src/h3lib/lib/cellsToMultiPoly.c (v4.5.0 and master at c340cd6), three internal int64_t counts are assigned to the public int count fields of GeoLoop, GeoPolygon and GeoMultiPolygon without a range check:

Line Function Assignment
375 createSortableLoop sloop->loop.numVerts = numVerts;
449 createSortablePoly spoly->poly.numHoles = numHoles;
565 createMultiPolygon mpoly->numPolygons = numPolys;
Why we are reporting it
  • Clang flags each assignment with -Wshorten-64-to-32 (implicit conversion loses integer precision: 'int64_t' (aka 'long long') to 'int'). We build H3 for iOS with -Werror -Wshorten-64-to-32, so this file does not compile there without a local patch.
  • If one of these counts ever exceeded INT_MAX, it would be truncated silently and the count in the returned struct would no longer match the allocation. As far as we can tell this is not reachable with inputs that fit in memory (it needs more than 2^31 vertices in one loop, holes in one polygon, or polygons), so this is hardening rather than a practical bug. It is in the same family as #1204.
Reproduce

Apple clang 21.0.0, iOS 27.0 simulator SDK, the v4.5.0 sources with cellsToMultiPoly.c taken from master (c340cd6):

clang -fsyntax-only -Wshorten-64-to-32 -I include *.c

(with the -target / -isysroot flags for the iOS simulator) reports the three lines above and nothing else in the library.

Possible fix

Check each count against INT_MAX before the assignment, return E_MEMORY_BOUNDS, and make the conversion explicit. Behaviour is unchanged for every input that works today. This is the patch we carry downstream; it applies cleanly to master:

--- a/src/h3lib/lib/cellsToMultiPoly.c
+++ b/src/h3lib/lib/cellsToMultiPoly.c
@@ -1,5 +1,6 @@
 #include "cellsToMultiPoly.h"
 
+#include <limits.h>
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdlib.h>
@@ -362,6 +363,12 @@ static H3Error createSortableLoop(Arc *arc, SortableLoop *sloop) {
         arc = arc->next;
     } while (arc->id != start);
 
+    // GeoLoop exposes an int count; reject a loop that cannot be represented.
+    if (numVerts > INT_MAX) {
+        H3_MEMORY(free)(verts);
+        return E_MEMORY_BOUNDS;
+    }
+
     // This memory ends up in GeoMultiPolygon, to be freed by caller of
     // cellsToMultiPolygon()
     LatLng *reallocVerts = H3_MEMORY(realloc)(verts, sizeof(LatLng) * numVerts);
@@ -372,7 +379,7 @@ static H3Error createSortableLoop(Arc *arc, SortableLoop *sloop) {
     verts = reallocVerts;
 
     sloop->root = getRoot(arc)->id;
-    sloop->loop.numVerts = numVerts;
+    sloop->loop.numVerts = (int)numVerts;
     sloop->loop.verts = verts;
     geoLoopAreaRads2(sloop->loop, &sloop->area);
 
@@ -434,6 +441,9 @@ static int64_t countPolys(SortableLoopSet loopset) {
 // Later, we sort the Polygons by the size of their outer loops.
 static H3Error createSortablePoly(SortableLoop *sloop, int64_t numHoles,
                                   SortablePoly *spoly) {
+    // GeoPolygon exposes an int count; check before allocating or copying holes.
+    if (numHoles > INT_MAX) return E_MEMORY_BOUNDS;
+
     GeoLoop *holes = NULL;
     if (numHoles > 0) {
         holes = H3_MEMORY(malloc)(sizeof(GeoLoop) * numHoles);
@@ -446,7 +456,7 @@ static H3Error createSortablePoly(SortableLoop *sloop, int64_t numHoles,
     }
 
     spoly->poly.geoloop = sloop[0].loop;
-    spoly->poly.numHoles = numHoles;
+    spoly->poly.numHoles = (int)numHoles;
     spoly->poly.holes = holes;
     spoly->outerArea = sloop[0].area;  // area of outer loop
 
@@ -527,6 +537,9 @@ static H3Error createMultiPolygon(SortableLoopSet loopset,
     }
 
     int64_t numPolys = countPolys(loopset);
+    // GeoMultiPolygon exposes an int count; check before transferring ownership.
+    if (numPolys > INT_MAX) return E_MEMORY_BOUNDS;
+
     SortablePoly *spolys = H3_MEMORY(malloc)(sizeof(SortablePoly) * numPolys);
     if (!spolys) {
         return E_MEMORY_ALLOC;
@@ -562,7 +575,7 @@ static H3Error createMultiPolygon(SortableLoopSet loopset,
         return E_MEMORY_ALLOC;
     }
 
-    mpoly->numPolygons = numPolys;
+    mpoly->numPolygons = (int)numPolys;
     for (int64_t i = 0; i < numPolys; i++) {
         mpoly->polygons[i] = spolys[i].poly;
     }

The new branches cannot be reached by a test, so you may prefer to wrap the conditions in NEVER(...) to keep coverage at 100%. Please feel free to take the patch as it is or in any other form.

Prepared with Claude Code on behalf of the owner of this account. The diagnostics were reproduced locally before filing.

Dominant language
C
Stars
6.7k
Forks
658
Avg merge
5d 21h
Merged PRs (30d)
12

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from uber/h3

All issues in uber/h3

Similar issues

More C issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.