ANT-2026-XGXD0ZVD · osgeo/gdal

heap-buffer-overflow high

Severity Claude high · Security research firm high · Maintainer -

Discovered by Claude Mythos Preview

REPORT

Anthropic's analysis, sealed at approval. Disclosure to the maintainer was performed by Ada Logics.

ANT-2026-XGXD0ZVD: Heap-buffer-overflow in gpb.h:288

GDAL's OSM PBF parser reads a blob into a heap buffer (33 bytes in this reproducer) and walks it with protobuf-style helpers. When ReadOSMInfo encounters an unknown field tag it calls SkipUnknownField → SkipVarInt, which reads varint continuation bytes without a sufficient end-pointer check. A crafted OSM PBF file can place a truncated varint at the exact end of the blob, causing SkipVarInt to read one byte past the allocation. An attacker who can supply an OSM PBF file to a GDAL consumer can trigger a crash (under ASAN) or a benign 1-byte over-read in release builds.

Target

Project: gdal
Location: gpb.h:288

Technical Details

ASAN: "READ of size 1 at 0x705ba424a0b1 ... located 0 bytes after 33-byte region". SkipVarInt at gpb.h:288 dereferences the next byte of a varint without confirming the pointer is still below pabyDataLimit; when the varint's high-bit continuation chain reaches the exact end of the blob buffer allocated in ReadBlob (osm_parser.cpp:1836), the next iteration reads one byte into the heap redzone.

Crash trace (truncated — full trace in attached crash.log):

INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 4293474245
INFO: Loaded 1 modules   (1157955 inline 8-bit counters): 1157955 [0x5c3b756d9a60, 0x5c3b757f45a3), 
INFO: Loaded 1 PC tables (1157955 PCs): 1157955 [0x5c3b757f45a8,0x5c3b7699f9d8), 
/out/mitab_mif_fuzzer: Running 1 inputs 1 time(s) each.
Running: /tmp/poc
EXIT_CODE:1


=== ASAN Report ===
=================================================================
==27==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x705ba424a0b1 at pc 0x5c3b6e622e16 bp 0x7ffc251f12d0 sp 0x7ffc251f12c8
READ of size 1 at 0x705ba424a0b1 thread T0
    #0 0x5c3b6e622e15 in SkipVarInt /src/gdal/ogr/ogrsf_frmts/osm/gpb.h:288:21
    #1 0x5c3b6e622e15 in SkipUnknownField(int, unsigned char const*, unsigned char const*, int) /src/gdal/ogr/ogrsf_frmts/osm/gpb.h:376:9
    #2 0x5c3b6fe44d15 in ReadOSMInfo(unsigned char const*, unsigned char const*, OSMInfo*, _OSMContext*) /src/gdal/ogr/ogrsf_frmts/osm/osm_parser.cpp:911:17
    #3 0x5c3b6fe3acba in ReadNode(unsigned char const*, unsigned char const*, _OSMContext*) /src/gdal/ogr/ogrsf_frmts/osm/osm_parser.cpp:1046:22
    #4 0x5c3b6fe38657 in ReadPrimitiveGroup /src/gdal/ogr/ogrsf_frmts/osm/osm_parser.cpp:1486:22
    #5 0x5c3b6fe38657 in ReadPrimitiveBlock(unsigned char const*, unsigned char const*, _OSMContext*) /src/gdal/ogr/ogrsf_frmts/osm/osm_parser.cpp:1615:22
    #6 0x5c3b6fe33a8a in ProcessSingleBlob /src/gdal/ogr/ogrsf_frmts/osm/osm_parser.cpp:1696:16
    #7 0x5c3b6fe33a8a in ReadBlob(_OSMContext*, BlobType) /src/gdal/ogr/ogrsf_frmts/osm/osm_parser.cpp:1890:18
    [... 30 more frames — full trace in crash.log]

Reproduction

  1. Construct an OSM PBF blob whose PrimitiveBlock contains a Node with an Info message ending in an unknown-tag varint field
  2. Truncate the varint so its continuation-bit byte lands on the final byte of the blob buffer
  3. Victim opens the file; ReadBlob allocates exactly blob-size bytes; ReadOSMInfo hits the unknown tag and calls SkipVarInt
  4. SkipVarInt reads one byte past the allocation

[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-XGXD0ZVD.


Reference: ANT-2026-XGXD0ZVD
Anthropic CVD Policy: https://www.anthropic.com/coordinated-vulnerability-disclosure

SECURITY RESEARCH FIRM ANALYSIS

Triage and disclosure were performed by Ada Logics.

Verdict
true positive
Severity
high
UPSTREAM FIX

The change that resolved this finding.

diff --git a/autotest/ogr/data/osm/poc_14532.osm.pbf b/autotest/ogr/data/osm/poc_14532.osm.pbf
new file mode 100644
index 000000000000..64b9f7723fb2
Binary files /dev/null and b/autotest/ogr/data/osm/poc_14532.osm.pbf differ
diff --git a/autotest/ogr/ogr_osm.py b/autotest/ogr/ogr_osm.py
index fcd757c140c8..723e727a45cc 100755
--- a/autotest/ogr/ogr_osm.py
+++ b/autotest/ogr/ogr_osm.py
@@ -955,3 +955,15 @@ def test_ogr_osm_parse_complex_multipolygon():
         f,
         "MULTIPOLYGON (((0 10,0 11,1 11,1 10,0 10),(0.1 10.1,0.1 10.4,0.9 10.4,0.9 10.6,0.1 10.1),(0.1 10.6,0.1 10.9,0.9 10.9,0.9 10.6,0.1 10.6)))",
     )
+
+
+###############################################################################
+# Test bugfix for https://github.com/OSGeo/gdal/issues/14532
+
+
+def test_ogr_osm_parse_fix_gh14532():
+
+    ds = ogr.Open("data/osm/poc_14532.osm.pbf")
+    for lyr in ds:
+        for f in lyr:
+            pass
diff --git a/ogr/ogrsf_frmts/osm/osm_parser.cpp b/ogr/ogrsf_frmts/osm/osm_parser.cpp
index 08d686471f8c..6e946ea4be6d 100644
--- a/ogr/ogrsf_frmts/osm/osm_parser.cpp
+++ b/ogr/ogrsf_frmts/osm/osm_parser.cpp
@@ -1043,8 +1043,7 @@ static bool ReadNode(const GByte *pabyData, const GByte *pabyDataLimit,
                 unsigned int nSize = 0;
                 READ_SIZE(pabyData, pabyDataLimit, nSize);
 
-                if (!ReadOSMInfo(pabyData, pabyDataLimit + nSize, &sNode.sInfo,
-                                 psCtxt))
+                if (!ReadOSMInfo(pabyData, pabyDataLimit, &sNode.sInfo, psCtxt))
                     THROW_OSM_PARSING_EXCEPTION;
 
                 pabyData += nSize;

https://github.com/OSGeo/gdal/commit/993ce15f943d68806e36176f8ddab26c649745ae

TIMELINE

Dates from discovery through public reveal.

  1. 2026-03-24 Reported to tracker
  2. 2026-05-11 Sent to maintainer
  3. 2026-05-13 Maintainer acknowledged
  4. 2026-06-05 Patch released
  5. 2026-07-08 Publicly revealed
PROVENANCE

SHA-3-512 hash:

062b06724bad1529fb53169abf44c9813d25267fe0db5ef8aaf8ade2995ed83fc2164777c2f6a89aecd15ee09a1720c5b77746fe5eb058fa8723f9527699a243

Committed 2026-05-13 10:55 PT

Revealed 2026-07-08 16:31 PT

Verify (download preimage.json)

Show preimage JSON
{
  "ant_id": "ANT-2026-XGXD0ZVD",
  "bug_class": "Heap-buffer-overflow",
  "claude_severity": "high",
  "commit_sha": null,
  "created_at": "2026-03-24T18:28:27+00:00",
  "description": "GDAL's OSM PBF parser reads a blob into a heap buffer (33 bytes in this reproducer) and walks it with protobuf-style helpers. When ReadOSMInfo encounters an unknown field tag it calls SkipUnknownField → SkipVarInt, which reads varint continuation bytes without a sufficient end-pointer check. A crafted OSM PBF file can place a truncated varint at the exact end of the blob, causing SkipVarInt to read one byte past the allocation. An attacker who can supply an OSM PBF file to a GDAL consumer can trigger a crash (under ASAN) or a benign 1-byte over-read in release builds.",
  "discovered_at": null,
  "location": "gpb.h:288",
  "poc_sha256": null,
  "preimage_version": 1,
  "project": "gdal",
  "reproduction": [
    "1. Construct an OSM PBF blob whose PrimitiveBlock contains a Node with an Info message ending in an unknown-tag varint field",
    "2. Truncate the varint so its continuation-bit byte lands on the final byte of the blob buffer",
    "3. Victim opens the file; ReadBlob allocates exactly blob-size bytes; ReadOSMInfo hits the unknown tag and calls SkipVarInt",
    "4. SkipVarInt reads one byte past the allocation"
  ],
  "technical_details": "ASAN: \"READ of size 1 at 0x705ba424a0b1 ... located 0 bytes after 33-byte region\". SkipVarInt at gpb.h:288 dereferences the next byte of a varint without confirming the pointer is still below pabyDataLimit; when the varint's high-bit continuation chain reaches the exact end of the blob buffer allocated in ReadBlob (osm_parser.cpp:1836), the next iteration reads one byte into the heap redzone.",
  "title": "Heap-buffer-overflow in gpb.h:288",
  "vendor_severity": "high"
}