1. Vulnerability Description
The heap-buffer-overflow READ described in the report is reproducible on the exact commit (004a56a) cited. A 28-byte malicious .pnts file triggers an out-of-bounds read in PntsToGltfConverter.cpp, detected by AddressSanitizer as a heap-buffer-overflow of size 1 at the byte immediately following the 28-byte allocation.
2. Vulnerability Analysis
parsePntsHeader (line 84–114) performs only one size check on the incoming tile buffer:
// PntsToGltfConverter.cpp:108
if (static_cast<uint32_t>(pntsBinary.size()) < pHeader->byteLength) {
result.errors.emplaceError(
"The PNTS is invalid because the total data available is less than the "
"size specified in its header.");
return;
}
This check verifies that the outer buffer is at least byteLength bytes, but it does not verify that the four declared section lengths actually fit inside byteLength:
headerLength + featureTableJsonByteLength
+ featureTableBinaryByteLength
+ batchTableJsonByteLength
+ batchTableBinaryByteLength <= byteLength <-- NEVER CHECKED
Consequently, when convertPntsContentToGltf (line 1531) builds the feature table JSON span:
// PntsToGltfConverter.cpp:1540-1541
const std::span<const std::byte> featureTableJsonData =
pntsBinary.subspan(headerLength, header.featureTableJsonByteLength);
std::span::subspan performs no bounds check in release mode. If headerLength + featureTableJsonByteLength > pntsBinary.size(), the resulting span points past the end of the allocation.
That span is then handed to parseFeatureTableJson (line 556), which calls rapidjson::Document::Parse(ptr, len). RapidJSON's MemoryStream::Peek() reads the first byte at ptr — which is now an out-of-bounds heap address — triggering the heap-buffer-overflow READ.
A second, structurally identical vulnerable path exists for the batch table JSON at lines 1553–1559 (batchTableStart + subspan).
3. Vulnerability Reproduction
poc_pnts_oob.zip
Reproduction command:
g++ -std=c++20 -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer \
-I/home/work/rapidjson/include \
/home/work/scripts/poc_pnts_oob.cpp -o /tmp/poc && \
ASAN_OPTIONS=detect_leaks=0 /tmp/poc /home/download/poc_pnts_oob.pnts
4、Additional Vulnerable Path
The same missing-check pattern affects the batch table JSON path at lines 1553–1559:
// PntsToGltfConverter.cpp:1553-1559
const int64_t batchTableStart = headerLength +
header.featureTableJsonByteLength +
header.featureTableBinaryByteLength;
rapidjson::Document batchTableJson;
if (header.batchTableJsonByteLength > 0) {
const std::span<const std::byte> batchTableJsonData = pntsBinary.subspan(
static_cast<size_t>(batchTableStart),
header.batchTableJsonByteLength); // <-- also unchecked
A PNTS with byteLength=28, ftJson=1, ftBin=1, btJson=1 (file poc_pnts_oob_batchtable.pnts) reaches this path. In the probe it crashes at the feature-table subspan first (which is encountered earlier in the same function), but in the real library the same class of OOB read applies to the batch table as well. A proper fix must cover both paths.
5、Recommended Fix
Add a single bounds check in parsePntsHeader, immediately after the existing byteLength check (after line 113):
const uint64_t headerLength64 = sizeof(PntsHeader);
const uint64_t sectionsEnd =
headerLength64 +
header.featureTableJsonByteLength +
header.featureTableBinaryByteLength +
header.batchTableJsonByteLength +
header.batchTableBinaryByteLength;
if (sectionsEnd > header.byteLength) {
result.errors.emplaceError(
"The PNTS is invalid because the feature/batch table sections extend "
"past the byteLength declared in the header.");
return;
}
Using uint64_t for the intermediate sum avoids uint32_t overflow in the addition. This single check protects both the feature-table and batch-table subspan calls downstream.
1. Vulnerability Description
The heap-buffer-overflow READ described in the report is reproducible on the exact commit (004a56a) cited. A 28-byte malicious .pnts file triggers an out-of-bounds read in PntsToGltfConverter.cpp, detected by AddressSanitizer as a heap-buffer-overflow of size 1 at the byte immediately following the 28-byte allocation.
2. Vulnerability Analysis
parsePntsHeader(line 84–114) performs only one size check on the incoming tile buffer:This check verifies that the outer buffer is at least
byteLengthbytes, but it does not verify that the four declared section lengths actually fit insidebyteLength:Consequently, when
convertPntsContentToGltf(line 1531) builds the feature table JSON span:std::span::subspanperforms no bounds check in release mode. IfheaderLength + featureTableJsonByteLength > pntsBinary.size(), the resulting span points past the end of the allocation.That span is then handed to
parseFeatureTableJson(line 556), which callsrapidjson::Document::Parse(ptr, len). RapidJSON'sMemoryStream::Peek()reads the first byte atptr— which is now an out-of-bounds heap address — triggering the heap-buffer-overflow READ.A second, structurally identical vulnerable path exists for the batch table JSON at lines 1553–1559 (
batchTableStart+subspan).3. Vulnerability Reproduction
poc_pnts_oob.zip
Reproduction command:
g++ -std=c++20 -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer \ -I/home/work/rapidjson/include \ /home/work/scripts/poc_pnts_oob.cpp -o /tmp/poc && \ ASAN_OPTIONS=detect_leaks=0 /tmp/poc /home/download/poc_pnts_oob.pnts4、Additional Vulnerable Path
The same missing-check pattern affects the batch table JSON path at lines 1553–1559:
A PNTS with
byteLength=28, ftJson=1, ftBin=1, btJson=1(filepoc_pnts_oob_batchtable.pnts) reaches this path. In the probe it crashes at the feature-table subspan first (which is encountered earlier in the same function), but in the real library the same class of OOB read applies to the batch table as well. A proper fix must cover both paths.5、Recommended Fix
Add a single bounds check in
parsePntsHeader, immediately after the existingbyteLengthcheck (after line 113):Using
uint64_tfor the intermediate sum avoidsuint32_toverflow in the addition. This single check protects both the feature-table and batch-tablesubspancalls downstream.