Skip to content

fix: Validate INI and savegame bounds across game modules - #3356

Open
CryoTheRenegade wants to merge 13 commits into
TheSuperHackers:mainfrom
CryoTheRenegade:bugfix/clang-tidy-debris-state-bounds
Open

CryoTheRenegade wants to merge 13 commits into
TheSuperHackers:mainfrom
CryoTheRenegade:bugfix/clang-tidy-debris-state-bounds

Conversation

@CryoTheRenegade

@CryoTheRenegade CryoTheRenegade commented Sep 25, 2026 •

Copy link
Copy Markdown

clang-tidy flagged W3DDebrisDraw for indexing its animation arrays with a state loaded from a savegame, without a range check. In review, xezon asked for an audit of INI parsing and savegame loading for the same kind of bug (comment), so this PR also fixes what that audit found.

Validate INI and savegame values before they control array access or allocation sizes in Generals and Zero Hour.

The changes reject invalid counts, indices, and inconsistent saved array lengths. They preserve valid sentinel values and the savegame format. Debris state is clamped during post-load processing, and particle rate calculations handle completed keyframe sequences before indexing.

INI counts that size fixed arrays (NumberGlobalLights, OuterEffectNumBones, NumRunways and the map cache numPlayers) are clamped by the new INI::parseIntClamped and INI::parseUnsignedIntClamped, which take the maximum in the field's userData. A loaded drawable with an unknown module type count or icon name, or a railed transport with an out-of-range path, now rejects the save.

Implementation was made with AI assistance

@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Explain Change →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The changes add clamped INI parsers for selected settings and add bounds or consistency checks for loaded and transferred game state across Core, Generals, and GeneralsMD.

Changes

State and configuration validation

Layer / File(s) Summary
Clamp configured values
Core/GameEngine/Include/Common/INI.h, Core/GameEngine/Source/Common/INI/*, Generals*/Code/GameEngine/Source/Common/GlobalData.cpp, GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/FlightDeckBehavior.cpp, Generals*/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp
Adds signed and unsigned clamped INI parsers and applies them to selected map, lighting, runway, and particle effect settings.
Validate client and map state
Core/GameEngine/Source/Common/System/Radar.cpp, Core/GameEngine/Source/GameClient/System/ParticleSys.cpp, Core/GameEngine/Source/GameLogic/Map/*, Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDebrisDraw.cpp, Generals*/Code/GameEngine/Source/GameClient/Drawable.cpp
Checks saved radar, particle, polygon, terrain, and drawable values before use. Debris animation state is clamped to its valid range.
Validate gameplay load state
Generals*/Code/GameEngine/Source/Common/RTS/Money.cpp, Generals*/Code/GameEngine/Source/GameLogic/AI/{AIPlayer,AISkirmishPlayer}.cpp, Generals*/Code/GameEngine/Source/GameLogic/Object/{Contain,ExperienceTracker.cpp,Update,BoneFXUpdate.cpp,WeaponSet.cpp}
Checks saved gameplay bounds during load post-processing. Skirmish players also call the base AI post-processing method, and station garrison-point queries are capped at buffer capacity.
Validate gameplay transfers and vector sizes
Generals*/Code/GameEngine/Source/GameLogic/Object/Update/{AIUpdate,DockUpdate}/..., Generals*/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp
Checks serialized transport paths and counts, script count consistency, and dock vector bounds and sizes before processing them.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Suggested reviewers: xezon

Merge Risk: Moderate · previous review 113c4

This PR hardens INI and savegame parsing, but a few gaps remain. A map cache entry without a player count can read out of bounds, and some configuration values (parking spaces per runway, out-of-range literals) and oversized dock vector sizes in saves are still not safely bounded. These should be fixed or explicitly accepted before merge.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 8b11c

The examined changes reject invalid counts before normal use or constrain loaded state, without showing a new exposure or weakened control. Some security coverage and runtime behavior remain unverified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated input paths affect rendering initialization or savegame loading within the game process. The available caller evidence does not establish remote attacker reachability or a broader privilege boundary.

Trust Boundaries and Controls

  • observed — The laser check rejects invalid configuration counts before allocation; the AI post-processing check rejects out-of-range saved repair-queue counts before normal queue processing.

