Skip to content

Harden model file loading against crashes - #7905

Open
dustinspace217 wants to merge 5 commits into
scp-fs2open:masterfrom
dustinspace217:model/harden-model-loading
Open

dustinspace217 wants to merge 5 commits into
scp-fs2open:masterfrom
dustinspace217:model/harden-model-loading

Conversation

@dustinspace217

@dustinspace217 dustinspace217 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

What?

This makes the model loader (modelread.cpp) more careful with model files that contain bad or unexpected data. Problems that could crash the game while loading a damaged model now produce a warning instead, or the model is rejected cleanly.

Why?

A damaged or hand-edited model file could crash the game during loading, or quietly read data from the wrong place. Valid models aren't affected: I checked every model in retail, MediaVPs 5.0.2 and the mods I have installed (7,966 POFs), and none of them hits the new checks except 12 mod models whose shield-mesh neighbor references now get reset (logged only, no dialog).

How?

  • Property values are copied with the size of the field they go into, and the readers for vector properties and keyword lookups stay inside the property text.
  • The glow bank nebula texture name is built in its own buffer, the same way glowpoint presets already do it.
  • Submodel and cross-section counts are limited to what the file can actually hold, and a model whose header counts a submodel the file never defines fails to load with a warning.
  • Live debris, bay paths, detail level names and shield meshes are checked before use. A shield mesh with a bad vertex reference is ignored; bad neighbor references are reset.

Testing?

  • Debug builds with AddressSanitizer on master and this branch, loading a set of hand-made broken models with gr.loadModel in SM1-01 (MediaVPs 5.0.2, no other mods). Master crashed or failed a check on each; this branch loads them all, or rejects the broken one cleanly, with no reports.
  • Regression: SM1-01 and loop2-2 on OpenGL and Vulkan, master vs branch. Screenshots match within run-to-run noise, and the debug logs show the same warnings.
  • Release and Debug builds, the unit tests, and CI's clang-tidy job in its own container.

Anything Else?

Longer property strings are handled by #7863.

I used Claude Code to help find and test this.

Model property values, vector properties, keyword lookups and the glow
nebula texture name are now read within the property text and the fields
they fill. Counts read from a model file are limited to what the file can
hold, and live debris, bay paths, detail level names and shield meshes are
checked before use. Malformed values now produce a warning; valid models
load as before.
@dustinspace217 dustinspace217 changed the title Harden model file loading against malformed data Harden model file loading against crashes Oct 18, 2026
A glow point bank whose parent submodel number is past the end of the
submodel list now warns and stops following a submodel. A $look_at or
$parent_submodel given as a number is now parsed with strtol and must name
an existing submodel (or be -1); anything else gets the same "unable to
match" warning as an unknown name. Valid models load as before.

Adds unit tests for resolve_submodel_index.
…rces

A chunk with a negative length now stops the chunk loop with a warning
instead of seeking back to the same chunk. Shield collision trees are
checked once at load (node sizes, child offsets, polygon numbers against
the shield mesh; old SLDC trees also before conversion), and a malformed
tree is dropped with a warning, so collisions fall back to checking every
shield triangle. Their sizes are limited to the bytes left in the file.
Virtual POF glowpoint and engine sources given by number must exist, as
weapon bank sources already must. Valid models load as before.
The load-time tree check now also rejects trees deeper than 256 levels
(mc_check_sldc recurses once per level; installed trees are at most 16
deep) and trees that reach the same node twice, and accepts an empty
36-byte leaf. A tree whose bytes come back short from the file is dropped.
A chunk length that would overflow the next chunk position stops the chunk
loop like a negative one, and a virtual POF engine lookup by subsystem name
checks for an empty list first. Note: a model with a zero-size tree used to
ignore shield hits entirely; it now falls back to checking every shield
triangle. Adds unit tests for both tree checks.
The load-time tree check now marks every byte a node covers and rejects
overlapping nodes, so validation and every collision test stay linear in
the tree size. convert_sldc_to_slc2 finds back targets with a search over
node starts instead of walking node by node (same result: the first node
start at or after the target), and an SLDC back offset must name a node
before the end of the tree. The chunk loop computes the next chunk position
only after checking the length. More unit tests for both tree formats.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant