CVE-2026-10200
Description
A heap-buffer-overflow in Assimp's glTFCommon::CopyValue allows local attackers to crash or leak memory via a crafted glTF file.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A heap-buffer-overflow in Assimp's glTFCommon::CopyValue allows local attackers to crash or leak memory via a crafted glTF file.
Vulnerability
A heap-buffer-overflow vulnerability exists in the glTFCommon::CopyValue function within the glTF2 importer of Assimp versions up to 6.0.4 [1][2]. The function is located in AssetLib/glTFCommon/glTFCommon.h at line 178 and is used to parse a 4x4 transformation matrix from a glTF file. When a malicious or corrupted glTF file provides a node transformation matrix with insufficient data length, the program allocates only 1 byte of heap memory but attempts to read 64 bytes (16 float values) without a boundary check, leading to an out-of-bounds read [1].
Exploitation
An attacker with local access can exploit this vulnerability by crafting a glTF file containing an invalid node transformation matrix with insufficient data length [1]. The attacker must then induce the target application to load the file using Assimp's glTF2 importer. No authentication or special privileges are required beyond the ability to provide the file. The exploit has been made public, and a proof-of-concept (PoC) is available in the referenced issue [1]. The PoC triggers the overflow when run with the assimp_fuzzer_gltf tool, as demonstrated by the AddressSanitizer report [1].
Impact
Successful exploitation results in a heap-buffer-overflow, causing an immediate crash (denial of service) and potentially allowing an attacker to read out-of-bounds heap memory, which could lead to information disclosure [1]. The AddressSanitizer report confirms a read of size 4 from an invalid heap address [1]. The impact is limited to local attacks, but the vulnerability could be used to leak sensitive data from the process memory.
Mitigation
As of the publication date, no official fix has been released for this vulnerability [1]. The issue has been reported to the Assimp project and tagged as a bug [1]. Users are advised to avoid loading untrusted glTF files with Assimp versions up to 6.0.4. Monitor the Assimp repository for updates and apply patches as soon as they become available [2]. No workaround is provided in the references.
AI Insight generated on Jun 1, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
1392a658f9c27Bugfix/sparky kitty studios (#6623)
5 files changed · +68 −60
code/AssetLib/FBX/FBXDeformer.cpp+10 −4 modified@@ -45,6 +45,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_FBX_IMPORTER +#include <algorithm> + #include "FBXParser.h" #include "FBXDocument.h" #include "FBXMeshGeometry.h" @@ -144,8 +146,10 @@ BlendShape::BlendShape(uint64_t id, const Element& element, const Document& doc, for (const Connection* con : conns) { const BlendShapeChannel* const bspc = ProcessSimpleConnection<BlendShapeChannel>(*con, false, "BlendShapeChannel -> BlendShape", element); if (bspc) { - auto pr = blendShapeChannels.insert(bspc); - if (!pr.second) { + // Only add a channel if it doesn't exist already + if (std::find(blendShapeChannels.begin(), blendShapeChannels.end(), bspc) == blendShapeChannels.end()) { + blendShapeChannels.push_back(bspc); + } else { FBXImporter::LogWarn("there is the same blendShapeChannel id ", bspc->ID()); } } @@ -170,8 +174,10 @@ BlendShapeChannel::BlendShapeChannel(uint64_t id, const Element& element, const for (const Connection* con : conns) { const ShapeGeometry* const sg = ProcessSimpleConnection<ShapeGeometry>(*con, false, "Shape -> BlendShapeChannel", element); if (sg) { - auto pr = shapeGeometries.insert(sg); - if (!pr.second) { + // Only add a geometry if it doesn't exist already + if (std::find(shapeGeometries.begin(), shapeGeometries.end(), sg) == shapeGeometries.end()) { + shapeGeometries.push_back(sg); + } else { FBXImporter::LogWarn("there is the same shapeGeometrie id ", sg->ID()); } }
code/AssetLib/FBX/FBXDocument.h+4 −4 modified@@ -865,14 +865,14 @@ class BlendShapeChannel final : public Deformer { return fullWeights; } - const std::unordered_set<const ShapeGeometry*>& GetShapeGeometries() const { + const std::vector<const ShapeGeometry*>& GetShapeGeometries() const { return shapeGeometries; } private: float percent; WeightArray fullWeights; - std::unordered_set<const ShapeGeometry*> shapeGeometries; + std::vector<const ShapeGeometry*> shapeGeometries; }; /** DOM class for BlendShape deformers */ @@ -882,12 +882,12 @@ class BlendShape final : public Deformer { virtual ~BlendShape() = default; - const std::unordered_set<const BlendShapeChannel*>& BlendShapeChannels() const { + const std::vector<const BlendShapeChannel*>& BlendShapeChannels() const { return blendShapeChannels; } private: - std::unordered_set<const BlendShapeChannel*> blendShapeChannels; + std::vector<const BlendShapeChannel*> blendShapeChannels; }; /** DOM class for skin deformer clusters (aka sub-deformers) */
code/AssetLib/FBX/FBXMeshGeometry.cpp+6 −3 modified@@ -45,6 +45,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_FBX_IMPORTER +#include <algorithm> #include <functional> #include "FBXMeshGeometry.h" @@ -69,16 +70,18 @@ Geometry::Geometry(uint64_t id, const Element& element, const std::string& name, } const BlendShape* const bsp = ProcessSimpleConnection<BlendShape>(*con, false, "BlendShape -> Geometry", element); if (bsp) { - auto pr = blendShapes.insert(bsp); - if (!pr.second) { + // Only add a blendshape if it doesn't exist already + if (std::find(blendShapes.begin(), blendShapes.end(), bsp) == blendShapes.end()) { + blendShapes.push_back(bsp); + } else { FBXImporter::LogWarn("there is the same blendShape id ", bsp->ID()); } } } } // ------------------------------------------------------------------------------------------------ -const std::unordered_set<const BlendShape*>& Geometry::GetBlendShapes() const { +const std::vector<const BlendShape*>& Geometry::GetBlendShapes() const { return blendShapes; }
code/AssetLib/FBX/FBXMeshGeometry.h+2 −2 modified@@ -72,11 +72,11 @@ class Geometry : public Object { /// @brief Get the BlendShape attached to this geometry or nullptr /// @return The blendshape arrays. - const std::unordered_set<const BlendShape*>& GetBlendShapes() const; + const std::vector<const BlendShape*>& GetBlendShapes() const; private: const Skin* skin; - std::unordered_set<const BlendShape*> blendShapes; + std::vector<const BlendShape*> blendShapes; };
code/PostProcessing/ImproveCacheLocality.cpp+46 −47 modified@@ -58,11 +58,55 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <stack> namespace Assimp { +namespace { + ai_real calculateInputACMR(aiMesh *pMesh, const aiFace *const pcEnd, + unsigned int configCacheDepth, unsigned int meshNum) { + ai_real fACMR = 0.0f; + unsigned int *piFIFOStack = new unsigned int[configCacheDepth]; + memset(piFIFOStack, 0xff, configCacheDepth * sizeof(unsigned int)); + unsigned int *piCur = piFIFOStack; + const unsigned int *const piCurEnd = piFIFOStack + configCacheDepth; + + // count the number of cache misses + unsigned int iCacheMisses = 0; + for (const aiFace *pcFace = pMesh->mFaces; pcFace != pcEnd; ++pcFace) { + for (unsigned int qq = 0; qq < 3; ++qq) { + bool bInCache = false; + for (unsigned int *pp = piFIFOStack; pp < piCurEnd; ++pp) { + if (*pp == pcFace->mIndices[qq]) { + // the vertex is in cache + bInCache = true; + break; + } + } + if (!bInCache) { + ++iCacheMisses; + if (piCurEnd == piCur) { + piCur = piFIFOStack; + } + *piCur++ = pcFace->mIndices[qq]; + } + } + } + delete[] piFIFOStack; + fACMR = (ai_real)iCacheMisses / pMesh->mNumFaces; + if (3.0 == fACMR) { + char szBuff[128]; // should be sufficiently large in every case + + // the JoinIdenticalVertices process has not been executed on this + // mesh, otherwise this value would normally be at least minimally + // smaller than 3.0 ... + ai_snprintf(szBuff, 128, "Mesh %u: Not suitable for vcache optimization", meshNum); + ASSIMP_LOG_WARN(szBuff); + return static_cast<ai_real>(0.f); + } + return fACMR; + } +} // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -ImproveCacheLocalityProcess::ImproveCacheLocalityProcess() : - mConfigCacheDepth(PP_ICL_PTCACHE_SIZE) { +ImproveCacheLocalityProcess::ImproveCacheLocalityProcess() : mConfigCacheDepth(PP_ICL_PTCACHE_SIZE) { // empty } @@ -107,51 +151,6 @@ void ImproveCacheLocalityProcess::Execute(aiScene *pScene) { } } -// ------------------------------------------------------------------------------------------------ -static ai_real calculateInputACMR(aiMesh *pMesh, const aiFace *const pcEnd, - unsigned int configCacheDepth, unsigned int meshNum) { - ai_real fACMR = 0.0f; - unsigned int *piFIFOStack = new unsigned int[configCacheDepth]; - memset(piFIFOStack, 0xff, configCacheDepth * sizeof(unsigned int)); - unsigned int *piCur = piFIFOStack; - const unsigned int *const piCurEnd = piFIFOStack + configCacheDepth; - - // count the number of cache misses - unsigned int iCacheMisses = 0; - for (const aiFace *pcFace = pMesh->mFaces; pcFace != pcEnd; ++pcFace) { - for (unsigned int qq = 0; qq < 3; ++qq) { - bool bInCache = false; - for (unsigned int *pp = piFIFOStack; pp < piCurEnd; ++pp) { - if (*pp == pcFace->mIndices[qq]) { - // the vertex is in cache - bInCache = true; - break; - } - } - if (!bInCache) { - ++iCacheMisses; - if (piCurEnd == piCur) { - piCur = piFIFOStack; - } - *piCur++ = pcFace->mIndices[qq]; - } - } - } - delete[] piFIFOStack; - fACMR = (ai_real)iCacheMisses / pMesh->mNumFaces; - if (3.0 == fACMR) { - char szBuff[128]; // should be sufficiently large in every case - - // the JoinIdenticalVertices process has not been executed on this - // mesh, otherwise this value would normally be at least minimally - // smaller than 3.0 ... - ai_snprintf(szBuff, 128, "Mesh %u: Not suitable for vcache optimization", meshNum); - ASSIMP_LOG_WARN(szBuff); - return static_cast<ai_real>(0.f); - } - return fACMR; -} - // ------------------------------------------------------------------------------------------------ // Improves the cache coherency of a specific mesh ai_real ImproveCacheLocalityProcess::ProcessMesh(aiMesh *pMesh, unsigned int meshNum) {
Vulnerability mechanics
Root cause
"Missing bounds check in glTFCommon::CopyValue when reading a 4x4 matrix from an undersized accessor buffer leads to heap-buffer-overflow."
Attack vector
An attacker provides a malicious glTF file where a node's transformation matrix accessor declares a 4x4 matrix (16 floats, 64 bytes) but the underlying buffer data is far smaller (e.g., only 1 byte). The glTF2 importer allocates heap memory matching the declared size but the actual data buffer is undersized; `CopyValue` then reads 64 bytes from that small allocation, causing a heap-buffer-overflow [ref_id=1]. The attack requires only the ability to supply a crafted glTF file to the library (local file or network upload).
Affected code
The vulnerability resides in `glTFCommon::CopyValue` in `code/AssetLib/glTFCommon/glTFCommon.h` at line 178, called from `glTF2Importer::ImportNode` in `glTF2Importer.cpp`. The glTF2 importer's node transformation matrix parser lacks a bounds check when reading a 4x4 matrix from an undersized accessor buffer.
What the fix does
The provided patch [patch_id=4018975] only modifies FBX deformer and mesh geometry code (changing `std::unordered_set` to `std::vector` and adding duplicate checks via `std::find`). It does **not** touch `glTFCommon::CopyValue` or the glTF2 importer. Therefore the heap-buffer-overflow in the glTF 4x4 matrix parser remains unaddressed by this patch. The advisory [ref_id=1] does not include a fix for the glTF issue; the project tagged it as a bug but no corrective code is present in this bundle.
Preconditions
- inputAttacker must supply a glTF file with a node transformation matrix accessor whose declared data size is 16 floats but whose actual buffer content is smaller than 64 bytes.
- configThe library must be built with the glTF2 importer enabled (default).
Reproduction
Extract the PoC from the zip archive referenced in [ref_id=1] and run: ``` ./assimp_fuzzer_gltf ./CopyValue-heapof ``` This triggers the heap-buffer-overflow at `glTFCommon.h:178`.
Generated on Jun 1, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5News mentions
0No linked articles in our index yet.