Resilience and Maintainability Implications

  • inferred — Post-processing failure routes the examined save-load path through reset rather than onward into gameplay; repeated-load behavior was not exercised.
Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title names the main change: validating INI and savegame values across game modules. The changeset adds bounds checks in many load/xfer paths (Radar, ParticleSys, PolygonTrigger, TerrainLogic, Mon…
Description check Passed The description states the purpose: clang-tidy flagged W3DDebrisDraw for unchecked savegame-loaded indices, and an audit of INI parsing and savegame loading followed. It lists the kinds of checks adde…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the main change: validating INI and savegame values across game modules. The changeset adds bounds checks in many load/xfer paths (Radar, ParticleSys, PolygonTrigger, TerrainLogic, Money, AIPlayer, OpenContain, DockUpdate, etc.) and adds INI clamped parsers. The title is specific and concise, and it matches the PR objectives. The word 'fix' is a reasonable conventional-commit prefix for defensive validation of invalid data.

Full details: Description check

Explanation

The description states the purpose: clang-tidy flagged W3DDebrisDraw for unchecked savegame-loaded indices, and an audit of INI parsing and savegame loading followed. It lists the kinds of checks added (invalid counts, indices, array lengths), the new INI::parseIntClamped and INI::parseUnsignedIntClamped helpers, and the affected fields. This matches the changeset.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@greptile-apps

greptile-apps Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[High impact] The latest change appears safe to merge.

Summary

The PR adds bounds checks for INI values and saved state across both game variants.

  • INI counts stay within the storage their fields can use.
  • Saved counts and paired lists must fit before the game reads them.
  • Saved gameplay indices stay within the arrays they select.
  • Particle and debris animation states stay inside their valid bounds.

Reviews (17) · Last reviewed commit: "bugfix: Initialize map cache player coun..." · Reviewed by Greptile

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Sep 25, 2026
@macroscopeapp

macroscopeapp Bot commented Sep 25, 2026

Copy link
Copy Markdown

Approvability

Verdict: Approved at 805403e

Macroscope's review found this PR approvable — This is a focused defensive fix that clamps invalid savegame state before existing fixed-size animation arrays are accessed. Valid states and serialization behavior remain unchanged, with no broader runtime or schema impact.

You can add or adjust custom eligibility rules. Learn more.

}
else if (m_state > FINAL)
{
m_state = FINAL;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks sloppy. Of course the bots are happy because they have no taste ;)

I expect this belongs into postProcessLoad as a m_state = clamp(INITIAL, m_state, FINAL)

Also, I expect this type of issue to be a concern for MANY modules that relate to INI parse and xfer. Can we make a project wide audit and fix all such findings?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the clamp into loadPostProcess() using clamp<Int>. I can audit the INI parsing and save/load code for similar issues and address the findings in a follow-up PR.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes make an audit please and maybe all of them can be fixed in one go or several batches depending on size. Try to bundle the changes where it makes sense.

This change is very small right now.

@CryoTheRenegade CryoTheRenegade changed the title fix(W3DDebrisDraw): Clamp loaded debris state to prevent out-of-bounds access fix: Validate INI and savegame bounds across game modules Sep 28, 2026

@Caball009 Caball009 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete review.

@CryoTheRenegade
CryoTheRenegade force-pushed the bugfix/clang-tidy-debris-state-bounds branch from 0174a11 to 6c995f6 Compare October 17, 2026 17:20
CryoTheRenegade and others added 10 commits November 5, 2026 11:53
Reject invalid counts, indices and inconsistent saved array sizes in both games. Preserve valid sentinel values and serialized layouts, and handle completed particle key sequences before indexing.
Ignore invalid docking counts and disable unsafe laser visuals instead of throwing INI errors. Use debug logging for the added validation diagnostics while preserving rejection of invalid save data.
thanks greptile

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CryoTheRenegade
CryoTheRenegade force-pushed the bugfix/clang-tidy-debris-state-bounds branch from 6c995f6 to 6174623 Compare November 5, 2026 18:56
@coderabbitai

coderabbitai Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Action not completed

Incremental review skipped.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@CryoTheRenegade

Copy link
Copy Markdown
Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

Major · Initialize m_numPlayers before parsing. · INIMapCache.cpp:46

Core/GameEngine/Source/Common/INI/INIMapCache.cpp:46
Major · Stability & Availability · Quick win

Initialize m_numPlayers before parsing.

If a cache entry omits numPlayers, initFromINI does not assign mdr.m_numPlayers. The later copy and waypoint loop can therefore use an indeterminate bound and read beyond mdr.m_waypoints.

Suggested fix

 	MapMetaDataReader mdr;
 	MapMetaData md;
+	mdr.m_numPlayers = 0;
 
 	// read the name
Review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5fa421ec-0917-4c27-8dcf-845b64cc44df
Commits

Reviewing files that changed from the base of the PR and between 3d02290 and 113c46a.

