You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(FlightDeckBehavior): Prevent undefined behavior when NumSpacesPerRunway exceeds the runway space bones - #3660
Fixes#3659. A FlightDeckBehavior whose NumSpacesPerRunway is larger than a runway's Runway<N>Spaces bone list, or not positive, reads out of bounds when the deck builds its parking spaces. CodeRabbit found this while reviewing #3356 (comment). It is split out here because the fix changes how spaces are built, not only how a value is parsed.
Summary
buildInfo fills m_spaces row by row, one space per runway in each row. NumSpacesPerRunway comes straight from INI and is never checked against the bone lists.
A new parser clamps a negative NumSpacesPerRunway to 0 when the INI is read. The cap against the bone counts has to stay in buildInfo. Retail INI sets NumSpacesPerRunway before the Runway<N>Spaces lists, and module data has no step that runs after its INI block ends, so buildInfo is the first place that knows both values.
The front-row loops in reserveRunway, isInPositionToTakeoff and update read m_spaces[i] for i < NumRunways. When no rows can be built, m_spaces is empty. A file-static getFrontSpaceCount now bounds those loops by the number of spaces actually built.
Retail Zero Hour sets NumSpacesPerRunway = 10 with 10 bones per runway on AmericaAircraftCarrier, so retail data builds the same spaces as before.
NumRunways above MAX_RUNWAYS is a separate out-of-bounds read, which #3356 clamps at parse time.
Evidence
Disassembly against main, Zero Hour FlightDeckBehavior.cpp, ClangCL and VC6 Release:
buildInfo: the new row cap adds instructions. Float arithmetic and calls match. The changed ClangCL dtor$N funclets are the same cleanup handlers, renumbered. VC6 inlines testStatus and the RunwayInfo copy differently.
reserveRunway, isInPositionToTakeoff, update: only the loop bound changes. Float arithmetic and calls match, except that VC6 no longer inlines isInPositionToTakeoff into update. That moves its getDistanceSquared call and fcomp into the out-of-line function.
In game, Zero Hour ClangCL Release, mission MD_GLA03 (the USS Reagan carrier), with a loose Data\INI\Object\FactionBuilding.ini override of AmericaAircraftCarrier:
NumSpacesPerRunway = 0, main: access violation right after loading, reading address 0x14 in FlightDeckBehavior::update (mov ecx,[eax+ecx*8+14h] with eax = 0, the data pointer of the empty m_spaces). Stack: FlightDeckBehavior::update ← GameLogic::update ← GameEngine::update ← GameMain.
NumSpacesPerRunway = 0, this PR: no crash. The deck has no parked jets, and the mission stayed playable for 2:34.
NumSpacesPerRunway = 11, main: no visible effect for 2:11, in two runs. The read past end() is silent in Release.
NumSpacesPerRunway = 11, this PR: two rows of 10 parked jets, as with retail data.
…Runway exceeds the runway space bones
Cap the built rows at the smallest Runway<N>Spaces count and at zero, and
bound the front-row loops by the number of spaces actually built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FlightDeckBehavior clamps negative runway-row counts to zero, limits initialized rows to the available space bones, and uses the resulting front-space count for takeoff reservation, takeoff-position checks, and launch-wave processing.
The parser logs negative NumSpacesPerRunway values and stores zero. buildInfo caps initialized rows at the smallest runway space-bone count. getFrontSpaceCount returns the smaller of the runway count and total initialized spaces.
Use bounded spaces in takeoff and launch GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/FlightDeckBehavior.cpp
Takeoff runway reservation, takeoff-position checks, and launch-wave processing scan no more than the available front-space count.
The change prevents crashes from oversized or negative runway-space settings. An extreme modded configuration with more than 255 total spaces could still corrupt save files. Retail data is unaffected, so this is a narrow edge case to note before merge.
Pre-merge checks: 4
Check name
Status
Explanation
Title check
The title identifies the main change: preventing invalid access when NumSpacesPerRunway exceeds the runway space bones.
Linked Issues check
[#3659] The INI parser clamps negative counts to zero. buildInfo limits rows to the smallest runway bone list before reserving or reading spaces. Front-row loops use the built space count. Retail da…
Out of Scope Changes check
[#3659] The changes to FlightDeckBehavior.cpp prevent invalid space construction and access. The parser logging supports the same fix. No unrelated changes appear in the whole-PR summary.
Description check
The description matches the changes to parking-space initialization, negative-value parsing, and front-row loop bounds.
Full details: Linked Issues check
Explanation
[#3659] The INI parser clamps negative counts to zero. buildInfo limits rows to the smallest runway bone list before reserving or reading spaces. Front-row loops use the built space count. Retail data with 10 bones per runway retains 10 rows. The issue does not specify automated tests.
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.
The reason will be displayed to describe this comment to others. Learn more.
Only from bad INI. INI::parseInt accepted NumSpacesPerRunway = -1. buildInfo then passed the negative product to reserve, which read it as a request for about 4 billion elements and threw. The parser now clamps the value to 0, so I removed this check.
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3659. A
FlightDeckBehaviorwhoseNumSpacesPerRunwayis larger than a runway'sRunway<N>Spacesbone list, or not positive, reads out of bounds when the deck builds its parking spaces. CodeRabbit found this while reviewing #3356 (comment). It is split out here because the fix changes how spaces are built, not only how a value is parsed.Summary
buildInfofillsm_spacesrow by row, one space per runway in each row.NumSpacesPerRunwaycomes straight from INI and is never checked against the bone lists.A new parser clamps a negative
NumSpacesPerRunwayto 0 when the INI is read. The cap against the bone counts has to stay inbuildInfo. Retail INI setsNumSpacesPerRunwaybefore theRunway<N>Spaceslists, and module data has no step that runs after its INI block ends, sobuildInfois the first place that knows both values.The front-row loops in
reserveRunway,isInPositionToTakeoffandupdatereadm_spaces[i]fori < NumRunways. When no rows can be built,m_spacesis empty. A file-staticgetFrontSpaceCountnow bounds those loops by the number of spaces actually built.Retail Zero Hour sets
NumSpacesPerRunway = 10with 10 bones per runway onAmericaAircraftCarrier, so retail data builds the same spaces as before.NumRunwaysaboveMAX_RUNWAYSis a separate out-of-bounds read, which #3356 clamps at parse time.Evidence
main, Zero HourFlightDeckBehavior.cpp, ClangCL and VC6 Release:buildInfo: the new row cap adds instructions. Float arithmetic and calls match. The changed ClangCLdtor$Nfunclets are the same cleanup handlers, renumbered. VC6 inlinestestStatusand theRunwayInfocopy differently.reserveRunway,isInPositionToTakeoff,update: only the loop bound changes. Float arithmetic and calls match, except that VC6 no longer inlinesisInPositionToTakeoffintoupdate. That moves itsgetDistanceSquaredcall andfcompinto the out-of-line function.MD_GLA03(the USS Reagan carrier), with a looseData\INI\Object\FactionBuilding.inioverride ofAmericaAircraftCarrier:NumSpacesPerRunway = 0,main: access violation right after loading, reading address0x14inFlightDeckBehavior::update(mov ecx,[eax+ecx*8+14h]witheax = 0, the data pointer of the emptym_spaces). Stack:FlightDeckBehavior::update←GameLogic::update←GameEngine::update←GameMain.NumSpacesPerRunway = 0, this PR: no crash. The deck has no parked jets, and the mission stayed playable for 2:34.NumSpacesPerRunway = 11,main: no visible effect for 2:11, in two runs. The read pastend()is silent in Release.NumSpacesPerRunway = 11, this PR: two rows of 10 parked jets, as with retail data.Implementation was made with AI assistance