cellsToMultiPolygon: int64_t counts are narrowed to int struct fields without a bounds check
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 72/100
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
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from uber/h3
-
polygonToCells CLI silently drops vertices when whitespace precedes a coordinate's closing bracketOpen
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day
-
Difficulty 3/5 Half a day Newbie friendliness 45/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 50/100
Maintainers usually reply within 1 day
-
Difficulty 3/5 Half a day Newbie friendliness 45/100
Maintainers usually reply within 1 day
Similar issues
-
Status: Opened
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
FriendsofECCE/ECCE#1018 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
immortalwrt/immortalwrt#2522 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
severity: low
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
luainkernel/lunatik#2530 ·
Maintainers usually reply within 1 day