Skip to content

Fix 4-byte out-of-bounds write in BIQ CITY parser (#865) - #996

Open
Billytifft wants to merge 1 commit into
C7-Game:Developmentfrom
Billytifft:billy/biq-city-heap-corruption-fix
Open

Billytifft wants to merge 1 commit into
C7-Game:Developmentfrom
Billytifft:billy/biq-city-heap-corruption-fix

Conversation

@Billytifft

Copy link
Copy Markdown
Contributor

Closes #865

Summary

The BIQ reader in QueryCiv3 parses the dynamic CITY section with Buffer.MemoryCopy over raw pointers. The documented split was CITY_LEN_1 (38) + CITY_LEN_2 (36) = 74 bytes, but sizeof(CITY) is only 70 bytes (Pack=1). The 36-byte tail copy therefore wrote 4 bytes past every city's struct: into the next array element's Length field, and past the end of the CITY[] array for the last city. That final 4-byte spill lands in the GC heap, corrupting whichever object happened to be allocated right after the array.

This is a strong candidate root cause for #865: the corruption surfaces later, deep inside CoreCLR, as "memory corruption" / malloc-guard failures (getPgoInstrumentationResults, VirtualCallStubManager, nanov2_guard_corruption_detected / _xzm_xzone_malloc_freelist_outlined), depending on what allocation was hit.

Fix

Set CITY_LEN_2 = 32 so CITY_LEN_1 + CITY_LEN_2 == sizeof(CITY) = 70. On-disk records are still 38 + 4 per building + 36 bytes; the 4 unmodeled trailing bytes per record are already accounted for by the existing + 4 in the record advance (dataPtr += City[i].Length + 4), so parse positions and field values are unchanged.

Regression guard

Added EngineTests/BiqSectionSizeTests, which asserts the documented invariant ("the sum of the LEN constants equals the struct size") for every dynamic BIQ section (GOVT, TERR, RACE, CITY, WMAP, PRTO, LEAD, RULE, GAME). CITY_LEN_* are now internal for the test. This would have caught the bug the moment either the constant or the struct changed, and it runs on every CI. No other dynamic section has this mismatch; the SAV parser already uses sizeof-based generic copies.

Verification

  • dotnet build C7/C7.sln - 0 errors
  • dotnet test C7/C7.sln (with CIV3_HOME) - 71/71 pass, including loading 2 Rise of Rome.biq and all Conquest scenarios and playing turns
  • dotnet format C7/C7.sln whitespace --verify-no-changes - clean
  • No field value read through the struct changes: interior spills were overwritten by the next element's first copy, and the array-end spill wrote heap garbage that nothing reads. Save/load data is byte-identical.

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.

[macOS ARM64] Crash on launch - memory corruption in CoreCLR JIT (v0.3 Dutch)

1 participant