CesiumGS / CesiumGS/cesium-native
PntsToGltfConverter.cpp OOB read
- Dominant language
- C++
- Stars
- 623
- Forks
- 277
- PR merge metrics
- No merged PRs in 30d
Description
## 1. Vulnerability Description
The heap-buffer-overflow READ described in the report is reproducible on the exact commit (004a56acdd0bd9c5c1f6142dc5c1264c639d5a83) 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:
```cpp
// PntsToGltfConverter.cpp:108
if (static_cast(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:
```cpp
// PntsToGltfConverter.cpp:1540-1541
const std::span 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](https://github.com/user-attachments/files/30246331/poc_pnts_oob.zip)
Reproduction command:
```bash
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:
```cpp
// PntsToGltfConverter.cpp:1553-1559
const int64_t batchTableStart = headerLength +
header.featureTableJsonByteLength +
header.featureTableBinaryByteLength;
rapidjson::Document batchTableJson;
if (header.batchTableJsonByteLength > 0) {
const std::span batchTableJsonData = pntsBinary.subspan(
static_cast(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):
```cpp
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.
Contributor guide
Research direction
Start in PntsToGltfConverter.cpp by reading parsePntsHeader and convertPntsContentToGltf, including the feature-table and batch-table subspan paths. Reproduce the issue with the provided AddressSanitizer command and poc_pnts_oob files. Done means malformed section lengths are rejected before either subspan can read past byteLength, covering both JSON paths.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- computer-graphics, security
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100