Files selected for processing: 42

  • Core/GameEngine/Include/Common/INI.h
  • Core/GameEngine/Source/Common/INI/INI.cpp
  • Core/GameEngine/Source/Common/INI/INIMapCache.cpp
  • Core/GameEngine/Source/Common/System/Radar.cpp
  • Core/GameEngine/Source/GameClient/System/ParticleSys.cpp
  • Core/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp
  • Core/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp
  • Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDebrisDraw.cpp
  • Generals/Code/GameEngine/Source/Common/GlobalData.cpp
  • Generals/Code/GameEngine/Source/Common/RTS/Money.cpp
  • Generals/Code/GameEngine/Source/GameClient/Drawable.cpp
  • Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp
  • Generals/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp
  • Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp
  • Generals/Code/GameEngine/Source/GameLogic/Object/ExperienceTracker.cpp
  • Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/AssaultTransportAIUpdate.cpp
  • Generals/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/RailedTransportAIUpdate.cpp
  • Generals/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp
  • Generals/Code/GameEngine/Source/GameLogic/Object/Update/DockUpdate/DockUpdate.cpp
  • Generals/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp
  • Generals/Code/GameEngine/Source/GameLogic/Object/Update/ProjectileStreamUpdate.cpp
  • Generals/Code/GameEngine/Source/GameLogic/Object/Update/WaveGuideUpdate.cpp
  • Generals/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp
  • Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp
  • GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp
  • GeneralsMD/Code/GameEngine/Source/Common/RTS/Money.cpp
  • GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/FlightDeckBehavior.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/GarrisonContain.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/Object/ExperienceTracker.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/AssaultTransportAIUpdate.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/RailedTransportAIUpdate.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BoneFXUpdate.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/DockUpdate/DockUpdate.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ParticleUplinkCannonUpdate.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ProjectileStreamUpdate.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/WaveGuideUpdate.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp
  • GeneralsMD/Code/GameEngine/Source/GameLogic/ScriptEngine/ScriptEngine.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

{ "NumRunways", INI::parseInt, nullptr, offsetof( FlightDeckBehaviorModuleData, m_numCols ) },
{ "NumSpacesPerRunway", INI::parseInt, nullptr, offsetof( FlightDeckBehaviorModuleData, m_numRows ) },
{ "NumRunways", INI::parseIntClamped, (const void*)MAX_RUNWAYS, offsetof( FlightDeckBehaviorModuleData, m_numCols ) },
{ "NumSpacesPerRunway", INI::parseInt, nullptr, offsetof( FlightDeckBehaviorModuleData, m_numRows ) },

@coderabbitai coderabbitai Bot Nov 5, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major · Stability & Availability · Quick win

Supported by static analysis

Script executed:

grep -n 'm_numRows\|m_numRunways\|reserve\|NumSpacesPerRunway' GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/FlightDeckBehavior.cpp
sed -n '150,230p' GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/FlightDeckBehavior.cpp

Repository: TheSuperHackers/GeneralsGameCode

Length of output: 4034


Validate NumSpacesPerRunway before it sizes or indexes the parking spaces.

NumSpacesPerRunway (m_numRows) is still parsed with INI::parseInt and is not range-checked. buildInfo uses it in m_spaces.reserve( data->m_numRows * data->m_numCols ) and in the row loop. This PR does not change this count.

Two invalid values can fail in this path:

  • A negative count with at least one runway makes the product negative. Converted to size_t, it becomes a very large size. reserve then throws std::length_error, which this function does not catch. This applies only if the INI parser accepts negative values.
  • A count greater than the number of SpacesBoneNames in a used runway moves the iterator to end(). The dereference of it at the bone-position call is then undefined behavior.

Reject invalid counts before reserve runs. Require a value that is not negative and does not exceed the number of bone names in each runway that is used.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is real. The fix is in #3660 (tracked in #3659), separate from this PR, because it changes how the flight deck builds its spaces rather than how a value is parsed. buildInfo now caps the row count at the smallest Runway<N>Spaces list and at zero. The front-row loops are bounded by the number of spaces actually built.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CryoTheRenegade Understood. The changes you describe cover both space construction and front-row access. Keeping that behavior change in #3660, tracked by #3659, is reasonable. No duplicate fix is needed in this PR.

I have not verified #3660, so this finding remains open pending verification there.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

You are interacting with an AI system.

…n load

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds INI::parseIntClamped and INI::parseUnsignedIntClamped, which take the
maximum in userData, and uses them for NumberGlobalLights,
OuterEffectNumBones, NumRunways and the map cache numPlayers. Garrison
station bones are capped at the station buffer size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CryoTheRenegade
CryoTheRenegade force-pushed the bugfix/clang-tidy-debris-state-bounds branch from 0bf54eb to b8bd23b Compare November 5, 2026 20:20
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

3 participants