已合并
[sync] PR-212: fix CVE-2025-15666, CVE-2026-14604, CVE-2026-19968, CVE-2026-19999 #221
openeuler-ci-bot创建于 9月29日
[sync] PR-212: fix CVE-2025-15666, CVE-2026-14604, CVE-2026-19968, CVE-2026-19999 #221
已合并
openeuler-ci-bot创建于 9月29日
共 5 个文件变更+848-1
@@ -0,0 +1,44 @@
1+From a4eff9f89585c590d7b43daf5065f7bdf398bbc6 Mon Sep 17 00:00:00 2001
2+From: Ilias <iliasaberkane6@gmail.com>
3+Date: Mon, 21 Sep 2026 21:38:42 +0200
4+Subject: [PATCH] MDL: reject zero-height textures with non-zero width (#6869)
5+ 
6+A texture with mHeight == 0 and mWidth > 0 was stored with pcData
7+allocated as a zero-element aiTexel array. The aiTexture contract
8+reserves mHeight == 0 for compressed blobs of mWidth bytes, so
9+SceneCombiner::Copy (e.g. during export) read mWidth bytes from the
10+empty allocation -> heap-buffer-overflow read.
11+ 
12+Reject such textures during import since a zero-height pixel texture
13+carries no image data anyway.
14+ 
15+Fixes #6079.
16+ 
17+Generated with [Devin](https://devin.ai)
18+ 
19+Co-authored-by: Ilias Aberkane <ilias.abkne@gmail.com>
20+Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
21+Co-authored-by: Kim Kulling <kimkulling@users.noreply.github.com>
22+---
23+ code/AssetLib/MDL/MDLMaterialLoader.cpp | 8 ++++++++
24+ 1 file changed, 8 insertions(+)
25+ 
26+diff --git a/code/AssetLib/MDL/MDLMaterialLoader.cpp b/code/AssetLib/MDL/MDLMaterialLoader.cpp
27+index fb3d9e4c38..ff707fa448 100644
28+--- a/code/AssetLib/MDL/MDLMaterialLoader.cpp
29++++ b/code/AssetLib/MDL/MDLMaterialLoader.cpp
30+@@ -257,6 +257,14 @@ void MDLImporter::ParseTextureColorData(const unsigned char *szData,
31+
32+ // allocate storage for the texture image
33+ if (do_read) {
34++ // a zero-height texture must be a compressed blob of mWidth bytes,
35++ // but this path produces an mWidth*mHeight pixel array - the
36++ // resulting object would violate the aiTexture contract and read
37++ // out of bounds when the scene is copied (e.g. on export)
38++ if (pcNew->mHeight == 0 && pcNew->mWidth != 0) {
39++ throw DeadlyImportError("Invalid MDL file. A texture has zero height.");
40++ }
41++
42+ // check for max texture sizes
43+ if (pcNew->mWidth > MaxTextureSize || pcNew->mHeight > MaxTextureSize) {
44+ throw DeadlyImportError("Invalid MDL file. A texture is too big.");
@@ -0,0 +1,304 @@
1+From bd301ee4d128cec5bb11e16b266f4f9bcb6a8d58 Mon Sep 17 00:00:00 2001
2+From: alb3e3 <74142887+Alb3e3@users.noreply.github.com>
3+Date: Wed, 29 Jul 2026 15:37:37 +0400
4+Subject: [PATCH] Fix double-free copying meshes with sparse or empty UV/color
5+ channels (fixes #6620) (#6751)
6+ 
7+* Add regression test for aliased mesh channels in SceneCombiner::Copy
8+ 
9+Copying a mesh with a populated UV/color channel that is either not the
10+first channel or belongs to a mesh reporting zero vertices must give the
11+copy its own buffers. On master the copy aliases the source arrays, so
12+destroying both double-frees them (assimp/assimp#6620). The test asserts
13+the pointers differ; it also trips AddressSanitizer without the fix.
14+ 
15+Assisted-by: Claude Code (Claude Opus 5)
16+ 
17+* Fix double-free when copying meshes with sparse or empty UV/color channels
18+ 
19+SceneCombiner::Copy(aiMesh) flat-copies the mesh and then reallocates
20+each array so the copy owns its own storage. UV and color channels were
21+reallocated with 'while (HasTextureCoords(n))' / 'while (HasVertexColors(n))'.
22+aiMesh::HasTextureCoords(n) returns false once mTextureCoords[n] is null
23+*or* mNumVertices == 0, so the loop stopped at the first empty channel and
24+skipped every channel of a zero-vertex mesh. Those channels kept the
25+flat-copied pointer into the source mesh, and because the destructor frees
26+every non-null channel of all AI_MAX_NUMBER_OF_* slots, they were freed
27+twice (assimp/assimp#6620).
28+ 
29+A PLY imported with aiProcess_JoinIdenticalVertices reaches exactly this
30+state (mNumVertices == 0 with mTextureCoords[0] still allocated), so
31+importing it and passing the scene to Exporter::ExportToBlob double-frees.
32+ 
33+Reallocate every populated UV and color channel unconditionally, matching
34+what the destructor frees.
35+ 
36+Assisted-by: Claude Code (Claude Opus 5)
37+ 
38+* Fix indentation and structure in SceneCombiner.cpp
39+ 
40+* Refactor logging and null checks in SceneCombiner
41+ 
42+Updated logging messages to use ASSIMP_LOG_ERROR for error cases and improved null pointer checks for prefix parameters.
43+ 
44+* Update SceneCombiner.cpp
45+ 
46+---------
47+ 
48+Co-authored-by: Kim Kulling <kimkulling@users.noreply.github.com>
49+---
50+ code/Common/SceneCombiner.cpp | 82 ++++++++++++++++++++---------------
51+ test/unit/utSceneCombiner.cpp | 51 ++++++++++++++++++++++
52+ 2 files changed, 99 insertions(+), 34 deletions(-)
53+ 
54+diff --git a/code/Common/SceneCombiner.cpp b/code/Common/SceneCombiner.cpp
55+index 0636c479b0..da45d7b560 100644
56+--- a/code/Common/SceneCombiner.cpp
57++++ b/code/Common/SceneCombiner.cpp
58+@@ -68,8 +68,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
59+ namespace Assimp {
60+
61+ #if (__GNUC__ >= 8 && __GNUC_MINOR__ >= 0)
62+-#pragma GCC diagnostic push
63+-#pragma GCC diagnostic ignored "-Wclass-memaccess"
64++# pragma GCC diagnostic push
65++# pragma GCC diagnostic ignored "-Wclass-memaccess"
66+ #endif
67+
68+ // ------------------------------------------------------------------------------------------------
69+@@ -80,8 +80,7 @@ inline void PrefixString(aiString &string, const char *prefix, unsigned int len)
70+ return;
71+
72+ if (len + string.length >= AI_MAXLEN - 1) {
73+- ASSIMP_LOG_VERBOSE_DEBUG("Can't add an unique prefix because the string is too long");
74+- ai_assert(false);
75++ ASSIMP_LOG_ERROR("Can't add an unique prefix because the string is too long");
76+ return;
77+ }
78+
79+@@ -116,7 +115,10 @@ void SceneCombiner::AddNodeHashes(aiNode *node, std::set<unsigned int> &hashes)
80+ // ------------------------------------------------------------------------------------------------
81+ // Add a name prefix to all nodes in a hierarchy
82+ void SceneCombiner::AddNodePrefixes(aiNode *node, const char *prefix, unsigned int len) {
83+- ai_assert(nullptr != prefix);
84++ if (prefix == nullptr) {
85++ ASSIMP_LOG_ERROR("Pointer to prefix is nullptr.");
86++ return;
87++ }
88+
89+ PrefixString(node->mName, prefix, len);
90+
91+@@ -144,7 +146,10 @@ bool SceneCombiner::FindNameMatch(const aiString &name, std::vector<SceneHelper>
92+ // Add a name prefix to all nodes in a hierarchy if a hash match is found
93+ void SceneCombiner::AddNodePrefixesChecked(aiNode *node, const char *prefix, unsigned int len,
94+ std::vector<SceneHelper> &input, unsigned int cur) {
95+- ai_assert(nullptr != prefix);
96++ if (prefix == nullptr) {
97++ ASSIMP_LOG_ERROR("Pointer to prefix is nullptr.");
98++ return;
99++ }
100+
101+ const unsigned int hash = SuperFastHash(node->mName.data, static_cast<uint32_t>(node->mName.length));
102+
103+@@ -165,8 +170,9 @@ void SceneCombiner::AddNodePrefixesChecked(aiNode *node, const char *prefix, uns
104+ // ------------------------------------------------------------------------------------------------
105+ // Add an offset to all mesh indices in a node graph
106+ void SceneCombiner::OffsetNodeMeshIndices(aiNode *node, unsigned int offset) {
107+- for (unsigned int i = 0; i < node->mNumMeshes; ++i)
108++ for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
109+ node->mMeshes[i] += offset;
110++ }
111+
112+ for (unsigned int i = 0; i < node->mNumChildren; ++i) {
113+ OffsetNodeMeshIndices(node->mChildren[i], offset);
114+@@ -177,6 +183,7 @@ void SceneCombiner::OffsetNodeMeshIndices(aiNode *node, unsigned int offset) {
115+ // Merges two scenes. Currently only used by the LWS loader.
116+ void SceneCombiner::MergeScenes(aiScene **_dest, std::vector<aiScene *> &src, unsigned int flags) {
117+ if (nullptr == _dest) {
118++ ASSIMP_LOG_ERROR("Pointer to destination scene is nullptr.");
119+ return;
120+ }
121+
122+@@ -211,7 +218,7 @@ void SceneCombiner::MergeScenes(aiScene **_dest, std::vector<aiScene *> &src, un
123+
124+ // ------------------------------------------------------------------------------------------------
125+ void SceneCombiner::AttachToGraph(aiNode *attach, std::vector<NodeAttachmentInfo> &srcList) {
126+- unsigned int cnt;
127++ unsigned int cnt{0};
128+ for (cnt = 0; cnt < attach->mNumChildren; ++cnt) {
129+ AttachToGraph(attach->mChildren[cnt], srcList);
130+ }
131+@@ -219,8 +226,9 @@ void SceneCombiner::AttachToGraph(aiNode *attach, std::vector<NodeAttachmentInfo
132+ cnt = 0;
133+ for (std::vector<NodeAttachmentInfo>::iterator it = srcList.begin();
134+ it != srcList.end(); ++it) {
135+- if ((*it).attachToNode == attach && !(*it).resolved)
136++ if ((*it).attachToNode == attach && !(*it).resolved) {
137+ ++cnt;
138++ }
139+ }
140+
141+ if (cnt) {
142+@@ -314,12 +322,6 @@ void SceneCombiner::MergeScenes(aiScene **_dest, aiScene *master, std::vector<At
143+
144+ // Generate unique names for all named stuff?
145+ if (flags & AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES) {
146+-#if 0
147+- // Construct a proper random number generator
148+- boost::mt19937 rng( );
149+- boost::uniform_int<> dist(1u,1 << 24u);
150+- boost::variate_generator<boost::mt19937&, boost::uniform_int<> > rndGen(rng, dist);
151+-#endif
152+ for (unsigned int i = 1; i < src.size(); ++i) {
153+ src[i].idlen = ai_snprintf(src[i].id, 32, "$%.6X$_", i);
154+
155+@@ -371,13 +373,14 @@ void SceneCombiner::MergeScenes(aiScene **_dest, aiScene *master, std::vector<At
156+ SceneHelper *cur = &src[n];
157+ for (unsigned int i = 0; i < (*cur)->mNumTextures; ++i) {
158+ if (n != duplicates[n]) {
159+- if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY)
160++ if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) {
161+ Copy(pip, (*cur)->mTextures[i]);
162+-
163+- else
164++ } else {
165+ continue;
166+- } else
167++ }
168++ } else {
169+ *pip = (*cur)->mTextures[i];
170++ }
171+ ++pip;
172+ }
173+
174+@@ -394,13 +397,14 @@ void SceneCombiner::MergeScenes(aiScene **_dest, aiScene *master, std::vector<At
175+ SceneHelper *cur = &src[n];
176+ for (unsigned int i = 0; i < (*cur)->mNumMaterials; ++i) {
177+ if (n != duplicates[n]) {
178+- if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY)
179++ if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) {
180+ Copy(pip, (*cur)->mMaterials[i]);
181+-
182+- else
183++ } else {
184+ continue;
185+- } else
186++ }
187++ } else {
188+ *pip = (*cur)->mMaterials[i];
189++ }
190+
191+ if ((*cur)->mNumTextures != dest->mNumTextures) {
192+ // We need to update all texture indices of the mesh. So we need to search for
193+@@ -457,16 +461,21 @@ void SceneCombiner::MergeScenes(aiScene **_dest, aiScene *master, std::vector<At
194+ SceneHelper *cur = &src[n];
195+ for (unsigned int i = 0; i < (*cur)->mNumMeshes; ++i) {
196+ if (n != duplicates[n]) {
197+- if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY)
198++ if (flags & AI_INT_MERGE_SCENE_DUPLICATES_DEEP_CPY) {
199+ Copy(pip, (*cur)->mMeshes[i]);
200+-
201+- else
202++ } else {
203+ continue;
204+- } else
205++ }
206++ } else {
207+ *pip = (*cur)->mMeshes[i];
208++ }
209+
210+ // update the material index of the mesh
211+- (*pip)->mMaterialIndex += offset[n];
212++ if ((*pip) != nullptr) {
213++ (*pip)->mMaterialIndex += offset[n];
214++ } else {
215++ ASSIMP_LOG_ERROR("CopyMeshes: Missing mesh instance found, skipped.");
216++ }
217+ ++pip;
218+ }
219+
220+@@ -1085,14 +1094,19 @@ void SceneCombiner::Copy(aiMesh **_dest, const aiMesh *src) {
221+ GetArrayCopy(dest->mTangents, dest->mNumVertices);
222+ GetArrayCopy(dest->mBitangents, dest->mNumVertices);
223+
224+- unsigned int n = 0;
225+- while (dest->HasTextureCoords(n)) {
226+- GetArrayCopy(dest->mTextureCoords[n++], dest->mNumVertices);
227++ // Reallocate every populated UV and color channel. The destructor frees
228++ // all AI_MAX_NUMBER_OF_* channels, so the copy must own an independent
229++ // buffer for each non-null one. Iterating with HasTextureCoords()/
230++ // HasVertexColors() would stop at the first empty channel (leaving any
231++ // later channel aliased with the source) and also skip channels of a mesh
232++ // with mNumVertices == 0, which then double-frees when both meshes are
233++ // destroyed.
234++ for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++n) {
235++ GetArrayCopy(dest->mTextureCoords[n], dest->mNumVertices);
236+ }
237+
238+- n = 0;
239+- while (dest->HasVertexColors(n)) {
240+- GetArrayCopy(dest->mColors[n++], dest->mNumVertices);
241++ for (unsigned int n = 0; n < AI_MAX_NUMBER_OF_COLOR_SETS; ++n) {
242++ GetArrayCopy(dest->mColors[n], dest->mNumVertices);
243+ }
244+
245+ // make a deep copy of all bones
246+diff --git a/test/unit/utSceneCombiner.cpp b/test/unit/utSceneCombiner.cpp
247+index 08c22b6e37..f6930ba977 100644
248+--- a/test/unit/utSceneCombiner.cpp
249++++ b/test/unit/utSceneCombiner.cpp
250+@@ -75,3 +75,54 @@ TEST_F(utSceneCombiner, CopySceneWithNullptr_AI_NO_EXCEPTion) {
251+ EXPECT_NO_THROW(SceneCombiner::CopyScene(nullptr, nullptr));
252+ EXPECT_NO_THROW(SceneCombiner::CopySceneFlat(nullptr, nullptr));
253+ }
254++
255++// Copying a mesh must give the copy its own UV and color buffers for every
256++// populated channel, even when the channel is not the first one or when the
257++// mesh reports zero vertices. Otherwise the copy aliases the source arrays and
258++// destroying both double-frees them (assimp/assimp#6620).
259++TEST_F(utSceneCombiner, CopyMeshDoesNotAliasSparseOrEmptyChannels) {
260++ auto makeMesh = []() {
261++ aiMesh *mesh = new aiMesh;
262++ // A populated UV channel that is not channel 0.
263++ mesh->mNumVertices = 2;
264++ mesh->mVertices = new aiVector3D[2];
265++ mesh->mTextureCoords[1] = new aiVector3D[2]{};
266++ mesh->mNumUVComponents[1] = 2;
267++ // A populated color channel that is not channel 0.
268++ mesh->mColors[1] = new aiColor4D[2]{};
269++ return mesh;
270++ };
271++
272++ // Non-contiguous channels: HasTextureCoords(0) is false, so the old loop
273++ // stopped before copying channel 1.
274++ {
275++ aiMesh *src = makeMesh();
276++ aiMesh *dst = nullptr;
277++ SceneCombiner::Copy(&dst, src);
278++ ASSERT_NE(dst, nullptr);
279++ EXPECT_NE(dst->mTextureCoords[1], src->mTextureCoords[1]);
280++ EXPECT_NE(dst->mColors[1], src->mColors[1]);
281++ delete dst;
282++ delete src;
283++ }
284++
285++ // A populated channel on a zero-vertex mesh: HasTextureCoords() requires
286++ // mNumVertices > 0, so the old loop skipped it.
287++ {
288++ aiMesh *src = makeMesh();
289++ src->mNumVertices = 0;
290++ delete[] src->mVertices;
291++ src->mVertices = nullptr;
292++ // Move the populated channels to index 0 to exercise the mNumVertices
293++ // guard specifically.
294++ std::swap(src->mTextureCoords[0], src->mTextureCoords[1]);
295++ std::swap(src->mColors[0], src->mColors[1]);
296++ aiMesh *dst = nullptr;
297++ SceneCombiner::Copy(&dst, src);
298++ ASSERT_NE(dst, nullptr);
299++ EXPECT_NE(dst->mTextureCoords[0], src->mTextureCoords[0]);
300++ EXPECT_NE(dst->mColors[0], src->mColors[0]);
301++ delete dst;
302++ delete src;
303++ }
304++}
@@ -0,0 +1,458 @@
1+From c39d8c15dbbe03174af61d8eedbbf90120f4eb9f Mon Sep 17 00:00:00 2001
2+From: Alb3e3 <74142887+Alb3e3@users.noreply.github.com>
3+Date: Sun, 28 Jun 2026 14:58:17 +0200
4+Subject: [PATCH 1/3] Fix heap-buffer-overflow in LWO2 variable-sized index
5+ reader
6+ 
7+ReadVSizedIntLWO2() advanced the file cursor by 2-4 bytes without ever
8+checking it against the end of the file buffer. A malformed LWO2 file
9+whose polygon index count runs past the end of the chunk makes the
10+reader walk off the heap allocation, producing an out-of-bounds read.
11+The unchecked helper is reached from all of its call sites, e.g.
12+CountVertsAndFacesLWO2() -> ReadVSizedIntLWO2().
13+ 
14+Guard every byte read against mFileBufferEnd and raise a
15+DeadlyImportError on truncated input. Well-formed files are unaffected;
16+malformed files now fail cleanly instead of reading out of bounds.
17+ 
18+Found with AddressSanitizer + libFuzzer.
19+ 
20+Assisted-by: Claude (Anthropic)
21+---
22+ code/AssetLib/LWO/LWOLoader.h | 30 ++++++++++++++++--------------
23+ 1 file changed, 16 insertions(+), 14 deletions(-)
24+ 
25+diff --git a/code/AssetLib/LWO/LWOLoader.h b/code/AssetLib/LWO/LWOLoader.h
26+index ac6f2aa44..dd2d1dc67 100644
27+--- a/code/AssetLib/LWO/LWOLoader.h
28++++ b/code/AssetLib/LWO/LWOLoader.h
29+@@ -46,6 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
31+ #include "LWOFileData.h"
32+ #include <assimp/BaseImporter.h>
33++#include <assimp/Exceptional.h>
34+ #include <assimp/material.h>
35+ #include <assimp/DefaultLogger.hpp>
36+
37+@@ -453,24 +454,25 @@ inline uint8_t LWOImporter::GetU1() {
38+
39+ // ------------------------------------------------------------------------------------------------
40+ inline int LWOImporter::ReadVSizedIntLWO2(uint8_t *&inout) {
41++ // A variable-sized index is 2 bytes long, or 4 bytes when the first byte
42++ // is 0xFF. Guard every byte read against the end of the file buffer so a
43++ // truncated or malformed chunk cannot trigger an out-of-bounds read.
44++ auto readByte = [&]() -> int {
45++ if (inout >= mFileBufferEnd) {
46++ throw DeadlyImportError("LWO2: Unexpected end of file while reading a variable-sized index");
47++ }
48++ return *inout++;
49++ };
50++
51+ int i;
52+- int c = *inout;
53+- inout++;
54++ int c = readByte();
55+ if (c != 0xFF) {
56+ i = c << 8;
57+- c = *inout;
58+- inout++;
59+- i |= c;
60++ i |= readByte();
61+ } else {
62+- c = *inout;
63+- inout++;
64+- i = c << 16;
65+- c = *inout;
66+- inout++;
67+- i |= c << 8;
68+- c = *inout;
69+- inout++;
70+- i |= c;
71++ i = readByte() << 16;
72++ i |= readByte() << 8;
73++ i |= readByte();
74+ }
75+ return i;
76+ }
77+ 
78+From 0f6bcfe7acd4c16bc198560db1be848757f953a8 Mon Sep 17 00:00:00 2001
79+From: Kim Kulling <kimkulling@users.noreply.github.com>
80+Date: Wed, 8 Jul 2026 21:23:39 +0200
81+Subject: [PATCH 2/3] Refactor comments in LWOLoader.h
82+ 
83+Removed unnecessary comments and added brief descriptions to function declarations.
84+---
85+ code/AssetLib/LWO/LWOLoader.h | 200 +++++++++++++---------------------
86+ 1 file changed, 73 insertions(+), 127 deletions(-)
87+ 
88+diff --git a/code/AssetLib/LWO/LWOLoader.h b/code/AssetLib/LWO/LWOLoader.h
89+index dd2d1dc67..8431edd28 100644
90+--- a/code/AssetLib/LWO/LWOLoader.h
91++++ b/code/AssetLib/LWO/LWOLoader.h
92+@@ -72,28 +72,10 @@ using namespace LWO;
93+ // ---------------------------------------------------------------------------
94+ class LWOImporter final : public BaseImporter {
95+ public:
96+- /**
97+- * @brief The class constructor.
98+- */
99+ LWOImporter() = default;
100+-
101+- /**
102+- * @brief The class destructor.
103+- */
104+ ~LWOImporter() override = default;
105+-
106+- // -------------------------------------------------------------------
107+- /** Returns whether the class can handle the format of the given file.
108+- * See BaseImporter::CanRead() for details.
109+- */
110+ bool CanRead(const std::string &pFile, IOSystem *pIOHandler,
111+ bool checkSig) const override;
112+-
113+- // -------------------------------------------------------------------
114+- /** Called prior to ReadFile().
115+- * The function is a request to the importer to update its configuration
116+- * basing on the Importer's configuration property list.
117+- */
118+ void SetupProperties(const Importer *pImp) override;
119+
120+ protected:
121+@@ -110,18 +92,15 @@ protected:
122+
123+ private:
124+ // -------------------------------------------------------------------
125+- /** Loads a LWO file in the older LWOB format (LW < 6)
126+- */
127++ /// Loads a LWO file in the older LWOB format (LW < 6)
128+ void LoadLWOBFile();
129+
130+ // -------------------------------------------------------------------
131+- /** Loads a LWO file in the newer LWO2 format (LW >= 6)
132+- */
133++ /// Loads a LWO file in the newer LWO2 format (LW >= 6)
134+ void LoadLWO2File();
135+
136+ // -------------------------------------------------------------------
137+- /** Parsing functions used for all file format versions
138+- */
139++ /// Parsing functions used for all file format versions
140+ void GetS0(std::string &out, unsigned int max);
141+ float GetF4();
142+ float GetF8();
143+@@ -131,39 +110,34 @@ private:
144+ uint8_t GetU1();
145+
146+ // -------------------------------------------------------------------
147+- /** Loads a surface chunk from an LWOB file
148+- * @param size Maximum size to be read, in bytes.
149+- */
150++ /// Loads a surface chunk from an LWOB file
151++ /// @param size Maximum size to be read, in bytes.
152+ void LoadLWOBSurface(unsigned int size);
153+
154+ // -------------------------------------------------------------------
155+- /** Loads a surface chunk from an LWO2 file
156+- * @param size Maximum size to be read, in bytes.
157+- */
158++ /// Loads a surface chunk from an LWO2 file
159++ /// @param size Maximum size to be read, in bytes.
160+ void LoadLWO2Surface(unsigned int size);
161+ void LoadLWO3Surface(unsigned int size);
162+
163+ // -------------------------------------------------------------------
164+- /** Loads a texture block from a LWO2 file.
165+- * @param size Maximum size to be read, in bytes.
166+- * @param head Header of the SUF.BLOK header
167+- */
168++ /// Loads a texture block from a LWO2 file.
169++ /// @param size Maximum size to be read, in bytes.
170++ /// @param head Header of the SUF.BLOK header
171+ void LoadLWO2TextureBlock(LE_NCONST IFF::SubChunkHeader *head,
172+ unsigned int size);
173+
174+ // -------------------------------------------------------------------
175+- /** Loads a shader block from a LWO2 file.
176+- * @param size Maximum size to be read, in bytes.
177+- * @param head Header of the SUF.BLOK header
178+- */
179++ /// Loads a shader block from a LWO2 file.
180++ /// @param size Maximum size to be read, in bytes.
181++ /// @param head Header of the SUF.BLOK header
182+ void LoadLWO2ShaderBlock(LE_NCONST IFF::SubChunkHeader *head,
183+ unsigned int size);
184+
185+ // -------------------------------------------------------------------
186+- /** Loads an image map from a LWO2 file
187+- * @param size Maximum size to be read, in bytes.
188+- * @param tex Texture object to be filled
189+- */
190++ /// Loads an image map from a LWO2 file
191++ /// @param size Maximum size to be read, in bytes.
192++ /// @param tex Texture object to be filled
193+ void LoadLWO2ImageMap(unsigned int size, LWO::Texture &tex);
194+ void LoadLWO2Gradient(unsigned int size, LWO::Texture &tex);
195+ void LoadLWO2Procedural(unsigned int size, LWO::Texture &tex);
196+@@ -172,48 +146,41 @@ private:
197+ void LoadLWO2TextureHeader(unsigned int size, LWO::Texture &tex);
198+
199+ // -------------------------------------------------------------------
200+- /** Loads the LWO tag list from the file
201+- * @param size Maximum size to be read, in bytes.
202+- */
203++ /// Loads the LWO tag list from the file
204++ /// @param size Maximum size to be read, in bytes.
205+ void LoadLWOTags(unsigned int size);
206+
207+ // -------------------------------------------------------------------
208+- /** Load polygons from a POLS chunk
209+- * @param length Size of the chunk
210+- */
211++ /// Load polygons from a POLS chunk
212++ /// @param length Size of the chunk
213+ void LoadLWO2Polygons(unsigned int length);
214+ void LoadLWOBPolygons(unsigned int length);
215+
216+ // -------------------------------------------------------------------
217+- /** Load polygon tags from a PTAG chunk
218+- * @param length Size of the chunk
219+- */
220++ /// Load polygon tags from a PTAG chunk
221++ /// @param length Size of the chunk
222+ void LoadLWO2PolygonTags(unsigned int length);
223+
224+ // -------------------------------------------------------------------
225+- /** Load a vertex map from a VMAP/VMAD chunk
226+- * @param length Size of the chunk
227+- * @param perPoly Operate on per-polygon base?
228+- */
229++ /// Load a vertex map from a VMAP/VMAD chunk
230++ /// @param length Size of the chunk
231++ /// @param perPoly Operate on per-polygon base?
232+ void LoadLWO2VertexMap(unsigned int length, bool perPoly);
233+
234+ // -------------------------------------------------------------------
235+- /** Load polygons from a PNTS chunk
236+- * @param length Size of the chunk
237+- */
238++ /// Load polygons from a PNTS chunk
239++ /// @param length Size of the chunk
240+ void LoadLWOPoints(unsigned int length);
241+
242+ // -------------------------------------------------------------------
243+- /** Load a clip from a CLIP chunk
244+- * @param length Size of the chunk
245+- */
246++ /// Load a clip from a CLIP chunk
247++ /// @param length Size of the chunk
248+ void LoadLWO2Clip(unsigned int length);
249+ void LoadLWO3Clip(unsigned int length);
250+
251+ // -------------------------------------------------------------------
252+- /** Load an envelope from an EVL chunk
253+- * @param length Size of the chunk
254+- */
255++ /// Load an envelope from an EVL chunk
256++ /// @param length Size of the chunk
257+ void LoadLWO2Envelope(unsigned int length);
258+ void LoadLWO3Envelope(unsigned int length);
259+
260+@@ -227,8 +194,7 @@ private:
261+ void LoadNodeData(unsigned int length);
262+
263+ // -------------------------------------------------------------------
264+- /** Count vertices and faces in a LWOB/LWO2 file
265+- */
266++ /// Count vertices and faces in a LWOB/LWO2 file
267+ void CountVertsAndFacesLWO2(unsigned int &verts,
268+ unsigned int &faces,
269+ uint16_t *&cursor,
270+@@ -242,8 +208,7 @@ private:
271+ unsigned int max = UINT_MAX);
272+
273+ // -------------------------------------------------------------------
274+- /** Read vertices and faces in a LWOB/LWO2 file
275+- */
276++ /// Read vertices and faces in a LWOB/LWO2 file
277+ void CopyFaceIndicesLWO2(LWO::FaceList::iterator &it,
278+ uint16_t *&cursor,
279+ const uint16_t *const end);
280+@@ -255,45 +220,36 @@ private:
281+ unsigned int max = UINT_MAX);
282+
283+ // -------------------------------------------------------------------
284+- /** Resolve the tag and surface lists that have been loaded.
285+- * Generates the mMapping table.
286+- */
287++ /// Resolve the tag and surface lists that have been loaded.
288++ /// Generates the mMapping table.
289+ void ResolveTags();
290+
291+ // -------------------------------------------------------------------
292+- /** Resolve the clip list that has been loaded.
293+- * Replaces clip references with real clips.
294+- */
295++ /// Resolve the clip list that has been loaded.
296++ /// Replaces clip references with real clips.
297+ void ResolveClips();
298+
299+ // -------------------------------------------------------------------
300+- /** Add a texture list to an output material description.
301+- *
302+- * @param pcMat Output material
303+- * @param in Input texture list
304+- * @param type Type identifier of the texture list
305+- */
306++ /// Add a texture list to an output material description.
307++ /// @param pcMat Output material
308++ /// @param in Input texture list
309++ /// @param type Type identifier of the texture list
310+ bool HandleTextures(aiMaterial *pcMat, const TextureList &in,
311+ aiTextureType type);
312+
313+ // -------------------------------------------------------------------
314+- /** Adjust a texture path
315+- */
316++ /// Adjust a texture path
317+ void AdjustTexturePath(std::string &out);
318+
319+ // -------------------------------------------------------------------
320+- /** Convert a LWO surface description to an ASSIMP material
321+- */
322++ /// Convert a LWO surface description to an ASSIMP material
323+ void ConvertMaterial(const LWO::Surface &surf, aiMaterial *pcMat);
324+
325+ // -------------------------------------------------------------------
326+- /** Get a list of all UV/VC channels required by a specific surface.
327+- *
328+- * @param surf Working surface
329+- * @param layer Working layer
330+- * @param out Output list. The members are indices into the
331+- * UV/VC channel lists of the layer
332+- */
333++ /// Get a list of all UV/VC channels required by a specific surface.
334++ /// @param surf Working surface
335++ /// @param layer Working layer
336++ /// @param out Output list. The members are indices into the UV/VC channel lists of the layer
337+ void FindUVChannels(/*const*/ LWO::Surface &surf,
338+ LWO::SortedRep &sorted,
339+ /*const*/ LWO::Layer &layer,
340+@@ -310,57 +266,48 @@ private:
341+ unsigned int out[AI_MAX_NUMBER_OF_COLOR_SETS]);
342+
343+ // -------------------------------------------------------------------
344+- /** Generate the final node graph
345+- * Unused nodes are deleted.
346+- * @param apcNodes Flat list of nodes
347+- */
348++ /// Generate the final node graph
349++ /// Unused nodes are deleted.
350++ /// @param apcNodes Flat list of nodes
351+ void GenerateNodeGraph(std::map<uint16_t, aiNode *> &apcNodes);
352+
353+ // -------------------------------------------------------------------
354+- /** Add children to a node
355+- * @param node Node to become a father
356+- * @param parent Index of the node
357+- * @param apcNodes Flat list of nodes - used nodes are set to nullptr.
358+- */
359+- void AddChildren(aiNode *node, uint16_t parent,
360+- std::vector<aiNode *> &apcNodes);
361++ /// Add children to a node
362++ /// @param node Node to become a father
363++ /// @param parent Index of the node
364++ /// @param apcNodes Flat list of nodes - used nodes are set to nullptr.
365++ void AddChildren(aiNode *node, uint16_t parent, std::vector<aiNode *> &apcNodes);
366+
367+ // -------------------------------------------------------------------
368+- /** Read a variable sized integer
369+- * @param inout Input and output buffer
370+- */
371++ /// Read a variable sized integer
372++ /// @param inout Input and output buffer
373+ int ReadVSizedIntLWO2(uint8_t *&inout);
374+
375+ // -------------------------------------------------------------------
376+- /** Assign a value from a VMAP to a vertex and all vertices
377+- * attached to it.
378+- * @param base VMAP destination data
379+- * @param numRead Number of float's to be read
380+- * @param idx Absolute index of the first vertex
381+- * @param data Value of the VMAP to be assigned - read numRead
382+- * floats from this array.
383++ /// Assign a value from a VMAP to a vertex and all vertices attached to it.
384++ /// @param base VMAP destination data
385++ /// @param numRead Number of float's to be read
386++ /// @param idx Absolute index of the first vertex
387++ /// @param data Value of the VMAP to be assigned - read numRead
388++ /// floats from this array.
389+ */
390+ void DoRecursiveVMAPAssignment(VMapEntry *base, unsigned int numRead,
391+ unsigned int idx, float *data);
392+
393+ // -------------------------------------------------------------------
394+- /** Compute normal vectors for a mesh
395+- * @param mesh Input mesh
396+- * @param smoothingGroups Smoothing-groups-per-face array
397+- * @param surface Surface for the mesh
398+- */
399++ /// Compute normal vectors for a mesh
400++ /// @param mesh Input mesh
401++ /// @param smoothingGroups Smoothing-groups-per-face array
402++ /// @param surface Surface for the mesh
403+ void ComputeNormals(aiMesh *mesh, const std::vector<unsigned int> &smoothingGroups,
404+ const LWO::Surface &surface);
405+
406+ // -------------------------------------------------------------------
407+- /** Setup a new texture after the corresponding chunk was
408+- * encountered in the file.
409+- * @param list Texture list
410+- * @param size Maximum number of bytes to be read
411+- * @return Pointer to new texture
412+- */
413+- LWO::Texture *SetupNewTextureLWOB(LWO::TextureList &list,
414+- unsigned int size);
415++ /// Setup a new texture after the corresponding chunk was encountered in the file.
416++ /// @param list Texture list
417++ /// @param size Maximum number of bytes to be read
418++ /// @return Pointer to new texture
419++ LWO::Texture *SetupNewTextureLWOB(LWO::TextureList &list, unsigned int size);
420+
421+ private:
422+ /// true if the file is a LWO2 file
423+@@ -465,8 +412,7 @@ inline int LWOImporter::ReadVSizedIntLWO2(uint8_t *&inout) {
424+ };
425+
426+ int i;
427+- int c = readByte();
428+- if (c != 0xFF) {
429++ if (int c = readByte(); c != 0xFF) {
430+ i = c << 8;
431+ i |= readByte();
432+ } else {
433+ 
434+From 924bb602e387e10ca7c64acaac83a26d6ad1d8c6 Mon Sep 17 00:00:00 2001
435+From: Alb3e3 <74142887+Alb3e3@users.noreply.github.com>
436+Date: Thu, 9 Jul 2026 13:03:57 +0200
437+Subject: [PATCH 3/3] LWO: remove stray comment terminator in LWOLoader.h
438+ 
439+The comment refactor left a dangling '*/' after the DoRecursiveVMAPAssignment
440+doc block was converted from /** ... */ to /// line comments. A bare '*/' in
441+code is a syntax error (expected unqualified-id before '/' token) and broke
442+the build on every platform. Remove the orphaned terminator.
443+---
444+ code/AssetLib/LWO/LWOLoader.h | 1 -
445+ 1 file changed, 1 deletion(-)
446+ 
447+diff --git a/code/AssetLib/LWO/LWOLoader.h b/code/AssetLib/LWO/LWOLoader.h
448+index 8431edd28..aaf205434 100644
449+--- a/code/AssetLib/LWO/LWOLoader.h
450++++ b/code/AssetLib/LWO/LWOLoader.h
451+@@ -290,7 +290,6 @@ private:
452+ /// @param idx Absolute index of the first vertex
453+ /// @param data Value of the VMAP to be assigned - read numRead
454+ /// floats from this array.
455+- */
456+ void DoRecursiveVMAPAssignment(VMapEntry *base, unsigned int numRead,
457+ unsigned int idx, float *data);
458+
@@ -0,0 +1,34 @@
1+From 50d767984e78d51b53e2020fdf0967fd624bc377 Mon Sep 17 00:00:00 2001
2+From: Scott Thomas <SThomasen7@gmail.com>
3+Date: Mon, 10 Aug 2026 06:33:03 -0600
4+Subject: [PATCH] Fix mdl buffer overflow issue 6633 (#6759)
5+ 
6+* validate frame size before accessing data
7+ 
8+* add security bot suggestions to calculate frame span
9+---
10+ code/AssetLib/MDL/MDLLoader.cpp | 6 +++++-
11+ 1 file changed, 5 insertions(+), 1 deletion(-)
12+ 
13+diff --git a/code/AssetLib/MDL/MDLLoader.cpp b/code/AssetLib/MDL/MDLLoader.cpp
14+index c339a5565b..6ed7d272d8 100644
15+--- a/code/AssetLib/MDL/MDLLoader.cpp
16++++ b/code/AssetLib/MDL/MDLLoader.cpp
17+@@ -1685,6 +1685,10 @@ void MDLImporter::ParseBoneTrafoKeys_3DGS_MDL7(
18+ // skip all frames vertices. We can't support them
19+ const MDL::BoneTransform_MDL7 *pcBoneTransforms = (const MDL::BoneTransform_MDL7 *)(((const char *)frame.pcFrame) + pcHeader->frame_stc_size +
20+ frame.pcFrame->vertices_count * pcHeader->framevertex_stc_size);
21++ // Confirm that the bone transformation matrices are within the file.
22++ const size_t boneTransformSpan = sizeof(MDL::BoneTransform_MDL7) +
23++ static_cast<size_t>(frame.pcFrame->transmatrix_count - 1u) * pcHeader->bonetrans_stc_size;
24++ VALIDATE_FILE_SIZE(reinterpret_cast<const char *>(pcBoneTransforms) + boneTransformSpan);
25+
26+ // read all transformation matrices
27+ for (unsigned int iTrafo = 0; iTrafo < frame.pcFrame->transmatrix_count; ++iTrafo) {
28+@@ -2017,4 +2021,4 @@ void MDLImporter::InternReadFile_HL2() {
29+ throw DeadlyImportError("HL2 MDLs are not implemented");
30+ }
31+
32+-#endif // !! ASSIMP_BUILD_NO_MDL_IMPORTER
33+ 
34++#endif // !! ASSIMP_BUILD_NO_MDL_IMPORTER
@@ -1,6 +1,6 @@
1Name: assimp1Name: assimp
2Version: 6.0.52Version: 6.0.5
3-Release: 43+Release: 5
4Summary: Library to load and process various 3D model formats into applications.4Summary: Library to load and process various 3D model formats into applications.
5License: BSD-3-Clause AND MIT AND BSL-1.0 AND Zlib5License: BSD-3-Clause AND MIT AND BSL-1.0 AND Zlib
6URL: https://www.assimp.org/6URL: https://www.assimp.org/
@@ -26,6 +26,10 @@ Patch11: CVE-2025-70072.patch
26Patch12: CVE-2026-10200.patch26Patch12: CVE-2026-10200.patch
27Patch13: CVE-2026-10232.patch27Patch13: CVE-2026-10232.patch
28Patch14: CVE-2026-14610.patch28Patch14: CVE-2026-14610.patch
29+Patch15: CVE-2025-15666.patch
30+Patch16: CVE-2026-14604.patch
31+Patch17: CVE-2026-19968.patch
32+Patch18: CVE-2026-19999.patch
29 33 
30BuildRequires: gcc-c++ boost-devel cmake dos2unix irrlicht-devel irrXML-devel34BuildRequires: gcc-c++ boost-devel cmake dos2unix irrlicht-devel irrXML-devel
31BuildRequires: doxygen poly2tri-devel gtest-devel pkgconfig(zziplib)35BuildRequires: doxygen poly2tri-devel gtest-devel pkgconfig(zziplib)
@@ -94,6 +98,9 @@ install -m 0644 port/PyAssimp/pyassimp/*.py %{buildroot}%{python3_sitelib}/pyass
94%{python3_sitelib}/pyassimp98%{python3_sitelib}/pyassimp
95 99 
96%changelog100%changelog
101+* Sun Sep 20 2026 Funda Wang <fundawang@yeah.net> - 6.0.5-5
102+- fix CVE-2025-15666, CVE-2026-14604, CVE-2026-19968, CVE-2026-19999
103+ 
97* Tue Jul 14 2026 yaoxin <1024769339@qq.com> - 6.0.5-4104* Tue Jul 14 2026 yaoxin <1024769339@qq.com> - 6.0.5-4
98- Fix some CVEs: CVE-2025-70067, CVE-2025-70069, CVE-2025-70070,105- Fix some CVEs: CVE-2025-70067, CVE-2025-70069, CVE-2025-70070,
99 CVE-2025-70071, CVE-2025-70072, CVE-2026-10200, CVE-2026-10232,106 CVE-2025-70071, CVE-2025-70072, CVE-2026-10200, CVE-2026-10232,