duckdb / duckdb/duckdb-spatial
ST_Read_Meta segfaults on any unreadable file in 1.5.5 (regression; ST_Read raises normally)
- Dominant language
- C
- Stars
- 708
- Forks
- 96
- Avg merge
- 1d 21h
- Merged PRs (30d)
- 5
Description
What happens
In DuckDB 1.5.5, ST_Read_Meta crashes with SIGSEGV when GDAL cannot open the input file. The process exits 139, and no exception reaches the caller. try/except blocks and other in-process error handling cannot recover from the failure.
This is specific to ST_Read_Meta. ST_Read raises duckdb.IOException for the same file. DuckDB 1.5.1 through 1.5.4 also do not crash, so the regression starts in 1.5.5.
Reproduction
import duckdb
with open("bad.geojson", "w") as f:
f.write('{"type": "FeatureCollection", "features": [INVALID')
con = duckdb.connect()
con.execute("INSTALL spatial; LOAD spatial;")
con.execute("SELECT * FROM ST_Read_Meta('bad.geojson')").fetchone()
$ python repro.py
$ echo $?
139
The script produces no output before the interpreter exits.
Versions tested
Each test used the spatial extension shipped with that DuckDB release.
duckdb | spatial extension | Result
-- | -- | --
1.5.1 | | returns, no error
1.5.2 | | returns, no error
1.5.3 | | returns, no error
1.5.4 | b68b309 | returns, no error
1.5.5 | eb1e57c | SIGSEGV, exit 139
Inputs tested
The crash is not specific to the truncated JSON above. On 1.5.5, all of these inputs exit 139:
truncated JSON
plain text saved with a
.geojsonextensionan empty
.geojsonfile
A valid GeoJSON file reads correctly. In each failing case, GDAL cannot open the file.
ST_Read handles the same error
On DuckDB 1.5.5, ST_Read raises an exception for the same malformed file instead of crashing:
con.execute("SELECT * FROM ST_Read('bad.geojson')").fetchone()
# raises duckdb.IOException
The GDAL open failure is therefore handled correctly by ST_Read but crashes ST_Read_Meta.
Impact
We found this in portolan-cli through geoparquet-io. geoparquet-io calls ST_Read_Meta to detect the CRS of every vector file it reads. One malformed file in a directory therefore kills the entire run, with no error message and no partial result.
Downstream code cannot guard against the failure because the process exits before DuckDB can return an exception. A library that needs to survive malformed input would have to isolate each call in a subprocess. We are pinning duckdb<1.5.5 for now.
Environment
DuckDB 1.5.5, Python client
spatial extension
eb1e57cPython 3.14.3 and 3.12.2, both affected
Linux 6.8.0-56-generic, x86_64, glibc 2.39
Related
Issue #544 reported a ST_Read_Meta crash in April 2025 on a different input and is now closed. This regression starts in 1.5.5 and reproduces with any unreadable file we tested.
In DuckDB 1.5.5, `ST_Read_Meta` crashes with `SIGSEGV` when GDAL cannot open the input file. The process exits 139, and no exception reaches the caller. `try`/`except` blocks and other in-process error handling cannot recover from the failure.
This is specific to `ST_Read_Meta`. `ST_Read` raises `duckdb.IOException` for the same file. DuckDB 1.5.1 through 1.5.4 also do not crash, so the regression starts in 1.5.5.
### Reproduction
```python
import duckdb
with open("bad.geojson", "w") as f:
f.write('{"type": "FeatureCollection", "features": [INVALID')
con = duckdb.connect()
con.execute("INSTALL spatial; LOAD spatial;")
con.execute("SELECT * FROM ST_Read_Meta('bad.geojson')").fetchone()
```
```console
$ python repro.py
$ echo $?
139
```
The script produces no output before the interpreter exits.
### Versions tested
Each test used the spatial extension shipped with that DuckDB release.
| duckdb | spatial extension | Result |
| ------ | ----------------- | ------------------- |
| 1.5.1 | | returns, no error |
| 1.5.2 | | returns, no error |
| 1.5.3 | | returns, no error |
| 1.5.4 | `b68b309` | returns, no error |
| 1.5.5 | `eb1e57c` | `SIGSEGV`, exit 139 |
### Inputs tested
The crash is not specific to the truncated JSON above. On 1.5.5, all of these inputs exit 139:
* truncated JSON
* plain text saved with a `.geojson` extension
* an empty `.geojson` file
A valid GeoJSON file reads correctly. In each failing case, GDAL cannot open the file.
### `ST_Read` handles the same error
On DuckDB 1.5.5, `ST_Read` raises an exception for the same malformed file instead of crashing:
```python
con.execute("SELECT * FROM ST_Read('bad.geojson')").fetchone()
# raises duckdb.IOException
```
The GDAL open failure is therefore handled correctly by `ST_Read` but crashes `ST_Read_Meta`.
### Impact
We found this in [[portolan-cli](https://github.com/portolan-sdi/portolan-cli)](https://github.com/portolan-sdi/portolan-cli) through [[geoparquet-io](https://github.com/geoparquet/geoparquet-io)](https://github.com/geoparquet/geoparquet-io). `geoparquet-io` calls `ST_Read_Meta` to detect the CRS of every vector file it reads. One malformed file in a directory therefore kills the entire run, with no error message and no partial result.
Downstream code cannot guard against the failure because the process exits before DuckDB can return an exception. A library that needs to survive malformed input would have to isolate each call in a subprocess. We are pinning `duckdb<1.5.5` for now.
### Environment
* DuckDB 1.5.5, Python client
* spatial extension `eb1e57c`
* Python 3.14.3 and 3.12.2, both affected
* Linux 6.8.0-56-generic, x86_64, glibc 2.39
### Related
Issue #544 reported a `ST_Read_Meta` crash in April 2025 on a different input and is now closed. This regression starts in 1.5.5 and reproduces with any unreadable file we tested.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the ST_Read_Meta entry point and reproduce the failure using the Python script and unreadable GeoJSON inputs described in the issue. Compare its behavior with ST_Read on DuckDB 1.5.5 and review the 1.5.4 versus 1.5.5 regression context. Done means unreadable input returns an exception instead of terminating with SIGSEGV, with coverage for the reported cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, python
- Domain
- databases
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100