Reject oversized memtable entries - #15161
Open
xingbowang wants to merge 1 commit into
Open
Conversation
Summary: Memtable entries encode their internal-key and value lengths as varint32, while MemTable::Add receives Slice lengths as size_t. Previously, it narrowed the key and value lengths and calculated the complete allocation size in uint32_t before allocating storage. A key that no longer fits after adding the eight-byte internal-key suffix, or a key/value pair whose complete encoding exceeds UINT32_MAX, could therefore wrap the calculated allocation size. Subsequent encoding and copying could write beyond the undersized allocation or silently truncate data. WriteBatch validates individual key and value lengths on common public write paths, but that is not sufficient: it does not validate the aggregate memtable encoding size and MemTable::Add is also reached by internal and recovery paths. MemTable::Add is the final common boundary before allocation and copying, so validate the sizes there before narrowing or mutating the memtable. Compute the complete encoded length in uint64_t and return Status::InvalidArgument when the entry cannot be represented by the uint32-based format. Keep the existing post-encoding assertion unchanged. It verifies private encoder bookkeeping for inputs that already satisfy the format contract. It cannot replace the runtime validation because it executes after allocation and copying, compares already-narrowed unsigned values that can hide wraparound, and is compiled out of release builds. The runtime checks reject invalid data safely, while the assertion continues to catch internal implementation mistakes during development. The regression test covers an individually representable value whose aggregate entry size overflows, and a key whose internal-key length overflows. It also verifies rejection happens before the memtable is modified. Test Plan: Built db_memtable_test and ran DBMemTableTest.RejectsOversizedEncodedEntry under normal and ASSERT_STATUS_CHECKED=1 configurations. Production memtable.o compiled with DEBUG_LEVEL=0. Reviewers: Subscribers: Tasks: Tags:
✅ clang-tidy: No findings on changed linesCompleted in 103.6s. |
|
@xingbowang has imported this pull request. If you are a Meta employee, you can view this in D117893558. |
✅ Claude Code ReviewAuto-triggered after CI passed — reviewing commit c88b7b3 SummaryWell-designed defensive fix that adds size validation to High-severity findings (0): Full review (click to expand)Findings🔴 HIGHNo high-severity findings. 🟡 MEDIUMM1. Implicit narrowing conversion from
|
| Context | Impact | Assessment |
|---|---|---|
| WriteBatch (public API) | Already validates key and value sizes before reaching Add() |
Defense-in-depth; Add() validation catches edge cases like aggregate overflow |
| WAL Recovery | Replays previously-valid entries that already fit in uint32_t | Safe — no regression |
MemTable::Update |
Falls back to Add() for non-in-place updates |
Safe via fallback; in-place path has pre-existing trust assumption (M2) |
AddLogicallyRedundantRangeTombstone |
Calls Add() with iterator-derived keys (bounded by existing SST sizes) |
Safe; Add() returns non-OK, caller returns false (line 1019-1023) |
| Concurrent writes | Validation is read-only on inputs, before any shared state mutation | Thread-safe |
| WritePreparedTxnDB | Uses same WriteBatch → MemTableInserter → Add() path |
Safe |
Positive Observations
- Correct placement: Validation at
MemTable::Add()is the right layer — it's the final common boundary before allocation and encoding, catching all paths including internal and recovery. - No mutation before validation: All counter updates (
num_entries_,data_size_), bloom filter updates, and memory allocation happen strictly after the validation checks. - Consistent with existing patterns: Uses
UNLIKELYfor error paths,Status::InvalidArgumentmatchingWriteBatchconventions, andkNumInternalBytesinstead of magic number8. - Clean uint64_t arithmetic: Computing
encoded_len64inuint64_twith explicituint64_t{}widening prevents any intermediate overflow. - Negligible performance impact: Two branch-predicted-away comparisons on the write hot path; 64-bit arithmetic is native on x86_64/ARM64.
- Good test technique: Using
Slicewith fake lengths to exercise validation without allocating gigabytes. - Documentation updated: Header comment in
memtable.hdocuments the newInvalidArgumentreturn.
ℹ️ About this response
Generated by Claude Code.
Review methodology: claude_md/ci_review_prompt.md
Limitations:
- Claude may miss context from files not in the diff
- Large PRs may be truncated
- Always apply human judgment to AI suggestions
Commands:
/claude-review [context]— Request a code review/claude-query <question>— Ask about the PR or codebase
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Memtable entries encode their internal-key and value lengths as varint32, while MemTable::Add receives Slice lengths as size_t. Previously, it narrowed the key and value lengths and calculated the complete allocation size in uint32_t before allocating storage. A key that no longer fits after adding the eight-byte internal-key suffix, or a key/value pair whose complete encoding exceeds UINT32_MAX, could therefore wrap the calculated allocation size. Subsequent encoding and copying could write beyond the undersized allocation or silently truncate data.
WriteBatch validates individual key and value lengths on common public write paths, but that is not sufficient: it does not validate the aggregate memtable encoding size and MemTable::Add is also reached by internal and recovery paths. MemTable::Add is the final common boundary before allocation and copying, so validate the sizes there before narrowing or mutating the memtable. Compute the complete encoded length in uint64_t and return Status::InvalidArgument when the entry cannot be represented by the uint32-based format.
Keep the existing post-encoding assertion unchanged. It verifies private encoder bookkeeping for inputs that already satisfy the format contract. It cannot replace the runtime validation because it executes after allocation and copying, compares already-narrowed unsigned values that can hide wraparound, and is compiled out of release builds. The runtime checks reject invalid data safely, while the assertion continues to catch internal implementation mistakes during development.
The regression test covers an individually representable value whose aggregate entry size overflows, and a key whose internal-key length overflows. It also verifies rejection happens before the memtable is modified.
Test plan
db_memtable_testand ranDBMemTableTest.RejectsOversizedEncodedEntryin normal andASSERT_STATUS_CHECKED=1configurations.memtable.owithDEBUG_LEVEL=0.