Skip to content

filameshio: fix heap OOB write in compressed vertex decode (CWE-787) - #10114

Open
foodlook wants to merge 2 commits into
google:mainfrom
foodlook:fix/filamesh-uv1-heap-oob
Open

foodlook wants to merge 2 commits into
google:mainfrom
foodlook:fix/filamesh-uv1-heap-oob

Conversation

@foodlook

Copy link
Copy Markdown

Summary

Fix a heap out-of-bounds write in MeshReader::loadMeshFromBuffer when parsing a crafted .filamesh file with the COMPRESSION flag set (non-interleaved path).

Root Cause

The uncompressed vertex buffer is allocated based on hasUV1, which is derived from Header.offsetUV1/Header.strideUV1:

const bool hasUV1 = header.offsetUV1 != uintmax && header.strideUV1 != uintmax;
size_t vertexSize = sizeof(half4) + sizeof(short4) + sizeof(ubyte4) + sizeof(ushort2)
                  + (hasUV1 ? sizeof(ushort2) : 0);   // 24 bytes when hasUV1==false
void* uncompressed = malloc(vertexSize * vertexCount);

However, the decode dispatch keys the uv1 write off CompressionHeader.uv1, an independent field embedded in the vertex payload:

if (sizes.uv1) {                              // from CompressionHeader, not hasUV1
    dstdata += sizeof(ushort2) * vertexCount;  // advances to byte 24*N (end of alloc)
    err |= decode(dstdata, vertexCount, sizeof(ushort2), srcdata, sizes.uv1);
                                               // writes 4*N bytes past end -> OOB
}

The only existing guard (\compressedSum > verticesSize) bounds the source reads, not the destination size.

Fix

Add a consistency check right after reading the \CompressionHeader: if \sizes.uv1 != 0\ but \hasUV1\ is false, reject the input.

Regression Test

\CompressedUV1MismatchRejected\ in \ est_filamesh.cpp: crafts a .filamesh buffer that triggers the inconsistency (COMPRESSION flag, hasUV1=false, CompressionHeader.uv1=1) and verifies \loadMeshFromBuffer\ returns an empty mesh.

Reproduction

Under AddressSanitizer (Docker \silkeh/clang:latest, linking the vendored meshoptimizer):

==ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 1024 at ... 0 bytes after 6144-byte region
  #2 meshopt_decodeVertexBuffer vertexcodec.cpp:1229
  #3 main poc_mem001_filament_meshreader_oob.cpp:203

After the fix, the same input is rejected cleanly with no ASan error.

In loadMeshFromBuffer, the uncompressed vertex buffer is allocated
based on hasUV1 (derived from Header.offsetUV1/strideUV1), but the
non-interleaved compressed decode dispatch keys the uv1 write off
CompressionHeader.uv1, an independent attacker-controlled field in
the vertex payload. When hasUV1 is false (no uv1 slot allocated) but
CompressionHeader.uv1 is non-zero, meshopt_decodeVertexBuffer writes
sizeof(ushort2)*vertexCount bytes past the end of the allocation.

Add a consistency check that rejects the input if CompressionHeader.uv1
is non-zero while the Header does not declare a UV1 attribute.

Add a regression test that crafts a .filamesh buffer triggering the
inconsistency and verifies loadMeshFromBuffer rejects it cleanly.
@pixelflinger pixelflinger added security internal Issue/PR does not affect clients labels Jun 16, 2026
@poweifeng
poweifeng enabled auto-merge (squash) July 22, 2026 18:49
@poweifeng

Copy link
Copy Markdown
Contributor

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

internal Issue/PR does not affect clients security

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants