CVE-2026-10201
Description
In Assimp up to 6.0.4, a division-by-zero bug in FBXExporter::WriteObjects() causes a crash when a UV channel has zero components.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
In Assimp up to 6.0.4, a division-by-zero bug in FBXExporter::WriteObjects() causes a crash when a UV channel has zero components.
Vulnerability
The vulnerability resides in the FBX exporter component of Assimp versions up to 6.0.4. In FBXExporter.cpp, the function FBXExporter::WriteObjects() iterates over UV channels and computes an index by dividing the UV data size by the number of components (nc). If nc is zero (i.e., a UV channel exists but contains zero components), a division by zero occurs at line 1249, leading to a crash. The issue is triggered when exporting a mesh that includes a UV channel with mNumUVComponents[uvi] == 0. No special configuration is required beyond loading such a malformed model and triggering the export functionality [1] [2].
Exploitation
An attacker must have local access to the system and provide a specially crafted 3D model file that, when loaded and exported via Assimp's FBX exporter, causes the division by zero. The attack sequence involves: (1) delivering a model with a UV channel that has zero components, (2) the user (or automated process) loading the model into an application using Assimp, (3) triggering the FBX export path, which calls FBXExporter::WriteObjects(). The crash is immediate and does not require authentication or elevated privileges beyond the ability to load the malicious file. A proof-of-concept has been publicly disclosed [1].
Impact
The attacker can cause a denial of service via a floating-point exception (FPE) crash of the application using the Assimp library. The impact is limited to availability (loss of service) as no code execution or privilege escalation is described. The crash occurs at the application level, potentially affecting any software that incorporates the vulnerable version of Assimp [1].
Mitigation
A patch has been developed and is advised to resolve the issue. Users should update to a fixed version of Assimp beyond 6.0.4 once available. As a workaround, avoid processing untrusted or externally-sourced 3D models through the FBX exporter. The vulnerability is not listed in CISA's Known Exploited Vulnerabilities (KEV) catalog [1] [2].
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
1Patches
2970f8691dd5eFBXExporter: Fix stack-use-after-scope in WriteObjects (#6472)
1 file changed · +2 −2
code/AssetLib/FBX/FBXExporter.cpp+2 −2 modified@@ -1766,8 +1766,8 @@ void FBXExporter::WriteObjects () { // can't easily determine which texture path will be correct, // so just store what we have in every field. // these being incorrect is a common problem with FBX anyway. - tnode.AddChild("FileName", tp_elem->second); - tnode.AddChild("RelativeFilename", tp_elem->second); + tnode.AddChild("FileName", tfile_path); + tnode.AddChild("RelativeFilename", tfile_path); tnode.AddChild("ModelUVTranslation", double(0.0), double(0.0)); tnode.AddChild("ModelUVScaling", double(1.0), double(1.0)); tnode.AddChild("Texture_Alpha_Source", "None");
392a658f9c27Bugfix/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 zero-check on `mNumUVComponents[uvi]` before using it as a divisor in `FBXExporter::WriteObjects()`."
Attack vector
An attacker provides a crafted FBX file that contains a UV channel with zero components (`mNumUVComponents[uvi] == 0`). When Assimp exports this scene via the FBX exporter, the loop over UV channels enters the body (because the channel exists) but `nc` is zero, leading to a division by zero at `FBXExporter.cpp:1249`. The attack requires local access to run the fuzzer or the exporter on the malicious file [ref_id=1].
Affected code
The vulnerability is in `FBXExporter::WriteObjects()` within `code/AssetLib/FBX/FBXExporter.cpp` at line 1249. The function divides by `mNumUVComponents[uvi]` without checking for zero, causing a floating-point exception (FPE) when a UV channel exists but has zero components. The patch files also touch `FBXDeformer.cpp`, `FBXMeshGeometry.cpp`, and related headers, but those changes address a separate blendshape-ordering regression, not the division-by-zero bug.
What the fix does
The provided patch [patch_id=4020131] does **not** address the division-by-zero bug in `FBXExporter::WriteObjects()`. Instead, it changes `std::unordered_set` containers to `std::vector` in the FBX blendshape code to fix a regression that scrambled blendshape order. The advisory [ref_id=1] notes that a proper fix would require adding a zero-check for `nc` before the division at line 1249, but no such check is included in this commit. The project has tagged the issue as a bug and recommends applying a patch to resolve it.
Preconditions
- inputThe attacker must supply a crafted FBX file with a UV channel whose component count is zero.
- configThe attacker must invoke the FBX exporter on the crafted file (e.g., via the assimp_fuzzer or the assimp command-line tool).
- networkThe attack is local; no network access is required.
Generated on Jun 1, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5News mentions
2- Assimp: Five Memory-Safety Bugs Disclosed in glTF and FBX ParsersVypr Intelligence · Jun 1, 2026
- Assimp: Five Memory-Safety Bugs Disclosed in glTF and FBX ParsersVypr Intelligence · Jun 1, 2026