Merge pull request #4160 from RichardTea/fix_gltf_warning_4126

Check and limit maximum size of glTF
pull/4159/head^2
Kim Kulling 2021-11-11 21:30:07 +01:00 committed by GitHub
commit 1f8edd5959
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 124 additions and 107 deletions

View File

@ -39,8 +39,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------- ----------------------------------------------------------------------
*/ */
#include <assimp/StringUtils.h>
#include <assimp/MemoryIOWrapper.h> #include <assimp/MemoryIOWrapper.h>
#include <assimp/StringUtils.h>
#include <iomanip> #include <iomanip>
// Header files, Assimp // Header files, Assimp
@ -57,11 +57,10 @@ using namespace glTFCommon;
namespace glTF { namespace glTF {
#if _MSC_VER #if _MSC_VER
# pragma warning(push) #pragma warning(push)
# pragma warning(disable : 4706) #pragma warning(disable : 4706)
#endif // _MSC_VER #endif // _MSC_VER
// //
// LazyDict methods // LazyDict methods
// //
@ -214,9 +213,10 @@ inline void Buffer::Read(Value &obj, Asset &r) {
} else { // Local file } else { // Local file
if (byteLength > 0) { if (byteLength > 0) {
std::string dir = !r.mCurrentAssetDir.empty() ? ( std::string dir = !r.mCurrentAssetDir.empty() ? (
r.mCurrentAssetDir.back() == '/' ? r.mCurrentAssetDir.back() == '/' ?
r.mCurrentAssetDir : r.mCurrentAssetDir + '/' r.mCurrentAssetDir :
) : ""; r.mCurrentAssetDir + '/') :
"";
IOStream *file = r.OpenFile(dir + uri, "rb"); IOStream *file = r.OpenFile(dir + uri, "rb");
if (file) { if (file) {
@ -734,8 +734,8 @@ inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) {
ASSIMP_LOG_INFO("GLTF: Decompressing Open3DGC data."); ASSIMP_LOG_INFO("GLTF: Decompressing Open3DGC data.");
/************** Read data from JSON-document **************/ /************** Read data from JSON-document **************/
#define MESH_READ_COMPRESSEDDATA_MEMBER(pFieldName, pOut) \ #define MESH_READ_COMPRESSEDDATA_MEMBER(pFieldName, pOut) \
if (!ReadMember(*comp_data, pFieldName, pOut)) { \ if (!ReadMember(*comp_data, pFieldName, pOut)) { \
throw DeadlyImportError("GLTF: \"compressedData\" must has \"", pFieldName, "\"."); \ throw DeadlyImportError("GLTF: \"compressedData\" must has \"", pFieldName, "\"."); \
} }
@ -771,8 +771,7 @@ inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) {
Decode_O3DGC(*ext_o3dgc, pAsset_Root); Decode_O3DGC(*ext_o3dgc, pAsset_Root);
Extension.push_back(ext_o3dgc); // store info in mesh extensions list. Extension.push_back(ext_o3dgc); // store info in mesh extensions list.
} // if(it_memb->name.GetString() == "Open3DGC-compression") } // if(it_memb->name.GetString() == "Open3DGC-compression")
else else {
{
throw DeadlyImportError("GLTF: Unknown mesh extension: \"", it_memb->name.GetString(), "\"."); throw DeadlyImportError("GLTF: Unknown mesh extension: \"", it_memb->name.GetString(), "\".");
} }
} // for(Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); json_extensions++) } // for(Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); json_extensions++)
@ -842,21 +841,21 @@ inline void Mesh::Decode_O3DGC(const SCompression_Open3DGC &pCompression_Open3DG
size_t tval = ifs.GetNFloatAttribute(static_cast<unsigned long>(idx)); size_t tval = ifs.GetNFloatAttribute(static_cast<unsigned long>(idx));
switch (ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))) { switch (ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))) {
case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD: case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD:
// Check situation when encoded data contain texture coordinates but primitive not. // Check situation when encoded data contain texture coordinates but primitive not.
if (idx_texcoord < primitives[0].attributes.texcoord.size()) { if (idx_texcoord < primitives[0].attributes.texcoord.size()) {
if (primitives[0].attributes.texcoord[idx]->count != tval) if (primitives[0].attributes.texcoord[idx]->count != tval)
throw DeadlyImportError("GLTF: Open3DGC. Compressed texture coordinates count (", ai_to_string(tval), throw DeadlyImportError("GLTF: Open3DGC. Compressed texture coordinates count (", ai_to_string(tval),
") not equal to uncompressed (", ai_to_string(primitives[0].attributes.texcoord[idx]->count), ")."); ") not equal to uncompressed (", ai_to_string(primitives[0].attributes.texcoord[idx]->count), ").");
idx_texcoord++; idx_texcoord++;
} else { } else {
ifs.SetNFloatAttribute(static_cast<unsigned long>(idx), 0ul); // Disable decoding this attribute. ifs.SetNFloatAttribute(static_cast<unsigned long>(idx), 0ul); // Disable decoding this attribute.
} }
break; break;
default: default:
throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: ", ai_to_string(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx)))); throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: ", ai_to_string(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))));
} }
tval *= ifs.GetFloatAttributeDim(static_cast<unsigned long>(idx)) * sizeof(o3dgc::Real); // After checking count of objects we can get size of array. tval *= ifs.GetFloatAttributeDim(static_cast<unsigned long>(idx)) * sizeof(o3dgc::Real); // After checking count of objects we can get size of array.
@ -868,14 +867,14 @@ inline void Mesh::Decode_O3DGC(const SCompression_Open3DGC &pCompression_Open3DG
// size = number_of_elements * components_per_element * size_of_component. See float attributes note. // size = number_of_elements * components_per_element * size_of_component. See float attributes note.
size_t tval = ifs.GetNIntAttribute(static_cast<unsigned long>(idx)); size_t tval = ifs.GetNIntAttribute(static_cast<unsigned long>(idx));
switch (ifs.GetIntAttributeType(static_cast<unsigned long>(idx))) { switch (ifs.GetIntAttributeType(static_cast<unsigned long>(idx))) {
case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN: case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN:
case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX: case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX:
case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID: case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID:
case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID: case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID:
break; break;
default: default:
throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: ", ai_to_string(ifs.GetIntAttributeType(static_cast<unsigned long>(idx)))); throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: ", ai_to_string(ifs.GetIntAttributeType(static_cast<unsigned long>(idx))));
} }
tval *= ifs.GetIntAttributeDim(static_cast<unsigned long>(idx)) * sizeof(long); // See float attributes note. tval *= ifs.GetIntAttributeDim(static_cast<unsigned long>(idx)) * sizeof(long); // See float attributes note.
@ -901,30 +900,30 @@ inline void Mesh::Decode_O3DGC(const SCompression_Open3DGC &pCompression_Open3DG
for (size_t idx = 0, idx_end = size_floatattr.size(), idx_texcoord = 0; idx < idx_end; idx++) { for (size_t idx = 0, idx_end = size_floatattr.size(), idx_texcoord = 0; idx < idx_end; idx++) {
switch (ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))) { switch (ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))) {
case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD: case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD:
if (idx_texcoord < primitives[0].attributes.texcoord.size()) { if (idx_texcoord < primitives[0].attributes.texcoord.size()) {
// See above about absent attributes. // See above about absent attributes.
ifs.SetFloatAttribute(static_cast<unsigned long>(idx), (o3dgc::Real *const)(decoded_data + get_buf_offset(primitives[0].attributes.texcoord[idx]))); ifs.SetFloatAttribute(static_cast<unsigned long>(idx), (o3dgc::Real *const)(decoded_data + get_buf_offset(primitives[0].attributes.texcoord[idx])));
idx_texcoord++; idx_texcoord++;
} }
break; break;
default: default:
throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: ", ai_to_string(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx)))); throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: ", ai_to_string(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))));
} }
} }
for (size_t idx = 0, idx_end = size_intattr.size(); idx < idx_end; idx++) { for (size_t idx = 0, idx_end = size_intattr.size(); idx < idx_end; idx++) {
switch (ifs.GetIntAttributeType(static_cast<unsigned int>(idx))) { switch (ifs.GetIntAttributeType(static_cast<unsigned int>(idx))) {
case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN: case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN:
case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX: case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX:
case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID: case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID:
case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID: case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID:
break; break;
// ifs.SetIntAttribute(idx, (long* const)(decoded_data + get_buf_offset(primitives[0].attributes.joint))); // ifs.SetIntAttribute(idx, (long* const)(decoded_data + get_buf_offset(primitives[0].attributes.joint)));
default: default:
throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: ", ai_to_string(ifs.GetIntAttributeType(static_cast<unsigned long>(idx)))); throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: ", ai_to_string(ifs.GetIntAttributeType(static_cast<unsigned long>(idx))));
} }
} }
@ -1148,7 +1147,8 @@ inline void Asset::ReadBinaryHeader(IOStream &stream) {
AI_SWAP4(header.length); AI_SWAP4(header.length);
AI_SWAP4(header.sceneLength); AI_SWAP4(header.sceneLength);
mSceneLength = static_cast<size_t>(header.sceneLength); static_assert(std::numeric_limits<uint32_t>::max() <= std::numeric_limits<size_t>::max(), "size_t must be at least 32bits");
mSceneLength = static_cast<size_t>(header.sceneLength); // Can't be larger than 4GB (max. uint32_t)
mBodyOffset = sizeof(header) + mSceneLength; mBodyOffset = sizeof(header) + mSceneLength;
mBodyOffset = (mBodyOffset + 3) & ~3; // Round up to next multiple of 4 mBodyOffset = (mBodyOffset + 3) & ~3; // Round up to next multiple of 4
@ -1179,8 +1179,17 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) {
mBodyLength = 0; mBodyLength = 0;
} }
// read the scene data // Smallest legal JSON file is "{}" Smallest loadable glTF file is larger than that but catch it later
if (mSceneLength < 2) {
throw DeadlyImportError("GLTF: No JSON file contents");
}
// Binary format only supports up to 4GB of JSON so limit it there to avoid extreme memory allocation
if (mSceneLength >= std::numeric_limits<uint32_t>::max()) {
throw DeadlyImportError("GLTF: JSON size greater than 4GB");
}
// read the scene data, ensure null termination
std::vector<char> sceneData(mSceneLength + 1); std::vector<char> sceneData(mSceneLength + 1);
sceneData[mSceneLength] = '\0'; sceneData[mSceneLength] = '\0';
@ -1258,7 +1267,7 @@ inline void Asset::ReadExtensionsUsed(Document &doc) {
#undef CHECK_EXT #undef CHECK_EXT
} }
inline IOStream *Asset::OpenFile(const std::string& path, const char *mode, bool absolute) { inline IOStream *Asset::OpenFile(const std::string &path, const char *mode, bool absolute) {
#ifdef ASSIMP_API #ifdef ASSIMP_API
(void)absolute; (void)absolute;
return mIOSystem->Open(path, mode); return mIOSystem->Open(path, mode);
@ -1300,7 +1309,7 @@ inline std::string Asset::FindUniqueID(const std::string &str, const char *suffi
} }
#if _MSC_VER #if _MSC_VER
# pragma warning(pop) #pragma warning(pop)
#endif // _MSC_VER #endif // _MSC_VER
} // namespace glTF } // namespace glTF

View File

@ -197,18 +197,18 @@ inline void SetDecodedIndexBuffer_Draco(const draco::Mesh &dracoMesh, Mesh::Prim
// Not same size, convert // Not same size, convert
switch (componentBytes) { switch (componentBytes) {
case sizeof(uint32_t): case sizeof(uint32_t):
CopyFaceIndex_Draco<uint32_t>(*decodedIndexBuffer, dracoMesh); CopyFaceIndex_Draco<uint32_t>(*decodedIndexBuffer, dracoMesh);
break; break;
case sizeof(uint16_t): case sizeof(uint16_t):
CopyFaceIndex_Draco<uint16_t>(*decodedIndexBuffer, dracoMesh); CopyFaceIndex_Draco<uint16_t>(*decodedIndexBuffer, dracoMesh);
break; break;
case sizeof(uint8_t): case sizeof(uint8_t):
CopyFaceIndex_Draco<uint8_t>(*decodedIndexBuffer, dracoMesh); CopyFaceIndex_Draco<uint8_t>(*decodedIndexBuffer, dracoMesh);
break; break;
default: default:
ai_assert(false); ai_assert(false);
break; break;
} }
// Assign this alternate data buffer to the accessor // Assign this alternate data buffer to the accessor
@ -247,27 +247,27 @@ inline void SetDecodedAttributeBuffer_Draco(const draco::Mesh &dracoMesh, uint32
decodedAttribBuffer->Grow(dracoMesh.num_points() * pDracoAttribute->num_components() * componentBytes); decodedAttribBuffer->Grow(dracoMesh.num_points() * pDracoAttribute->num_components() * componentBytes);
switch (accessor.componentType) { switch (accessor.componentType) {
case ComponentType_BYTE: case ComponentType_BYTE:
GetAttributeForAllPoints_Draco<int8_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); GetAttributeForAllPoints_Draco<int8_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer);
break; break;
case ComponentType_UNSIGNED_BYTE: case ComponentType_UNSIGNED_BYTE:
GetAttributeForAllPoints_Draco<uint8_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); GetAttributeForAllPoints_Draco<uint8_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer);
break; break;
case ComponentType_SHORT: case ComponentType_SHORT:
GetAttributeForAllPoints_Draco<int16_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); GetAttributeForAllPoints_Draco<int16_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer);
break; break;
case ComponentType_UNSIGNED_SHORT: case ComponentType_UNSIGNED_SHORT:
GetAttributeForAllPoints_Draco<uint16_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); GetAttributeForAllPoints_Draco<uint16_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer);
break; break;
case ComponentType_UNSIGNED_INT: case ComponentType_UNSIGNED_INT:
GetAttributeForAllPoints_Draco<uint32_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); GetAttributeForAllPoints_Draco<uint32_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer);
break; break;
case ComponentType_FLOAT: case ComponentType_FLOAT:
GetAttributeForAllPoints_Draco<float>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); GetAttributeForAllPoints_Draco<float>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer);
break; break;
default: default:
ai_assert(false); ai_assert(false);
break; break;
} }
// Assign this alternate data buffer to the accessor // Assign this alternate data buffer to the accessor
@ -299,7 +299,7 @@ inline LazyDict<T>::~LazyDict() {
template <class T> template <class T>
inline void LazyDict<T>::AttachToDocument(Document &doc) { inline void LazyDict<T>::AttachToDocument(Document &doc) {
Value *container = nullptr; Value *container = nullptr;
const char* context = nullptr; const char *context = nullptr;
if (mExtId) { if (mExtId) {
if (Value *exts = FindObject(doc, "extensions")) { if (Value *exts = FindObject(doc, "extensions")) {
@ -721,18 +721,18 @@ inline void Accessor::Sparse::PatchData(unsigned int elementSize) {
while (pIndices != indicesEnd) { while (pIndices != indicesEnd) {
size_t offset; size_t offset;
switch (indicesType) { switch (indicesType) {
case ComponentType_UNSIGNED_BYTE: case ComponentType_UNSIGNED_BYTE:
offset = *pIndices; offset = *pIndices;
break; break;
case ComponentType_UNSIGNED_SHORT: case ComponentType_UNSIGNED_SHORT:
offset = *reinterpret_cast<uint16_t *>(pIndices); offset = *reinterpret_cast<uint16_t *>(pIndices);
break; break;
case ComponentType_UNSIGNED_INT: case ComponentType_UNSIGNED_INT:
offset = *reinterpret_cast<uint32_t *>(pIndices); offset = *reinterpret_cast<uint32_t *>(pIndices);
break; break;
default: default:
// have fun with float and negative values from signed types as indices. // have fun with float and negative values from signed types as indices.
throw DeadlyImportError("Unsupported component type in index."); throw DeadlyImportError("Unsupported component type in index.");
} }
offset *= elementSize; offset *= elementSize;
@ -751,9 +751,8 @@ inline void Accessor::Read(Value &obj, Asset &r) {
byteOffset = MemberOrDefault(obj, "byteOffset", size_t(0)); byteOffset = MemberOrDefault(obj, "byteOffset", size_t(0));
componentType = MemberOrDefault(obj, "componentType", ComponentType_BYTE); componentType = MemberOrDefault(obj, "componentType", ComponentType_BYTE);
{ {
const Value* countValue = FindUInt(obj, "count"); const Value *countValue = FindUInt(obj, "count");
if (!countValue) if (!countValue) {
{
throw DeadlyImportError("A count value is required, when reading ", id.c_str(), name.empty() ? "" : " (" + name + ")"); throw DeadlyImportError("A count value is required, when reading ", id.c_str(), name.empty() ? "" : " (" + name + ")");
} }
count = countValue->GetUint(); count = countValue->GetUint();
@ -1233,7 +1232,7 @@ inline void Material::Read(Value &material, Asset &r) {
MaterialIOR ior; MaterialIOR ior;
ReadMember(*curMaterialIOR, "ior", ior.ior); ReadMember(*curMaterialIOR, "ior", ior.ior);
this->materialIOR = Nullable<MaterialIOR>(ior); this->materialIOR = Nullable<MaterialIOR>(ior);
} }
} }
@ -1777,9 +1776,9 @@ inline void Asset::ReadBinaryHeader(IOStream &stream, std::vector<char> &sceneDa
throw DeadlyImportError("GLTF: JSON chunk missing"); throw DeadlyImportError("GLTF: JSON chunk missing");
} }
// read the scene data // read the scene data, ensure null termination
static_assert(std::numeric_limits<uint32_t>::max() <= std::numeric_limits<size_t>::max(), "size_t must be at least 32bits");
mSceneLength = chunk.chunkLength; mSceneLength = chunk.chunkLength; // Can't be larger than 4GB (max. uint32_t)
sceneData.resize(mSceneLength + 1); sceneData.resize(mSceneLength + 1);
sceneData[mSceneLength] = '\0'; sceneData[mSceneLength] = '\0';
@ -1836,8 +1835,12 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) {
mSceneLength = stream->FileSize(); mSceneLength = stream->FileSize();
mBodyLength = 0; mBodyLength = 0;
// read the scene data // Binary format only supports up to 4GB of JSON, use that as a maximum
if (mSceneLength >= std::numeric_limits<uint32_t>::max()) {
throw DeadlyImportError("GLTF: JSON size greater than 4GB");
}
// read the scene data, ensure null termination
sceneData.resize(mSceneLength + 1); sceneData.resize(mSceneLength + 1);
sceneData[mSceneLength] = '\0'; sceneData[mSceneLength] = '\0';
@ -1846,6 +1849,11 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) {
} }
} }
// Smallest legal JSON file is "{}" Smallest loadable glTF file is larger than that but catch it later
if (mSceneLength < 2) {
throw DeadlyImportError("GLTF: No JSON file contents");
}
// parse the JSON document // parse the JSON document
ASSIMP_LOG_DEBUG("Parsing GLTF2 JSON"); ASSIMP_LOG_DEBUG("Parsing GLTF2 JSON");
Document doc; Document doc;
@ -1974,7 +1982,7 @@ inline void Asset::ReadExtensionsUsed(Document &doc) {
#undef CHECK_EXT #undef CHECK_EXT
} }
inline IOStream *Asset::OpenFile(const std::string& path, const char *mode, bool /*absolute*/) { inline IOStream *Asset::OpenFile(const std::string &path, const char *mode, bool /*absolute*/) {
#ifdef ASSIMP_API #ifdef ASSIMP_API
return mIOSystem->Open(path, mode); return mIOSystem->Open(path, mode);
#else #else