CNA-BUG-131: The .cnj Model bones parser accepts forward or self parent indices and turns a malformed transform into identity
Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
ParseCnjBoneArrayEXT keeps a transform only when it has exactly 16 numbers and the bone loop checks only that a parent index is in range, so a bone can name a later bone (or itself) as parent and CopyAbsoluteBoneTransformsTo reads an uncomputed slot.
- Identifier
CNA-BUG-131- Category
- Bug
- Subsystem
- Models & glTF
- Status
- Open
- Verified against
- CNA
009d40f5(009d40f5dd085c4e674d3479675fac84b12b3e0a) - Severity
- Low (a triage suggestion, not a project priority)
- Evidence basis
- Source-verified: read at TARGET, not executed
- Tests touching this area
- Yes: see Current tests
- Affected contract
- ContentManager::Load<Model> on .cnj Model documents with a bones array; Model::CopyAbsoluteBoneTransformsTo
Expected behaviour
Model::CopyAbsoluteBoneTransformsTo in Model.cpp assumes each parent precedes its child (it composes with dest[parentIdx] in index order), as XNA's does; AnimationPlayer refuses a hierarchy that is not topologically ordered. A loader should refuse a bone list that breaks that order or carries a malformed transform.
Actual behaviour at TARGET
ParseCnjBoneArrayEXT in ContentManager.cpp sets a bone's transform only when the array has exactly 16 numbers, leaving identity otherwise, without an error. The Model reader then checks only 0 <= parent < boneCount before AddChild, so a parent index equal to or greater than the child's is accepted; a self-parent makes the bone its own child. Absolute transforms then use a parent slot that has not been computed yet (the thread-local draw buffer's stale value in Model::Draw).
Source locations
modules/content/src/Xna/ContentManager.cpp— ParseCnjBoneArrayEXT: transform kept only when it has 16 numbersmodules/content/src/Xna/ContentManager.cpp— ModelTypeReader bone loop: range check only, then ModelBone::AddChildmodules/graphics/src/Xna/Model.cpp— Model::CopyAbsoluteBoneTransformsTo: parent-before-child assumption
Evidence
Checked by reading at 009d40f5; not executed.
Focused reproduction
No focused reproduction is known. Nothing has been invented here; the evidence above is what exists.
Current tests
easygl_model_json_reader_bone_hierarchy_test.cpp and the .cnj tests load well-formed hierarchies; no test feeds a malformed bone list.
Regression test
Fixtures with a forward parent, a self parent and a 15-number transform, each expecting ContentLoadException.
Blast radius
.cnj Model documents written by hand or by other tools; gltf_to_cnj writes topologically ordered bones.
Workaround
No workaround is known.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- Known issues
- Bug index