From 943f9773e8e7f2077d692ba4cf6f42939fc75c82 Mon Sep 17 00:00:00 2001 From: TristanInSec Date: Fri, 17 Apr 2026 11:23:16 -0400 Subject: [PATCH] Validate filamesh buffer bounds in MeshReader Add a dataSize parameter to loadMeshFromBuffer and validate all header fields against the buffer boundary before advancing the parse pointer. Also check for multiplication overflow in the compressed index and vertex buffer size calculations. Update all callers to pass the buffer size. --- .../hello-ar/FilamentArView/FilamentApp.cpp | 2 +- .../hello-pbr/hello-pbr/FilamentApp.cpp | 2 +- .../include/filameshio/MeshReader.h | 4 +- libs/filameshio/src/MeshReader.cpp | 58 +++++++++++++++++-- libs/filameshio/tests/test_filamesh.cpp | 4 +- samples/hellopbr.cpp | 2 +- samples/hellostereo.cpp | 2 +- samples/materialinstancestress.cpp | 2 +- samples/multiple_windows.cpp | 4 +- samples/rendertarget.cpp | 2 +- samples/suzanne.cpp | 4 +- web/filament-js/jsbindings.cpp | 2 +- 12 files changed, 67 insertions(+), 21 deletions(-) diff --git a/ios/samples/hello-ar/hello-ar/FilamentArView/FilamentApp.cpp b/ios/samples/hello-ar/hello-ar/FilamentArView/FilamentApp.cpp index aa8038d9657d..65133f1b2e48 100644 --- a/ios/samples/hello-ar/hello-ar/FilamentArView/FilamentApp.cpp +++ b/ios/samples/hello-ar/hello-ar/FilamentArView/FilamentApp.cpp @@ -236,7 +236,7 @@ void FilamentApp::setupMaterial() { void FilamentApp::setupMesh() { MeshReader::Mesh mesh = MeshReader::loadMeshFromBuffer(engine, RESOURCES_CUBE_DATA, - nullptr, nullptr, app.materialInstance); + RESOURCES_CUBE_SIZE, nullptr, nullptr, app.materialInstance); app.materialInstance->setParameter("baseColor", RgbType::sRGB, {0.71f, 0.0f, 0.0f}); app.renderable = mesh.renderable; diff --git a/ios/samples/hello-pbr/hello-pbr/FilamentApp.cpp b/ios/samples/hello-pbr/hello-pbr/FilamentApp.cpp index 732be6993340..d0889eafdaa5 100644 --- a/ios/samples/hello-pbr/hello-pbr/FilamentApp.cpp +++ b/ios/samples/hello-pbr/hello-pbr/FilamentApp.cpp @@ -88,7 +88,7 @@ void FilamentApp::initialize() { app.materialInstance = app.mat->createInstance(); MeshReader::Mesh mesh = MeshReader::loadMeshFromBuffer(engine, RESOURCES_MATERIAL_SPHERE_DATA, - nullptr, nullptr, app.materialInstance); + RESOURCES_MATERIAL_SPHERE_SIZE, nullptr, nullptr, app.materialInstance); app.materialInstance->setParameter("baseColor", RgbType::sRGB, {0.71f, 0.0f, 0.0f}); app.renderable = mesh.renderable; diff --git a/libs/filameshio/include/filameshio/MeshReader.h b/libs/filameshio/include/filameshio/MeshReader.h index 2253379120d7..12aa772502b0 100644 --- a/libs/filameshio/include/filameshio/MeshReader.h +++ b/libs/filameshio/include/filameshio/MeshReader.h @@ -102,7 +102,7 @@ class UTILS_PUBLIC MeshReader { * named "DefaultMaterial" to the registry. */ static Mesh loadMeshFromBuffer(filament::Engine* engine, - void const* data, Callback destructor, void* user, + void const* data, size_t dataSize, Callback destructor, void* user, MaterialRegistry& materials); /** @@ -111,7 +111,7 @@ class UTILS_PUBLIC MeshReader { * renderable are assigned the specified default material. */ static Mesh loadMeshFromBuffer(filament::Engine* engine, - void const* data, Callback destructor, void* user, + void const* data, size_t dataSize, Callback destructor, void* user, filament::MaterialInstance* defaultMaterial); }; diff --git a/libs/filameshio/src/MeshReader.cpp b/libs/filameshio/src/MeshReader.cpp index 700856177db0..2a0fae0d35f4 100644 --- a/libs/filameshio/src/MeshReader.cpp +++ b/libs/filameshio/src/MeshReader.cpp @@ -165,6 +165,9 @@ MeshReader::Mesh MeshReader::loadMeshFromFile(filament::Engine* engine, const ut Mesh mesh; int fd = open(path.c_str(), O_RDONLY); + if (fd < 0) { + return mesh; + } size_t size = fileSize(fd); char* data = (char*) malloc(size); @@ -176,7 +179,7 @@ MeshReader::Mesh MeshReader::loadMeshFromFile(filament::Engine* engine, const ut p += sizeof(MAGICID); if (!strncmp(MAGICID, magic, 8)) { - mesh = loadMeshFromBuffer(engine, data, nullptr, nullptr, materials); + mesh = loadMeshFromBuffer(engine, data, size, nullptr, nullptr, materials); } Fence::waitAndDestroy(engine->createFence()); @@ -188,42 +191,77 @@ MeshReader::Mesh MeshReader::loadMeshFromFile(filament::Engine* engine, const ut } MeshReader::Mesh MeshReader::loadMeshFromBuffer(filament::Engine* engine, - void const* data, Callback destructor, void* user, + void const* data, size_t dataSize, Callback destructor, void* user, MaterialInstance* defaultMaterial) { MaterialRegistry reg; reg.registerMaterialInstance(utils::CString(DEFAULT_MATERIAL), defaultMaterial); - return loadMeshFromBuffer(engine, data, destructor, user, reg); + return loadMeshFromBuffer(engine, data, dataSize, destructor, user, reg); } MeshReader::Mesh MeshReader::loadMeshFromBuffer(filament::Engine* engine, - void const* data, Callback destructor, void* user, + void const* data, size_t dataSize, Callback destructor, void* user, MaterialRegistry& materials) { const uint8_t* p = (const uint8_t*) data; - if (strncmp(MAGICID, (const char *) p, 8)) { + const uint8_t* const end = p + dataSize; + + if (dataSize < 8 || strncmp(MAGICID, (const char *) p, 8)) { utils::slog.e << "Magic string not found." << utils::io::endl; return {}; } p += 8; + if (size_t(end - p) < sizeof(Header)) { + utils::slog.e << "Buffer too small for header." << utils::io::endl; + return {}; + } Header* header = (Header*) p; p += sizeof(Header); + if (header->vertexSize > size_t(end - p)) { + utils::slog.e << "Invalid vertexSize." << utils::io::endl; + return {}; + } uint8_t const* vertexData = p; p += header->vertexSize; + if (header->indexSize > size_t(end - p)) { + utils::slog.e << "Invalid indexSize." << utils::io::endl; + return {}; + } uint8_t const* indices = p; p += header->indexSize; + size_t partsBytes = size_t(header->parts) * sizeof(Part); + if (header->parts != 0 && partsBytes / sizeof(Part) != header->parts) { + utils::slog.e << "Invalid parts count." << utils::io::endl; + return {}; + } + if (partsBytes > size_t(end - p)) { + utils::slog.e << "Invalid parts count." << utils::io::endl; + return {}; + } Part* parts = (Part*) p; - p += header->parts * sizeof(Part); + p += partsBytes; + if (size_t(end - p) < sizeof(uint32_t)) { + utils::slog.e << "Buffer too small for material count." << utils::io::endl; + return {}; + } uint32_t materialCount = (uint32_t) *p; p += sizeof(uint32_t); std::vector partsMaterial(materialCount); for (size_t i = 0; i < materialCount; i++) { + if (size_t(end - p) < sizeof(uint32_t)) { + utils::slog.e << "Buffer too small for material name." << utils::io::endl; + return {}; + } uint32_t nameLength = (uint32_t) *p; p += sizeof(uint32_t); + if (size_t(end - p) < nameLength + 1u) { + utils::slog.e << "Invalid material name length." << utils::io::endl; + return {}; + } partsMaterial[i] = (const char*) p; p += nameLength + 1; // null terminated } @@ -244,6 +282,10 @@ MeshReader::Mesh MeshReader::loadMeshFromBuffer(filament::Engine* engine, size_t indexSize = header->indexType == UI16 ? sizeof(uint16_t) : sizeof(uint32_t); size_t indexCount = header->indexCount; size_t uncompressedSize = indexSize * indexCount; + if (indexCount != 0 && uncompressedSize / indexCount != indexSize) { + utils::slog.e << "Index buffer size overflow." << utils::io::endl; + return {}; + } void* uncompressed = malloc(uncompressedSize); int err = meshopt_decodeIndexBuffer(uncompressed, indexCount, indexSize, indices, indicesSize); @@ -303,6 +345,10 @@ MeshReader::Mesh MeshReader::loadMeshFromBuffer(filament::Engine* engine, (hasUV1 ? sizeof(ushort2) : 0); size_t vertexCount = header->vertexCount; size_t uncompressedSize = vertexSize * vertexCount; + if (vertexCount != 0 && uncompressedSize / vertexCount != vertexSize) { + utils::slog.e << "Vertex buffer size overflow." << utils::io::endl; + return {}; + } void* uncompressed = malloc(uncompressedSize); const uint8_t* srcdata = vertexData + sizeof(CompressionHeader); int err = 0; diff --git a/libs/filameshio/tests/test_filamesh.cpp b/libs/filameshio/tests/test_filamesh.cpp index 57d3db26332c..89ced795c5fd 100644 --- a/libs/filameshio/tests/test_filamesh.cpp +++ b/libs/filameshio/tests/test_filamesh.cpp @@ -157,7 +157,7 @@ TEST_F(FilameshTest, NonInterleaved) { // Deserialize the mesh as a smoke test. MaterialInstance* mi = engine->getDefaultMaterial()->createInstance(); - auto mesh = MeshReader::loadMeshFromBuffer(engine, stream.str().data(), nullptr, nullptr, mi); + auto mesh = MeshReader::loadMeshFromBuffer(engine, stream.str().data(), stream.str().size(), nullptr, nullptr, mi); auto& rm = engine->getRenderableManager(); auto inst = rm.getInstance(mesh.renderable); EXPECT_EQ(rm.getPrimitiveCount(inst), 1); @@ -206,7 +206,7 @@ TEST_F(FilameshTest, Interleaved) { // Deserialize the mesh as a smoke test. MaterialInstance* mi = engine->getDefaultMaterial()->createInstance(); - auto mesh = MeshReader::loadMeshFromBuffer(engine, stream.str().data(), nullptr, nullptr, mi); + auto mesh = MeshReader::loadMeshFromBuffer(engine, stream.str().data(), stream.str().size(), nullptr, nullptr, mi); auto& rm = engine->getRenderableManager(); auto inst = rm.getInstance(mesh.renderable); EXPECT_EQ(rm.getPrimitiveCount(inst), 1); diff --git a/samples/hellopbr.cpp b/samples/hellopbr.cpp index 6f1f383e4e31..a672fe2efeb3 100644 --- a/samples/hellopbr.cpp +++ b/samples/hellopbr.cpp @@ -123,7 +123,7 @@ int main(int argc, char** argv) { mi->setParameter("reflectance", 0.5f); // Add geometry into the scene. - app.mesh = MeshReader::loadMeshFromBuffer(engine, MONKEY_SUZANNE_DATA, nullptr, nullptr, mi); + app.mesh = MeshReader::loadMeshFromBuffer(engine, MONKEY_SUZANNE_DATA, MONKEY_SUZANNE_SIZE, nullptr, nullptr, mi); auto ti = tcm.getInstance(app.mesh.renderable); app.transform = mat4f{ mat3f(1), float3(0, 0, -4) } * tcm.getWorldTransform(ti); rcm.setCastShadows(rcm.getInstance(app.mesh.renderable), false); diff --git a/samples/hellostereo.cpp b/samples/hellostereo.cpp index 668aa6263653..59a0a2669e0c 100644 --- a/samples/hellostereo.cpp +++ b/samples/hellostereo.cpp @@ -193,7 +193,7 @@ int main(int argc, char** argv) { // Add a monkey and a light source into the main scene. app.monkeyMesh = MeshReader::loadMeshFromBuffer( - engine, MONKEY_SUZANNE_DATA, nullptr, nullptr, mi); + engine, MONKEY_SUZANNE_DATA, MONKEY_SUZANNE_SIZE, nullptr, nullptr, mi); auto ti = tcm.getInstance(app.monkeyMesh.renderable); app.monkeyTransform = mat4f{mat3f(1), monkeyPosition } * tcm.getWorldTransform(ti); rcm.setCastShadows(rcm.getInstance(app.monkeyMesh.renderable), false); diff --git a/samples/materialinstancestress.cpp b/samples/materialinstancestress.cpp index 92a178b6e188..50bc0cdb13c2 100644 --- a/samples/materialinstancestress.cpp +++ b/samples/materialinstancestress.cpp @@ -207,7 +207,7 @@ int main(int argc, char** argv) { auto setup = [&app](Engine* engine, View* view, Scene* scene) { app.desiredObjectCount = app.ui.objectCountSlider; app.suzanneTemplate = MeshReader::loadMeshFromBuffer(engine, - MONKEY_SUZANNE_DATA, nullptr, nullptr, nullptr); + MONKEY_SUZANNE_DATA, MONKEY_SUZANNE_SIZE, nullptr, nullptr, nullptr); app.litMaterial = Material::Builder() .package(RESOURCES_AIDEFAULTMAT_DATA, RESOURCES_AIDEFAULTMAT_SIZE) diff --git a/samples/multiple_windows.cpp b/samples/multiple_windows.cpp index e3d665119019..f954ebf325e0 100644 --- a/samples/multiple_windows.cpp +++ b/samples/multiple_windows.cpp @@ -297,7 +297,7 @@ void setup_static_scene(Window& w, Engine* engine) { w.materialInstance->setParameter("sheenColor", 0.00f); w.materialInstance->setParameter("clearCoat", 1.00f); w.materialInstance->setParameter("clearCoatRoughness", 0.00f); - w.mesh = filamesh::MeshReader::loadMeshFromBuffer(engine, MONKEY_SUZANNE_DATA, nullptr, nullptr, w.materialInstance); + w.mesh = filamesh::MeshReader::loadMeshFromBuffer(engine, MONKEY_SUZANNE_DATA, MONKEY_SUZANNE_SIZE, nullptr, nullptr, w.materialInstance); w.scene->addEntity(w.mesh.renderable); int width, height; @@ -330,7 +330,7 @@ void setup_animating_scene(Window& w, Engine* engine) { w.materialInstance->setParameter("sheenColor", 0.00f); w.materialInstance->setParameter("clearCoat", 0.00f); w.materialInstance->setParameter("clearCoatRoughness", 0.00f); - w.mesh = filamesh::MeshReader::loadMeshFromBuffer(engine, MONKEY_SUZANNE_DATA, nullptr, nullptr, w.materialInstance); + w.mesh = filamesh::MeshReader::loadMeshFromBuffer(engine, MONKEY_SUZANNE_DATA, MONKEY_SUZANNE_SIZE, nullptr, nullptr, w.materialInstance); w.scene->addEntity(w.mesh.renderable); int width, height; diff --git a/samples/rendertarget.cpp b/samples/rendertarget.cpp index 084d316e437a..839c404c2878 100644 --- a/samples/rendertarget.cpp +++ b/samples/rendertarget.cpp @@ -276,7 +276,7 @@ int main(int argc, char** argv) { mi->setParameter("reflectance", 0.5f); // Add monkey into the scene. - app.monkeyMesh = MeshReader::loadMeshFromBuffer(engine, MONKEY_SUZANNE_DATA, nullptr, nullptr, mi); + app.monkeyMesh = MeshReader::loadMeshFromBuffer(engine, MONKEY_SUZANNE_DATA, MONKEY_SUZANNE_SIZE, nullptr, nullptr, mi); auto ti = tcm.getInstance(app.monkeyMesh.renderable); app.transform = mat4f{ mat3f(1), float3(0, 0, -4) } * tcm.getWorldTransform(ti); rcm.setCastShadows(rcm.getInstance(app.monkeyMesh.renderable), false); diff --git a/samples/suzanne.cpp b/samples/suzanne.cpp index cc24b9545b31..fa7ea28737cd 100644 --- a/samples/suzanne.cpp +++ b/samples/suzanne.cpp @@ -187,8 +187,8 @@ int main(int argc, char** argv) { ibl->setRotation(mat3f::rotation(0.5f, float3{ 0, 1, 0 })); // Add geometry into the scene. - app.mesh = filamesh::MeshReader::loadMeshFromBuffer(engine, MONKEY_SUZANNE_DATA, nullptr, - nullptr, app.materialInstance); + app.mesh = filamesh::MeshReader::loadMeshFromBuffer(engine, MONKEY_SUZANNE_DATA, + MONKEY_SUZANNE_SIZE, nullptr, nullptr, app.materialInstance); auto ti = tcm.getInstance(app.mesh.renderable); app.transform = mat4f{ mat3f(1), float3(0, 0, -4) } * tcm.getWorldTransform(ti); rcm.setCastShadows(rcm.getInstance(app.mesh.renderable), false); diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index 22c9518e3103..629207dc9d2a 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -1907,7 +1907,7 @@ class_("MeshReader") }; // Parse the filamesh buffer. This creates the VB, IB, and renderable. return MeshReader::loadMeshFromBuffer( - engine, buffer.bd->buffer, + engine, buffer.bd->buffer, buffer.bd->size, destructor, bundle, matreg); }), allow_raw_pointers());