Skip to content

fix: avoid multikey onCommit use-after-free - #489

Open
Meowooh wants to merge 1 commit into
eloqdata:mainfrom
Meowooh:fix-multikey-oncommit-uaf
Open

fix: avoid multikey onCommit use-after-free#489
Meowooh wants to merge 1 commit into
eloqdata:mainfrom
Meowooh:fix-multikey-oncommit-uaf

Conversation

@Meowooh

@Meowooh Meowooh commented Aug 3, 2026

Copy link
Copy Markdown

What changed

  • Skip the IndexCatalogEntryImpl multikey commit callback for the Eloq storage engine.
  • Preserve the original in-memory multikey and plan-cache behavior for other storage engines.
  • Add a stress test covering multikey catalog commits, metadata refreshes, rollbacks, transactions, and restart persistence.

Why

IndexCatalogEntryImpl::setMultikey() persists the new multikey metadata and then registers an onCommit callback that captures the entry through a raw this pointer.

Eloq publishes catalog changes as a new schema version. During commit, refreshing that schema can replace the cached Collection and destroy its IndexCatalogEntryImpl before RecoveryUnit callbacks run. The callback can then dereference a dangling pointer.

For Eloq, the refreshed catalog entry is rebuilt from committed metadata and already contains the committed multikey state and path-level metadata. The callback is therefore unnecessary. Transaction-local multikey tracking remains unchanged for read-own-writes.

Other storage engines retain the original callback that updates _isMultikey, merges multikey paths, and clears the plan cache.

Testing

Added tests/jstests/eloq_basic/multikey_catalog_commit_stress.js, covering:

  • direct inserts and updates that make indexes multikey
  • transaction commit and abort
  • read-own-writes inside transactions
  • rollback after CannotIndexParallelArrays
  • index builds over existing array values
  • path-level multikey metadata
  • warmed query plans before a multikey transition
  • persistent metadata verification after restart

Results:

  • The stress test passed with 8 workers and 20 iterations per worker: 160 total rounds.
  • Each of the five test scenarios was exercised 32 times.
  • All failed parallel-array writes rolled back their multikey metadata correctly.
  • The restart canary retained the expected multikey and path-level metadata.
  • multikey_index.js and read_own_multikey_writes.js passed.
  • The incremental install-core build passed.
  • node --check and git diff --check passed.

Summary by CodeRabbit

  • Bug Fixes

    • Improved multikey index metadata handling for the Eloq storage engine.
    • Preserved existing multikey updates and query-cache behavior for other storage engines.
  • Tests

    • Added stress coverage for multikey index commits, transactions, parallel-array failures, index creation, collection/database removal, restarts, and metadata validation.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The change skips Eloq multikey recovery callbacks and adds a configurable stress test. The test validates multikey metadata, transactions, failed writes, repeated database destruction, concurrent workers, and persistent canary behavior across restarts.

Changes

Multikey commit validation

Layer / File(s) Summary
Eloq multikey callback behavior
src/mongo/db/catalog/index_catalog_entry_impl.cpp
The index catalog skips post-commit multikey state updates and plan-cache clearing when the storage engine is Eloq. Other engines retain the existing behavior.
Multikey metadata and canary validation
tests/jstests/eloq_basic/multikey_catalog_commit_stress.js
The test defines scenarios, document builders, explain-plan checks, index metadata checks, transactional canary validation, and verify-only execution.
Concurrent stress execution
tests/jstests/eloq_basic/multikey_catalog_commit_stress.js
Concurrent workers perform direct and transactional writes, abort checks, failed parallel-array writes, checkpoints, and repeated collection or database destruction. The test aggregates statistics and validates the server and canary.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant StressWorkers
  participant MongoDBServer
  participant MultikeyIndex
  participant PersistentCanary
  StressWorkers->>MongoDBServer: perform direct and transactional writes
  MongoDBServer->>MultikeyIndex: commit multikey metadata
  StressWorkers->>MongoDBServer: run explain and checkpoint validation
  StressWorkers->>PersistentCanary: create or retain restart canary
  PersistentCanary-->>MongoDBServer: validate persistent data
Loading

Suggested reviewers: xiexiaoy

Poem

I’m a rabbit with a test to run,
Through arrays, commits, and checks of fun.
Canaries wait through restart night,
While workers hop from write to write.
Eloq keeps its catalog tight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: preventing a multikey onCommit use-after-free.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@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.

🧹 Nitpick comments (1)
tests/jstests/eloq_basic/multikey_catalog_commit_stress.js (1)

252-255: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Strengthen the plan-cache warmup to actually exercise the multikey/plan-cache interaction.

This warmup runs the {"a.b": base} shape three times before the collection becomes multikey, and assertHealthyMultikeyQueries (Lines 168-176) later runs the same shape ({"a.b": base + 2}, no .hint()) after the index becomes multikey. This is the right shape to target a stale, pre-multikey plan-cache entry.

However, in every scenario only one array element per document matches the queried value, so a stale plan that assumes the index is not multikey would still return the correct itcount() of 1 - it would not need to deduplicate matches from multiple array positions in the same document. As currently written, the test may pass even if a query plan cached before the commit incorrectly persists past the multikey change, which is exactly the class of bug this PR's callback-skip change could introduce for Eloq if the plan cache is not otherwise invalidated.

Add a case where the warmed query value matches more than one element of the same array field in a single document (for example a query value appearing twice in tags), so a stale non-multikey-aware plan would surface as an incorrect duplicate count.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/jstests/eloq_basic/multikey_catalog_commit_stress.js` around lines 252
- 255, Strengthen the warmup in the multikey plan-cache scenario so its queried
value matches multiple elements of the same document array, such as a duplicated
value in tags. Update the setup and assertions around the warmup and
assertHealthyMultikeyQueries to verify the expected deduplicated document count,
ensuring a pre-multikey cached plan would produce a detectable incorrect
duplicate count.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/jstests/eloq_basic/multikey_catalog_commit_stress.js`:
- Around line 252-255: Strengthen the warmup in the multikey plan-cache scenario
so its queried value matches multiple elements of the same document array, such
as a duplicated value in tags. Update the setup and assertions around the warmup
and assertHealthyMultikeyQueries to verify the expected deduplicated document
count, ensuring a pre-multikey cached plan would produce a detectable incorrect
duplicate count.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dfa07f74-1e29-46b4-8a6a-a0b3883cc652

📥 Commits

Reviewing files that changed from the base of the PR and between 4485b4b and decb5a6.

📒 Files selected for processing (2)
  • src/mongo/db/catalog/index_catalog_entry_impl.cpp
  • tests/jstests/eloq_basic/multikey_catalog_commit_stress.js

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.

2 participants