Skip to content

Commit e8b1661

Browse files
isaacbrodskyNick Rabinowitz
and
Nick Rabinowitz
authored
Polygon to cells experimental fuzzer (#800)
This adds new fuzzers for the `polygonToCellsExperimental` function, based on the existing functions. Since #796 (this PR is based on that branch) adds containment modes, this updates the existing fuzzers to exercise those options too. The estimation functions are adjusted so that the fuzzers pass. This may reduce performance of the new algorithms somewhat, but avoids potential buffer problems. This can be revisited to get them back to how they were intended to work. --------- Co-authored-by: Nick Rabinowitz <[email protected]>
1 parent 7ce561c commit e8b1661

11 files changed

+250
-15
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
/lib/
1111
# Travis CI build directory
1212
/build/
13+
/build*/
1314
# Local build directories
1415
/Debug/
1516
/Release/

CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,9 @@ set(OTHER_SOURCE_FILES
285285
src/apps/fuzzers/fuzzerDirectedEdge.c
286286
src/apps/fuzzers/fuzzerLocalIj.c
287287
src/apps/fuzzers/fuzzerPolygonToCells.c
288+
src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c
288289
src/apps/fuzzers/fuzzerPolygonToCellsNoHoles.c
290+
src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c
289291
src/apps/fuzzers/fuzzerCellToChildPos.c
290292
src/apps/fuzzers/fuzzerInternalAlgos.c
291293
src/apps/fuzzers/fuzzerInternalCoordIjk.c
@@ -560,7 +562,9 @@ if(BUILD_FUZZERS)
560562
add_h3_fuzzer(fuzzerDirectedEdge src/apps/fuzzers/fuzzerDirectedEdge.c)
561563
add_h3_fuzzer(fuzzerLocalIj src/apps/fuzzers/fuzzerLocalIj.c)
562564
add_h3_fuzzer(fuzzerPolygonToCells src/apps/fuzzers/fuzzerPolygonToCells.c)
565+
add_h3_fuzzer(fuzzerPolygonToCellsExperimental src/apps/fuzzers/fuzzerPolygonToCellsExperimental.c)
563566
add_h3_fuzzer(fuzzerPolygonToCellsNoHoles src/apps/fuzzers/fuzzerPolygonToCellsNoHoles.c)
567+
add_h3_fuzzer(fuzzerPolygonToCellsExperimentalNoHoles src/apps/fuzzers/fuzzerPolygonToCellsExperimentalNoHoles.c)
564568
add_h3_fuzzer(fuzzerCellToChildPos src/apps/fuzzers/fuzzerCellToChildPos.c)
565569
if(ENABLE_REQUIRES_ALL_SYMBOLS)
566570
add_h3_fuzzer(fuzzerInternalAlgos src/apps/fuzzers/fuzzerInternalAlgos.c)

scripts/make_countries.js

+1
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ for (int res = 0; res < MAX_RES + 1; res++) {
170170
H3_EXPORT(maxPolygonToCellsSizeExperimental)(&COUNTRIES[index], res, CONTAINMENT_CENTER, &numHexagons);
171171
hexagons = calloc(numHexagons, sizeof(H3Index));
172172
H3_EXPORT(polygonToCellsExperimental)(&COUNTRIES[index], res, CONTAINMENT_FULL, hexagons);
173+
free(hexagons);
173174
}
174175
});
175176

src/apps/fuzzers/README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ such as the H3 core library.
1111

1212
The public API of H3 is covered in the following fuzzers:
1313

14-
| Function | File or status
15-
| -------- | --------------
14+
| Function | File
15+
| -------- | ----
1616
| areNeighborCells | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
1717
| cellArea | [fuzzerCellArea](./fuzzerCellArea.c)
1818
| cellToBoundary | [fuzzerCellToLatLng](./fuzzerCellToLatLng.c)
@@ -60,6 +60,7 @@ The public API of H3 is covered in the following fuzzers:
6060
| localIjToCell | [fuzzerLocalIj](./fuzzerLocalIj.c)
6161
| originToDirectedEdges | [fuzzerDirectedEdge](./fuzzerDirectedEdge.c)
6262
| polygonToCells | [fuzzerPoylgonToCells](./fuzzerPolygonToCells.c)
63+
| polygonToCellsExperimental | [fuzzerPoylgonToCellsExperimental](./fuzzerPolygonToCellsExperimental.c) [fuzzerPoylgonToCellsExperimentalNoHoles](./fuzzerPolygonToCellsExperimentalNoHoles.c)
6364
| radsToDegs | Trivial
6465
| stringToH3 | [fuzzerIndexIO](./fuzzerIndexIO.c)
6566
| uncompactCells | [fuzzerCompact](./fuzzerCompact.c)

