ANT-2026-3DSGRTDD · osgeo/gdal
heap-buffer-overflow high
Severity Claude high · Security research firm high · Maintainer -
Discovered by Claude Opus 4.6
Anthropic's analysis, sealed at approval. Disclosure to the maintainer was performed by Ada Logics.
ANT-2026-3DSGRTDD: Heap-buffer-overflow in gbits.c:62
GDAL's GRIB2 driver calls gdal_comunpack (comunpack.c:150) to decode complex-packed data from Section 7, which in turn invokes gdal_gbits to extract bitfields from a packed byte buffer. That buffer is a 228-byte heap region allocated by ReadGrib2Record (degrib2.cpp:1008). With a crafted GRIB2 file, the bit offset/width parameters passed down from g2_unpack7 cause gdal_gbits to index one byte past the end of the allocation. An attacker who can supply a GRIB2 file to any GDAL-based application gets a 1-byte out-of-bounds heap read — primarily a denial-of-service, with marginal information-disclosure potential.
Target
Project: gdal
Location: gbits.c:62
Technical Details
ASAN: "READ of size 1 at 0x740e9e602fe4 ... 0 bytes after 228-byte region". gdal_gbits performs bit-level extraction from a packed byte array but does not validate that the computed byte offset stays within the bounds of the buffer supplied by gdal_comunpack. The offset and bit-width are derived from fields inside the GRIB2 message (Section 5/7 metadata), so a malformed file can push the read past the end of the realloc'd c_ipack buffer.
Crash trace (truncated — full trace in attached crash.log):
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 3933186869
INFO: Loaded 1 modules (1158073 inline 8-bit counters): 1158073 [0x5949ea59ba60, 0x5949ea6b6619),
INFO: Loaded 1 PC tables (1158073 PCs): 1158073 [0x5949ea6b6620,0x5949eb8621b0),
/out/gdal_filesystem_fuzzer: Running 1 inputs 1 time(s) each.
Running: /tmp/poc
EXIT_CODE:1
=== ASAN Report ===
=================================================================
==27==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x740e9e602fe4 at pc 0x5949e6203e4d bp 0x7ffc95821c60 sp 0x7ffc95821c58
READ of size 1 at 0x740e9e602fe4 thread T0
#0 0x5949e6203e4c in gdal_gbits /src/gdal/frmts/grib/degrib/g2clib/gbits.c:62:54
#1 0x5949e6203e4c in gdal_gbit /src/gdal/frmts/grib/degrib/g2clib/gbits.c:6:14
#2 0x5949e62093d2 in gdal_comunpack /src/gdal/frmts/grib/degrib/g2clib/comunpack.c:150:15
#3 0x5949e6207d6a in gdal_g2_unpack7 /src/gdal/frmts/grib/degrib/g2clib/g2_unpack7.c:122:13
#4 0x5949e4ba41b7 in gdal_g2_getfld /src/gdal/frmts/grib/degrib/g2clib/g2_getfld.c:527:16
#5 0x5949e4b951f3 in unpk_g2ncep /src/gdal/frmts/grib/degrib/degrib/grib2api.c:878:11
#6 0x5949e4b64ed3 in ReadGrib2Record /src/gdal/frmts/grib/degrib/degrib/degrib2.cpp:1187:7
#7 0x5949e4b20ec4 in GRIBRasterBand::ReadGribData(VSIVirtualHandle*, unsigned long long, int, double**, grib_MetaData**) /src/gdal/frmts/grib/gribdataset.cpp:1160:5
[... 35 more frames — full trace in crash.log]
Reproduction
- Construct a GRIB2 message where Section 5 declares complex packing and Section 7 metadata (group widths/references/bit counts) implies more packed bits than the actual Section 7 payload contains
- Deliver the file to a target that reads raster data from it (e.g., gdalinfo -checksum, tile server, format conversion pipeline)
- GRIBRasterBand::IReadBlock triggers LoadData → ReadGrib2Record → g2_unpack7 → comunpack → gbits, which reads past the end of the c_ipack heap buffer
[No reproducer or sanitizer output attached — request from security-cvd@anthropic.com if needed.]
Acknowledgement
This vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged by the Anthropic security team in collaboration with Anthropic Research. Please direct questions to security-cvd@anthropic.com and reference ANT-2026-3DSGRTDD.
Reference: ANT-2026-3DSGRTDD
Anthropic CVD Policy: https://www.anthropic.com/coordinated-vulnerability-disclosure
Triage and disclosure were performed by Ada Logics.
- Verdict
- true positive
- Severity
- high
The change that resolved this finding.
diff --git a/autotest/gdrivers/grib.py b/autotest/gdrivers/grib.py
index a9269453afae..72d8cfe1ddc0 100755
--- a/autotest/gdrivers/grib.py
+++ b/autotest/gdrivers/grib.py
@@ -13,6 +13,7 @@
# SPDX-License-Identifier: MIT
###############################################################################
+import base64
import os
import shutil
import struct
@@ -2438,3 +2439,17 @@ def test_grib_grib2_tmerc_negative_false_easting_false_northing(tmp_vsimem):
"+proj=tmerc +lat_0=-1 +lon_0=-2 +k=1 +x_0=-300000 +y_0=-400000"
in ds.GetSpatialRef().ExportToProj4()
)
+
+
+@gdaltest.enable_exceptions()
+def test_grib_complex_unpacking_invalid_bits_per_packed_value(tmp_vsimem):
+
+ with gdal.VSIFile(tmp_vsimem / "src.grib2", "wb") as infile:
+ infile.write(
+ base64.b64decode(
+ "R1JJQgAAAAIAAAAAAAAA4gAAABUBAAcAAAIBAQfoAQEAAAAAAQAAAEgDAAAAABAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxBQAAABAAAwAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAQjyAAAAAAEAAAAQAAJbAAAABgb/AAAAGAcBookAAVwDBAUlBwgJCgUMDQ4PNzc3Nw=="
+ )
+ )
+
+ with pytest.raises(Exception, match="Error reading GRIB data"):
+ gdal.Translate("", tmp_vsimem / "src.grib2", format="MEM")
diff --git a/frmts/grib/degrib/g2clib/comunpack.c b/frmts/grib/degrib/g2clib/comunpack.c
index 5ec35ee48f6e..0638a0d70072 100644
--- a/frmts/grib/degrib/g2clib/comunpack.c
+++ b/frmts/grib/degrib/g2clib/comunpack.c
@@ -147,7 +147,7 @@ int comunpack(unsigned char *cpack,g2int cpack_length,g2int lensec,g2int idrsnum
if (idrsnum == 3) {
if (nbitsd != 0) {
// one mistake here should be unsigned int
- gbit(cpack,&ival1,iofst,nbitsd);
+ int ok = gbit2(cpack, cpack_length, &ival1, iofst,nbitsd) != -1;
iofst=iofst+nbitsd;
// gbit(cpack,&isign,iofst,1);
// iofst=iofst+1
@@ -156,7 +156,7 @@ int comunpack(unsigned char *cpack,g2int cpack_length,g2int lensec,g2int idrsnum
// if (isign == 1) ival1=-ival1;
if (idrstmpl[16] == 2) {
// one mistake here should be unsigned int
- gbit(cpack,&ival2,iofst,nbitsd);
+ ok = ok && gbit2(cpack, cpack_length, &ival2,iofst,nbitsd) != -1;
iofst=iofst+nbitsd;
// gbit(cpack,&isign,iofst,1);
// iofst=iofst+1;
@@ -164,10 +164,19 @@ int comunpack(unsigned char *cpack,g2int cpack_length,g2int lensec,g2int idrsnum
// iofst=iofst+nbitsd-1;
// if (isign == 1) ival2=-ival2;
}
- gbit(cpack,&isign,iofst,1);
+ ok = ok && gbit2(cpack, cpack_length, &isign,iofst,1) != -1;
iofst=iofst+1;
- gbit(cpack,&minsd,iofst,nbitsd-1);
+ ok = ok && gbit2(cpack, cpack_length, &minsd,iofst,nbitsd-1) != - 1;
iofst=iofst+nbitsd-1;
+
+ if (!ok)
+ {
+ free(ifld);
+ free(gref);
+ free(gwidth);
+ return -1;
+ }
+
if (isign == 1 && minsd != INT_MIN) minsd=-minsd;
}
else {
diff --git a/frmts/grib/gribdataset.cpp b/frmts/grib/gribdataset.cpp
index b0be93a0f5c2..7e778af38ebc 100644
--- a/frmts/grib/gribdataset.cpp
+++ b/frmts/grib/gribdataset.cpp
@@ -847,7 +847,7 @@ CPLErr GRIBRasterBand::LoadData()
ReadGribData(poGDS->fp, start, subgNum, &m_Grib_Data, &m_Grib_MetaData);
if (!m_Grib_Data)
{
- CPLError(CE_Failure, CPLE_AppDefined, "Out of memory.");
+ CPLError(CE_Failure, CPLE_AppDefined, "Error reading GRIB data.");
if (m_Grib_MetaData != nullptr)
{
MetaFree(m_Grib_MetaData);https://github.com/OSGeo/gdal/commit/60d61b5181a6288ebc29624fbac29c81f92c9e7c
Dates from discovery through public reveal.
- 2026-03-24 Reported to tracker
- 2026-05-11 Sent to maintainer
- 2026-05-13 Maintainer acknowledged
- 2026-06-05 Patch released
- 2026-07-20 Publicly revealed
SHA-3-512 hash:
b6a6a1804140a36fe0508887afae650be2caf1685e7440aaf27e56df624712155cfeecb3f3c13f3d060ecbe2605e547688529b05c3913d3d252f89615f639f50
Committed 2026-05-13 10:55 PT
Revealed 2026-07-20 22:00 PT
Verify (download preimage.json)
Show preimage JSON
{
"ant_id": "ANT-2026-3DSGRTDD",
"bug_class": "Heap-buffer-overflow",
"claude_severity": "high",
"commit_sha": null,
"created_at": "2026-03-24T18:29:30+00:00",
"description": "GDAL's GRIB2 driver calls gdal_comunpack (comunpack.c:150) to decode complex-packed data from Section 7, which in turn invokes gdal_gbits to extract bitfields from a packed byte buffer. That buffer is a 228-byte heap region allocated by ReadGrib2Record (degrib2.cpp:1008). With a crafted GRIB2 file, the bit offset/width parameters passed down from g2_unpack7 cause gdal_gbits to index one byte past the end of the allocation. An attacker who can supply a GRIB2 file to any GDAL-based application gets a 1-byte out-of-bounds heap read — primarily a denial-of-service, with marginal information-disclosure potential.",
"discovered_at": null,
"location": "gbits.c:62",
"poc_sha256": null,
"preimage_version": 1,
"project": "gdal",
"reproduction": [
"Construct a GRIB2 message where Section 5 declares complex packing and Section 7 metadata (group widths/references/bit counts) implies more packed bits than the actual Section 7 payload contains",
"Deliver the file to a target that reads raster data from it (e.g., gdalinfo -checksum, tile server, format conversion pipeline)",
"GRIBRasterBand::IReadBlock triggers LoadData → ReadGrib2Record → g2_unpack7 → comunpack → gbits, which reads past the end of the c_ipack heap buffer"
],
"technical_details": "ASAN: \"READ of size 1 at 0x740e9e602fe4 ... 0 bytes after 228-byte region\". gdal_gbits performs bit-level extraction from a packed byte array but does not validate that the computed byte offset stays within the bounds of the buffer supplied by gdal_comunpack. The offset and bit-width are derived from fields inside the GRIB2 message (Section 5/7 metadata), so a malformed file can push the read past the end of the realloc'd c_ipack buffer.",
"title": "Heap-buffer-overflow in gbits.c:62",
"vendor_severity": "high"
}