Skip to content

Commit 7d9dc2f

Browse files
authored
fix: Crash when metadata contains value with type that is not a list, object, or string (#91)
Uncovered by geoarrow/geoarrow-r#34 ! Basically, because we did a `break` instead of `return EINVAL`, we exposed uninitialized memory which (sometimes) caused a crash. I also added the ability to treat edges/crs as `null` as identical to missing (even though it's technically not valid).
1 parent bb2d093 commit 7d9dc2f

File tree

3 files changed

+87
-4
lines changed

3 files changed

+87
-4
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ out/
2121
CMakeUserPresets.json
2222
.vscode
2323
.Rproj.user
24+
.cache

src/geoarrow/metadata.c

+34-4
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,23 @@ static int SkipUntil(struct ArrowStringView* s, const char* items) {
109109
return 0;
110110
}
111111

112+
static GeoArrowErrorCode FindNull(struct ArrowStringView* s,
113+
struct ArrowStringView* out) {
114+
if (s->size_bytes < 4) {
115+
return EINVAL;
116+
}
117+
118+
if (strncmp(s->data, "null", 4) != 0) {
119+
return EINVAL;
120+
}
121+
122+
out->data = s->data;
123+
out->size_bytes = 4;
124+
s->size_bytes -= 4;
125+
s->data += 4;
126+
return GEOARROW_OK;
127+
}
128+
112129
static GeoArrowErrorCode FindString(struct ArrowStringView* s,
113130
struct ArrowStringView* out) {
114131
out->data = s->data;
@@ -245,25 +262,38 @@ static GeoArrowErrorCode ParseJSONMetadata(struct GeoArrowMetadataView* metadata
245262
case '\"':
246263
NANOARROW_RETURN_NOT_OK(FindString(s, &v));
247264
break;
248-
default:
265+
case 'n':
266+
NANOARROW_RETURN_NOT_OK(FindNull(s, &v));
249267
break;
268+
default:
269+
// e.g., a number or boolean
270+
return EINVAL;
250271
}
251272

252273
if (k.size_bytes == 7 && strncmp(k.data, "\"edges\"", 7) == 0) {
253274
if (v.size_bytes == 11 && strncmp(v.data, "\"spherical\"", 11) == 0) {
254275
metadata_view->edge_type = GEOARROW_EDGE_TYPE_SPHERICAL;
276+
} else if (v.size_bytes == 8 && strncmp(v.data, "\"planar\"", 8) == 0) {
277+
metadata_view->edge_type = GEOARROW_EDGE_TYPE_PLANAR;
278+
} else if (v.data[0] == 'n') {
279+
metadata_view->edge_type = GEOARROW_EDGE_TYPE_PLANAR;
280+
} else {
281+
return EINVAL;
255282
}
256283
} else if (k.size_bytes == 5 && strncmp(k.data, "\"crs\"", 5) == 0) {
257284
if (v.data[0] == '{') {
258285
metadata_view->crs_type = GEOARROW_CRS_TYPE_PROJJSON;
286+
metadata_view->crs.data = v.data;
287+
metadata_view->crs.size_bytes = v.size_bytes;
259288
} else if (v.data[0] == '\"') {
260289
metadata_view->crs_type = GEOARROW_CRS_TYPE_UNKNOWN;
290+
metadata_view->crs.data = v.data;
291+
metadata_view->crs.size_bytes = v.size_bytes;
292+
} else if (v.data[0] == 'n') {
293+
metadata_view->crs_type = GEOARROW_CRS_TYPE_NONE;
261294
} else {
262295
return EINVAL;
263296
}
264-
265-
metadata_view->crs.data = v.data;
266-
metadata_view->crs.size_bytes = v.size_bytes;
267297
}
268298

269299
SkipUntil(s, ",}");

src/geoarrow/metadata_test.cc

+52
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,16 @@ TEST(MetadataTest, MetadataTestReadJSONParsing) {
9898
metadata.data = "{}";
9999
metadata.size_bytes = 2;
100100
EXPECT_EQ(GeoArrowMetadataViewInit(&metadata_view, metadata, &error), GEOARROW_OK);
101+
102+
// Incomplete 'null'
103+
metadata.data = "{\"key\": n";
104+
metadata.size_bytes = strlen(metadata.data);
105+
EXPECT_EQ(GeoArrowMetadataViewInit(&metadata_view, metadata, &error), EINVAL);
106+
107+
// Enough characters but not actually 'null'
108+
metadata.data = "{\"key\": nincompoop}";
109+
metadata.size_bytes = strlen(metadata.data);
110+
EXPECT_EQ(GeoArrowMetadataViewInit(&metadata_view, metadata, &error), EINVAL);
101111
}
102112

103113
TEST(MetadataTest, MetadataTestReadJSON) {
@@ -123,6 +133,48 @@ TEST(MetadataTest, MetadataTestReadJSON) {
123133
EXPECT_EQ(metadata_view.crs_type, GEOARROW_CRS_TYPE_UNKNOWN);
124134
EXPECT_EQ(std::string(metadata_view.crs.data, metadata_view.crs.size_bytes),
125135
"\"a string\"");
136+
137+
const char* json_crs_none = "{\"crs\":null}";
138+
metadata.data = json_crs_none;
139+
metadata.size_bytes = strlen(json_crs_none);
140+
141+
EXPECT_EQ(GeoArrowMetadataViewInit(&metadata_view, metadata, &error), GEOARROW_OK);
142+
EXPECT_EQ(metadata_view.edge_type, GEOARROW_EDGE_TYPE_PLANAR);
143+
EXPECT_EQ(metadata_view.crs_type, GEOARROW_CRS_TYPE_NONE);
144+
EXPECT_EQ(metadata_view.crs.size_bytes, 0);
145+
146+
const char* json_crs_invalid = "{\"crs\":[]}";
147+
metadata.data = json_crs_invalid;
148+
metadata.size_bytes = strlen(json_crs_invalid);
149+
EXPECT_EQ(GeoArrowMetadataViewInit(&metadata_view, metadata, &error), EINVAL);
150+
151+
const char* json_edges_none = "{\"edges\":null}";
152+
metadata.data = json_edges_none;
153+
metadata.size_bytes = strlen(json_edges_none);
154+
155+
EXPECT_EQ(GeoArrowMetadataViewInit(&metadata_view, metadata, &error), GEOARROW_OK);
156+
EXPECT_EQ(metadata_view.edge_type, GEOARROW_EDGE_TYPE_PLANAR);
157+
EXPECT_EQ(metadata_view.crs_type, GEOARROW_CRS_TYPE_NONE);
158+
EXPECT_EQ(metadata_view.crs.size_bytes, 0);
159+
160+
const char* json_edges_planar = "{\"edges\":\"planar\"}";
161+
metadata.data = json_edges_planar;
162+
metadata.size_bytes = strlen(json_edges_planar);
163+
164+
EXPECT_EQ(GeoArrowMetadataViewInit(&metadata_view, metadata, &error), GEOARROW_OK);
165+
EXPECT_EQ(metadata_view.edge_type, GEOARROW_EDGE_TYPE_PLANAR);
166+
EXPECT_EQ(metadata_view.crs_type, GEOARROW_CRS_TYPE_NONE);
167+
EXPECT_EQ(metadata_view.crs.size_bytes, 0);
168+
169+
const char* json_edges_invalid = "{\"edges\":\"unsupported value\"}";
170+
metadata.data = json_edges_invalid;
171+
metadata.size_bytes = strlen(json_edges_invalid);
172+
EXPECT_EQ(GeoArrowMetadataViewInit(&metadata_view, metadata, &error), EINVAL);
173+
174+
const char* json_edges_invalid_type = "{\"edges\":true}";
175+
metadata.data = json_edges_invalid_type;
176+
metadata.size_bytes = strlen(json_edges_invalid_type);
177+
EXPECT_EQ(GeoArrowMetadataViewInit(&metadata_view, metadata, &error), EINVAL);
126178
}
127179

128180
TEST(MetadataTest, MetadataTestSetMetadata) {

0 commit comments

Comments
 (0)