src/apps/fuzzers/fuzzerPolygonToCells.c

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022 Uber Technologies, Inc.
2+
* Copyright 2022-2024 Uber Technologies, Inc.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919

2020
#include "aflHarness.h"
2121
#include "h3api.h"
22+
#include "polygon.h"
2223
#include "utility.h"
2324

2425
typedef struct {
@@ -71,7 +72,8 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
7172
int res = args->res % (MAX_RES + 1);
7273

7374
GeoPolygon geoPolygon;
74-
geoPolygon.numHoles = args->numHoles % MAX_HOLES;
75+
int originalNumHoles = args->numHoles % MAX_HOLES;
76+
geoPolygon.numHoles = originalNumHoles;
7577
if (geoPolygon.numHoles < 0) {
7678
return 0;
7779
}
@@ -88,10 +90,12 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
8890
}
8991
}
9092

91-
// TODO: Fuzz the `flags` input as well when it has meaningful input
92-
run(&geoPolygon, 0, res);
93-
geoPolygon.numHoles = 0;
94-
run(&geoPolygon, 0, res);
93+
for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) {
94+
geoPolygon.numHoles = originalNumHoles;
95+
run(&geoPolygon, 0, res);
96+
geoPolygon.numHoles = 0;
97+
run(&geoPolygon, 0, res);
98+
}
9599
free(geoPolygon.holes);
96100

97101
return 0;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2023-2024 Uber Technologies, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
/** @file
17+
* @brief Fuzzer program for polygonToCells2 and related functions
18+
*/
19+
20+
#include "aflHarness.h"
21+
#include "h3api.h"
22+
#include "polyfill.h"
23+
#include "polygon.h"
24+
#include "utility.h"
25+
26+
typedef struct {
27+
int res;
28+
int numHoles;
29+
// repeating: num verts, verts
30+
// We add a large fixed buffer so our test case generator for AFL
31+
// knows how large to make the file.
32+
uint8_t buffer[1024];
33+
} inputArgs;
34+
35+
const int MAX_RES = 15;
36+
const int MAX_SZ = 4000000;
37+
const int MAX_HOLES = 100;
38+
39+
int populateGeoLoop(GeoLoop *g, const uint8_t *data, size_t *offset,
40+
size_t size) {
41+
if (size < *offset + sizeof(int)) {
42+
return 1;
43+
}
44+
int numVerts = *(const int *)(data + *offset);
45+
*offset = *offset + sizeof(int);
46+
g->numVerts = numVerts;
47+
if (size < *offset + sizeof(LatLng) * numVerts) {
48+
return 1;
49+
}
50+
g->verts = (LatLng *)(data + *offset);
51+
*offset = *offset + sizeof(LatLng) * numVerts;
52+
return 0;
53+
}
54+
55+
void run(GeoPolygon *geoPolygon, uint32_t flags, int res) {
56+
int64_t sz;
57+
H3Error err = H3_EXPORT(maxPolygonToCellsSizeExperimental)(geoPolygon, res,
58+
flags, &sz);
59+
if (!err && sz < MAX_SZ) {
60+
H3Index *out = calloc(sz, sizeof(H3Index));
61+
H3_EXPORT(polygonToCellsExperimental)(geoPolygon, res, flags, out);
62+
free(out);
63+
}
64+
}
65+
66+
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
67+
// TODO: It is difficult for the fuzzer to generate inputs that are
68+
// considered valid by this fuzzer. fuzzerPolygonToCellsNoHoles.c
69+
// is a workaround for that.
70+
if (size < sizeof(inputArgs)) {
71+
return 0;
72+
}
73+
const inputArgs *args = (const inputArgs *)data;
74+
int res = args->res % (MAX_RES + 1);
75+
76+
GeoPolygon geoPolygon;
77+
int originalNumHoles = args->numHoles % MAX_HOLES;
78+
geoPolygon.numHoles = originalNumHoles;
79+
if (geoPolygon.numHoles < 0) {
80+
return 0;
81+
}
82+
geoPolygon.holes = calloc(geoPolygon.numHoles, sizeof(GeoLoop));
83+
size_t offset = sizeof(inputArgs) - sizeof(args->buffer);
84+
if (populateGeoLoop(&geoPolygon.geoloop, data, &offset, size)) {
85+
free(geoPolygon.holes);
86+
return 0;
87+
}
88+
for (int i = 0; i < geoPolygon.numHoles; i++) {
89+
if (populateGeoLoop(&geoPolygon.holes[i], data, &offset, size)) {
90+
free(geoPolygon.holes);
91+
return 0;
92+
}
93+
}
94+
95+
for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) {
96+
geoPolygon.numHoles = originalNumHoles;
97+
run(&geoPolygon, flags, res);
98+
geoPolygon.numHoles = 0;
99+
run(&geoPolygon, flags, res);
100+
}
101+
free(geoPolygon.holes);
102+
103+
return 0;
104+
}
105+
106+
AFL_HARNESS_MAIN(sizeof(inputArgs));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2023-2024 Uber Technologies, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
/** @file
17+
* @brief Fuzzer program for polygonToCellsExperimental and related functions,
18+
* without holes
19+
*/
20+
21+
#include "aflHarness.h"
22+
#include "h3api.h"
23+
#include "polyfill.h"
24+
#include "polygon.h"
25+
#include "utility.h"
26+
27+
const int MAX_RES = 15;
28+
const int MAX_SZ = 4000000;
29+
30+
void run(GeoPolygon *geoPolygon, uint32_t flags, int res) {
31+
int64_t sz;
32+
H3Error err = H3_EXPORT(maxPolygonToCellsSizeExperimental)(geoPolygon, res,
33+
flags, &sz);
34+
if (!err && sz < MAX_SZ) {
35+
H3Index *out = calloc(sz, sizeof(H3Index));
36+
H3_EXPORT(polygonToCellsExperimental)(geoPolygon, res, flags, out);
37+
free(out);
38+
}
39+
}
40+
41+
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
42+
if (size < sizeof(int)) {
43+
return 0;
44+
}
45+
46+
uint8_t res = *data;
47+
size_t vertsSize = size - 1;
48+
int numVerts = vertsSize / sizeof(LatLng);
49+
50+
GeoPolygon geoPolygon;
51+
geoPolygon.numHoles = 0;
52+
geoPolygon.holes = NULL;
53+
geoPolygon.geoloop.numVerts = numVerts;
54+
// Offset by 1 since *data was used for `res`, above.
55+
geoPolygon.geoloop.verts = (LatLng *)(data + 1);
56+
57+
for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) {
58+
run(&geoPolygon, flags, res);
59+
}
60+
61+
return 0;
62+
}
63+
64+
AFL_HARNESS_MAIN(sizeof(H3Index) * 1024);

