DecodeEntry (table/block.cc:71 at HEAD 7ee830d) validates a block entry with:
if (static_cast<uint32_t>(limit - p) < (*non_shared + *value_length)) {
return nullptr;
}
The sum *non_shared + *value_length is computed in uint32_t and wraps around. A crafted entry with non_shared = 0x80000000 and value_length = 0x80000001 sums to 1, passes the check, and Block::Iter::ParseNextKey then executes key_.append(p, 0x80000000) (table/block.cc:269) — reading 2 GiB starting from inside the (small) block buffer.
Reproduction (105-byte crafted SSTable, no fuzzing required):
- data block contents: entry
[shared=0][non_shared=varint(0x80000000)][value_length=varint(0x80000001)] + 1 filler byte + restart array (restart point 0, num_restarts 1); trailer type kNoCompression
- index block with a single entry pointing at that data block; standard footer with magic
Table::Open succeeds; NewIterator() + SeekToFirst() triggers the read
Observed:
- Release build (leveldb built by its own CMake): the process dies with SIGBUS/SIGSEGV inside the 2 GiB read (exit 135).
- ASan build with NDEBUG:
AddressSanitizer: READ of size 2147483648 in leveldb::Block::Iter::ParseNextKey() via SeekToFirst.
This is reachable from any iteration/seek over a crafted on-disk table (block checksums protect against accidental corruption, not against a deliberately constructed file), e.g. when opening a restored/synced/imported database. I reported this through Google's vulnerability process first; it was reviewed as a valid finding and I was directed to open it here.
Suggested fix — widen the sum before comparing:
if (static_cast<uint64_t>(limit - p) <
static_cast<uint64_t>(*non_shared) + *value_length) {
return nullptr;
}
or bound-check each operand against limit - p individually.
A self-contained reproduction program (crafts the table and drives Table::Open/SeekToFirst) is available; happy to attach it or send it as a PR adding a regression test.
DecodeEntry(table/block.cc:71 at HEAD 7ee830d) validates a block entry with:The sum
*non_shared + *value_lengthis computed inuint32_tand wraps around. A crafted entry withnon_shared = 0x80000000andvalue_length = 0x80000001sums to 1, passes the check, andBlock::Iter::ParseNextKeythen executeskey_.append(p, 0x80000000)(table/block.cc:269) — reading 2 GiB starting from inside the (small) block buffer.Reproduction (105-byte crafted SSTable, no fuzzing required):
[shared=0][non_shared=varint(0x80000000)][value_length=varint(0x80000001)]+ 1 filler byte + restart array (restart point 0, num_restarts 1); trailer typekNoCompressionTable::Opensucceeds;NewIterator()+SeekToFirst()triggers the readObserved:
AddressSanitizer: READ of size 2147483648inleveldb::Block::Iter::ParseNextKey()viaSeekToFirst.This is reachable from any iteration/seek over a crafted on-disk table (block checksums protect against accidental corruption, not against a deliberately constructed file), e.g. when opening a restored/synced/imported database. I reported this through Google's vulnerability process first; it was reviewed as a valid finding and I was directed to open it here.
Suggested fix — widen the sum before comparing:
or bound-check each operand against
limit - pindividually.A self-contained reproduction program (crafts the table and drives
Table::Open/SeekToFirst) is available; happy to attach it or send it as a PR adding a regression test.