已合并
[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
已合并
共 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 @@ | |||
| 1 | Name: assimp | 1 | Name: assimp |
| 2 | Version: 6.0.5 | 2 | Version: 6.0.5 |
| 3 | -Release: 4 | 3 | +Release: 5 |
| 4 | Summary: Library to load and process various 3D model formats into applications. | 4 | Summary: Library to load and process various 3D model formats into applications. |
| 5 | License: BSD-3-Clause AND MIT AND BSL-1.0 AND Zlib | 5 | License: BSD-3-Clause AND MIT AND BSL-1.0 AND Zlib |
| 6 | URL: https://www.assimp.org/ | 6 | URL: https://www.assimp.org/ |
| @@ -26,6 +26,10 @@ Patch11: CVE-2025-70072.patch | |||
| 26 | Patch12: CVE-2026-10200.patch | 26 | Patch12: CVE-2026-10200.patch |
| 27 | Patch13: CVE-2026-10232.patch | 27 | Patch13: CVE-2026-10232.patch |
| 28 | Patch14: CVE-2026-14610.patch | 28 | Patch14: 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 | ||
| 30 | BuildRequires: gcc-c++ boost-devel cmake dos2unix irrlicht-devel irrXML-devel | 34 | BuildRequires: gcc-c++ boost-devel cmake dos2unix irrlicht-devel irrXML-devel |
| 31 | BuildRequires: doxygen poly2tri-devel gtest-devel pkgconfig(zziplib) | 35 | BuildRequires: 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}/pyassimp | 98 | %{python3_sitelib}/pyassimp |
| 95 | 99 | ||
| 96 | %changelog | 100 | %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-4 | 104 | * 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, |