VYPR
Medium severity5.3NVD Advisory· Published Jun 1, 2026

CVE-2026-10232

CVE-2026-10232

Description

A use-after-free vulnerability in Assimp's ASE file parser (up to 6.0.4) allows local attackers to cause a crash or potentially execute code via a malformed ASE file.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A use-after-free vulnerability in Assimp's ASE file parser (up to 6.0.4) allows local attackers to cause a crash or potentially execute code via a malformed ASE file.

Vulnerability

A heap-use-after-free vulnerability exists in Assimp versions up to 6.0.4 within the aiNode::~aiNode() destructor in code/Common/scene.cpp (line 150). The flaw is triggered when parsing a malformed ASE file that creates an invalid node tree, leading to a double-free or use-after-free during recursive node destruction. The issue is specific to the ASE file parser component. [1]

Exploitation

An attacker with local access can exploit this vulnerability by providing a specially crafted ASE file to an application using the Assimp library. The attack requires no special privileges beyond the ability to load the file. The provided proof-of-concept (PoC) demonstrates the crash using AddressSanitizer, showing a heap-use-after-free read of size 4 during node destruction. The exploit has been made public. [1]

Impact

Successful exploitation results in a heap-use-after-free condition, which can cause a denial of service (application crash) or potentially allow arbitrary code execution in the context of the process. The vulnerability is classified as medium severity (CVSS 5.3) and is considered a bug by the project. [1]

Mitigation

As of the publication date, no official fix has been released. The issue has been reported and acknowledged as a bug in the Assimp issue tracker. Users are advised to monitor the repository for updates and avoid processing untrusted ASE files until a patch is available. [1]

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

1

Patches

1
392a658f9c27

Bugfix/sparky kitty studios (#6623)

https://github.com/assimp/assimpKim KullingApr 30, 2026Fixed in 6.0.5via release-tag
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

"The ASE parser constructs an invalid aiNode tree where a child node is freed before its parent destructor finishes iterating over children, leading to a use-after-free in aiNode::~aiNode()."

Attack vector

An attacker provides a malformed ASE file that causes `ASEImporter::AddNodes` to build an `aiNode` tree with invalid parent-child relationships [ref_id=1]. When the `aiScene` is destroyed, the recursive `aiNode::~aiNode()` destructor reads `mNumChildren` and `mChildren` from a node that has already been freed, triggering a heap-use-after-free [CWE-416]. The attack requires only local access and the ability to supply a crafted `.ase` file to the Assimp library.

Affected code

The vulnerability resides in the ASE file parser (`code/AssetLib/ASE/ASELoader.cpp`) and the `aiNode::~aiNode()` destructor (`code/Common/scene.cpp`). During ASE parsing, an invalid node tree is constructed; when the scene is destroyed, the recursive destructor accesses already-freed `aiNode` objects, causing a use-after-free [ref_id=1].

What the fix does

The advisory does not include a patch diff. The project tagged the issue as a bug, indicating that a fix is expected to validate the node tree structure during ASE parsing so that no `aiNode` is freed while still referenced as a child of another node [ref_id=1]. Without such validation, the recursive destructor will continue to access freed memory.

Preconditions

  • inputAttacker must be able to supply a malformed ASE file to the Assimp library for parsing.
  • networkThe attack is local (requires local file access or local input to a consuming application).

Generated on Jun 1, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

1