MemTable::Add encodes key_size + 8 as varint32: Put accepts a key of 2^32-8 .. 2^32-1 bytes and reading it back segfaults#1362
Summary
MemTable::Add sizes the entry buffer using VarintLength(key_size + 8)
(64-bit) but writes the length with EncodeVarint32 (32-bit). For a key of
length 4294967288 .. 4294967295 bytes, key_size + 8 is >= 2^32, the encoded
internal-key length wraps to a value < 8, and the memtable entry is malformed.
Put returns OK; other keys keep working; Get of that key crashes (release
build). Key lengths >= 2^32 are not affected by this path because the
WriteBatch length prefix wraps first and the batch is rejected (the same wrap
reported for values in #718). So a size check added for #718 at 2^32 would
still leave this window open: the key limit has to be 2^32 - 9.
Reproduction
Needs ~16 GB RAM. Built against main with
g++ -O2 -std=c++17 repro.cc -Ileveldb/include leveldb/build/libleveldb.a -lsnappy -lpthread.
// Usage: repro <dbpath> <key_bytes>
#include <cstdio>
#include <cstdlib>
#include <string>
#include "leveldb/db.h"
int main(int argc, char** argv) {
setvbuf(stdout, nullptr, _IONBF, 0);
const std::string path = argv[1];
const size_t n = std::strtoull(argv[2], nullptr, 10);
leveldb::DestroyDB(path, leveldb::Options());
leveldb::Options o;
o.create_if_missing = true;
leveldb::DB* db;
if (!leveldb::DB::Open(o, path, &db).ok()) return 2;
db->Put(leveldb::WriteOptions(), "sentinel", "SENT");
std::string key(n, 'K');
leveldb::Status s = db->Put(leveldb::WriteOptions(), key, "THE-VALUE");
std::printf("Put(key of %zu bytes): %s\n", n, s.ToString().c_str());
std::string v;
s = db->Get(leveldb::ReadOptions(), "sentinel", &v);
std::printf("Get(\"sentinel\"): %s value=%s\n", s.ToString().c_str(), v.c_str());
std::printf("Get(big key) ...\n");
s = db->Get(leveldb::ReadOptions(), key, &v);
std::printf("Get(big key): %s value=%s\n", s.ToString().c_str(), v.c_str());
delete db;
return 0;
}