src/apps/fuzzers/fuzzerPolygonToCellsNoHoles.c

+4-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "aflHarness.h"
2121
#include "h3api.h"
22+
#include "polygon.h"
2223
#include "utility.h"
2324

2425
const int MAX_RES = 15;
@@ -50,8 +51,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
5051
// Offset by 1 since *data was used for `res`, above.
5152
geoPolygon.geoloop.verts = (LatLng *)(data + 1);
5253

53-
// TODO: Fuzz the `flags` input as well when it has meaningful input
54-
run(&geoPolygon, 0, res);
54+
for (uint32_t flags = 0; flags < CONTAINMENT_INVALID; flags++) {
55+
run(&geoPolygon, flags, res);
56+
}
5557

5658
return 0;
5759
}

src/apps/testapps/testPolygonToCellsReportedExperimental.c

+30
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,36 @@
2828
// Tests for specific polygonToCells examples
2929

3030
SUITE(polygonToCells_reported) {
31+
// fuzzer crash due to inconsistent handling of CONTAINMENT_OVERLAPPING
32+
TEST(fuzzer_crash) {
33+
uint8_t data[] = {
34+
0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
35+
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0xff,
36+
0xff, 0x0, 0x0, 0x0, 0xa, 0xa, 0xa, 0xa, 0xa, 0xff,
37+
};
38+
39+
uint8_t res = 0;
40+
size_t vertsSize = sizeof(data);
41+
int numVerts = vertsSize / sizeof(LatLng);
42+
43+
GeoPolygon geoPolygon;
44+
geoPolygon.numHoles = 0;
45+
geoPolygon.holes = NULL;
46+
geoPolygon.geoloop.numVerts = numVerts;
47+
// Offset by 1 since *data was used for `res`, above.
48+
geoPolygon.geoloop.verts = (LatLng *)(data);
49+
50+
uint32_t flags = CONTAINMENT_OVERLAPPING;
51+
int64_t sz;
52+
t_assertSuccess(H3_EXPORT(maxPolygonToCellsSizeExperimental)(
53+
&geoPolygon, res, flags, &sz));
54+
t_assert(sz == 1, "Expected output count");
55+
H3Index *out = calloc(sz, sizeof(H3Index));
56+
t_assertSuccess(H3_EXPORT(polygonToCellsExperimental)(&geoPolygon, res,
57+
flags, out));
58+
free(out);
59+
}
60+
3161
// https://github.com/uber/h3-js/issues/76#issuecomment-561204505
3262
TEST(entireWorld) {
3363
// TODO: Fails for a single worldwide polygon

src/h3lib/lib/polyfill.c

+26-4
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,8 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) {
433433

434434
// Target res: Do a fine-grained check
435435
if (cellRes == iter->_res) {
436-
if (mode == CONTAINMENT_CENTER || mode == CONTAINMENT_OVERLAPPING) {
436+
if (mode == CONTAINMENT_CENTER || mode == CONTAINMENT_OVERLAPPING ||
437+
mode == CONTAINMENT_OVERLAPPING_BBOX) {
437438
// Check if the cell center is inside the polygon
438439
LatLng center;
439440
H3Error centerErr = H3_EXPORT(cellToLatLng)(cell, &center);
@@ -448,7 +449,8 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) {
448449
return;
449450
}
450451
}
451-
if (mode == CONTAINMENT_OVERLAPPING) {
452+
if (mode == CONTAINMENT_OVERLAPPING ||
453+
mode == CONTAINMENT_OVERLAPPING_BBOX) {
452454
// For overlapping, we need to do a quick check to determine
453455
// whether the polygon is wholly contained by the cell. We
454456
// check the first polygon vertex, which if it is contained
@@ -477,7 +479,8 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) {
477479
}
478480
}
479481
}
480-
if (mode == CONTAINMENT_FULL || mode == CONTAINMENT_OVERLAPPING) {
482+
if (mode == CONTAINMENT_FULL || mode == CONTAINMENT_OVERLAPPING ||
483+
mode == CONTAINMENT_OVERLAPPING_BBOX) {
481484
CellBoundary boundary;
482485
H3Error boundaryErr =
483486
H3_EXPORT(cellToBoundary)(cell, &boundary);
@@ -494,7 +497,8 @@ void iterStepPolygonCompact(IterCellsPolygonCompact *iter) {
494497
return;
495498
}
496499
// Check if the cell is fully contained by the polygon
497-
if (mode == CONTAINMENT_FULL &&
500+
if ((mode == CONTAINMENT_FULL ||
501+
mode == CONTAINMENT_OVERLAPPING_BBOX) &&
498502
cellBoundaryInsidePolygon(iter->_polygon, iter->_bboxes,
499503
&boundary, &bbox)) {
500504
// Set to next output
@@ -692,9 +696,27 @@ void iterDestroyPolygon(IterCellsPolygon *iter) {
692696
H3Error H3_EXPORT(polygonToCellsExperimental)(const GeoPolygon *polygon,
693697
int res, uint32_t flags,
694698
H3Index *out) {
699+
#ifdef H3_POLYGON_TO_CELLS_ASSERT
700+
// TODO: This is incompatible with testH3Memory, since it will make more
701+
// allocations. This is just for debugging that the algorithm is not
702+
// exceeding its buffer size.
703+
int64_t maxSize;
704+
H3Error sizeError = H3_EXPORT(maxPolygonToCellsSizeExperimental)(
705+
polygon, res, flags, &maxSize);
706+
if (sizeError) {
707+
return sizeError;
708+
}
709+
#endif
710+
695711
IterCellsPolygon iter = iterInitPolygon(polygon, res, flags);
696712
int64_t i = 0;
697713
for (; iter.cell; iterStepPolygon(&iter)) {
714+
#ifdef H3_POLYGON_TO_CELLS_ASSERT
715+
if (NEVER(i >= maxSize)) {
716+
iterDestroyPolygon(&iter);
717+
return E_FAILED;
718+
}
719+
#endif
698720
out[i++] = iter.cell;
699721
}
700722
return iter.error;

0 commit comments

Comments
 (0)