Undefined behavior in leveldb: invocation to memcpy reads padding bytes because of flexible array member
Author: ubersandroCreated May 8, 2026Updated May 8, 2026
System Information
- oss-fuzz (git commit 1282bc0ec188def62761c7e77d16d5003b72fcaa, branch master)
- building and running environment: the vulnerable leveldb version builds and runs in the the docker image oss-fuzz builder, not the runner. I am using the builder for fuzzing in my custom workflow for convenience. More specifically, the container is based on Ubuntu 20.04.
root@b6ea40e79b1e:/src/level# cat /etc/issue
Ubuntu 20.04.6 LTSleveld version: cutting edge version cloned from github in the Dockerfile, git commit 7ee830d02b623e8ffe0b95d59a74db1e58da04c5, main branch)
compiler: Clang 21.1.0, base git tag
llvmorg-21.1.0, custom HWAsan that checks for Intra-object bugs. I cannot disclose the prototype at the moment since it is an ongoing research project. Everything is compiled against my custom instrumentation and runtime.
Bug description
Using flexible array member for the key_data field in struct LRUHandle, defined in file cache.c at line 41 as follows:
// An entry is a variable length heap-allocated structure. Entries
// are kept in a circular doubly linked list ordered by access time.
struct LRUHandle {
void* value;
void (*deleter)(const Slice&, void* value);
LRUHandle* next_hash;
LRUHandle* next;
LRUHandle* prev;
size_t charge; // TODO(opt): Only allow uint32_t?
size_t key_length;
bool in_cache; // Whether entry is in the cache.
uint32_t refs; // References, including cache reference, if present.
uint32_t hash; // Hash of key(); used for fast sharding and comparisons
char key_data[1]; // Beginning of key
Slice key() const {
// next is only equal to this if the LRU handle is the list head of an
// empty list. List heads never have meaningful keys.
assert(next != this);
return Slice(key_data, key_length);
}
};```
causes an intra-object overflow that triggers a crash in my sanitizer for such bugs. A memcpy writes inside compiler-inserted padding bytes. This is a (classic) example of undefined behavior caused by the use of flexible array members and I suggest that you consider turning `char key_data[1]` into `char key_data[]` and then changing the lines 273 and 274 inside `cache.cc` as follows:
a: LRUHandle* e = reinterpret_cast<LRUHandle*>(malloc(sizeof(LRUHandle) - 1 + key.size()));
b: LRUHandle* e = reinterpret_cast<LRUHandle*>(malloc(sizeof(LRUHandle) + key.size()));
### STACK TRACE
root@10985de90b39:/src/leveldb# /out/fuzz_db /crashes-fuzz_db/*
Reading 262144 bytes from /crashes-fuzz_db/04e9ce2d296e542fcfda87adbd01f845a94b2e16
[HWASAN] Tag mismatch detected at address 0x16007c4ff7260134: ptr tag=b0b0b0b mem tag=0b
==3704==ERROR: HWAddressSanitizer: tag-mismatch on address 0x7c4ff7260134 at pc 0x55555558d0a5
WRITE of size 8 at 0x7c4ff7260134 tags: 0b/ff (ptr/mem) in thread T0
Invalid access starting at offset 1
#0 0x55555558d0a5 in SigTrap<(__hwasan::ErrorAction)1, (__hwasan::AccessType)1> /src/fsan/llvm-project/compiler-rt/lib/hwasan/hwasan_checks.h:113:3
#1 0x55555558d0a5 in __hwasan_memcpy /src/fsan/llvm-project/compiler-rt/lib/hwasan/hwasan_memintrinsics.cpp:32:3
#2 0x5555556d5f70 in leveldb::(anonymous namespace)::LRUCache::Insert(leveldb::Slice const&, unsigned int, void*, unsigned long, void (*)(leveldb::Slice const&, void*)) /src/leveldb/util/cache.cc:282:3
#3 0x5555556d5f70 in leveldb::(anonymous namespace)::ShardedLRUCache::Insert(leveldb::Slice const&, void*, unsigned long, void (*)(leveldb::Slice const&, void*)) /src/leveldb/util/cache.cc:362:32
#4 0x555555640145 in leveldb::TableCache::FindTable(unsigned long, unsigned long, leveldb::Cache::Handle**) /src/leveldb/db/table_cache.cc:72:25
#5 0x5555556407dd in leveldb::TableCache::NewIterator(leveldb::ReadOptions const&, unsigned long, unsigned long, leveldb::Table**) /src/leveldb/db/table_cache.cc:86:14
#6 0x55555570b05f in leveldb::BuildTable(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, leveldb::Env*, leveldb::Options const&, leveldb::TableCache*, leveldb::Iterator*, leveldb::FileMetaData*) /src/leveldb/db/builder.cc:62:35
#7 0x5555555ddfa1 in leveldb::DBImpl::WriteLevel0Table(leveldb::MemTable*, leveldb::VersionEdit*, leveldb::Version*) /src/leveldb/db/db_impl.cc:519:9
#8 0x5555555daf37 in leveldb::DBImpl::RecoverLogFile(unsigned long, bool, bool*, leveldb::VersionEdit*, unsigned long*) /src/leveldb/db/db_impl.cc:497:16
#9 0x5555555d8a71 in leveldb::DBImpl::Recover(leveldb::VersionEdit*, bool*) /src/leveldb/db/db_impl.cc:366:9
#10 0x5555555ff088 in leveldb::DB::Open(leveldb::Options const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, leveldb::DB**) /src/leveldb/db/db_impl.cc:1511:20
#11 0x5555555c7397 in (anonymous namespace)::OpenDB() /src/leveldb/build/../fuzz_db.cc:57:7
#12 0x5555555bffce in LLVMFuzzerTestOneInput /src/leveldb/build/../fuzz_db.cc:85:37
#13 0x5555555bfcc3 in ExecuteFilesOnyByOne /src/aflplusplus/utils/aflpp_driver/aflpp_driver.c:298:7
#14 0x5555555bf912 in LLVMFuzzerRunDriver /src/aflplusplus/utils/aflpp_driver/aflpp_driver.c
#15 0x5555555bf5c1 in main /src/aflplusplus/utils/aflpp_driver/aflpp_driver.c:354:10
#16 0x7ffff7acc082 in __libc_start_main /build/glibc-B3wQXB/glibc-2.31/csu/../csu/libc-start.c:308:16
#17 0x55555556bc9d in _start (/out/fuzz_db+0x17c9d)
[0x7c4ff72600f0,0x7c4ff7260140) is a small allocated heap chunk; size: 80 offset: 68
Cause: heap-buffer-overflow
0x7c4ff7260134 is located 68 bytes inside a 79-byte region [0x7c4ff72600f0,0x7c4ff726013f)
allocated by thread T0 here:
#0 0x555555576b79 in malloc /src/fsan/llvm-project/compiler-rt/lib/hwasan/hwasan_allocation_functions.cpp:147:3
#1 0x5555556d5c94 in leveldb::(anonymous namespace)::LRUCache::Insert(leveldb::Slice const&, unsigned int, void*, unsigned long, void (*)(leveldb::Slice const&, void*)) /src/leveldb/util/cache.cc:274:36
#2 0x5555556d5c94 in leveldb::(anonymous namespace)::ShardedLRUCache::Insert(leveldb::Slice const&, void*, unsigned long, void (*)(leveldb::Slice const&, void*)) /src/leveldb/util/cache.cc:362:32
#3 0x555555640145 in leveldb::TableCache::FindTable(unsigned long, unsigned long, leveldb::Cache::Handle**) /src/leveldb/db/table_cache.cc:72:25
#4 0x5555556407dd in leveldb::TableCache::NewIterator(leveldb::ReadOptions const&, unsigned long, unsigned long, leveldb::Table**) /src/leveldb/db/table_cache.cc:86:14
#5 0x55555570b05f in leveldb::BuildTable(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, leveldb::Env*, leveldb::Options const&, leveldb::TableCache*, leveldb::Iterator*, leveldb::FileMetaData*) /src/leveldb/db/builder.cc:62:35
#6 0x5555555ddfa1 in leveldb::DBImpl::WriteLevel0Table(leveldb::MemTable*, leveldb::VersionEdit*, leveldb::Version*) /src/leveldb/db/db_impl.cc:519:9
#7 0x5555555daf37 in leveldb::DBImpl::RecoverLogFile(unsigned long, bool, bool*, leveldb::VersionEdit*, unsigned long*) /src/leveldb/db/db_impl.cc:497:16
#8 0x5555555d8a71 in leveldb::DBImpl::Recover(leveldb::VersionEdit*, bool*) /src/leveldb/db/db_impl.cc:366:9
#9 0x5555555ff088 in leveldb::DB::Open(leveldb::Options const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, leveldb::DB**) /src/leveldb/db/db_impl.cc:1511:20
#10 0x5555555c7397 in (anonymous namespace)::OpenDB() /src/leveldb/build/../fuzz_db.cc:57:7
#11 0x5555555bffce in LLVMFuzzerTestOneInput /src/leveldb/build/../fuzz_db.cc:85:37
#12 0x5555555bfcc3 in ExecuteFilesOnyByOne /src/aflplusplus/utils/aflpp_driver/aflpp_driver.c:298:7
Thread: T0 0x0fff00002000 stack: [0x7fffff7ff000,0x7ffffffff000) sz: 8388608 tls: [0x7ffff7a99000,0x7ffff7a9a140)
Memory tags around the buggy address (one tag corresponds to 1 bytes):
0x7c4ff72600b0: 02 02 02 02 02 02 02 02 03 03 03 03 03 03 03 03
0x7c4ff72600c0: 04 04 04 04 04 04 04 04 01 01 01 01 01 01 01 01
0x7c4ff72600d0: 02 02 02 02 02 02 02 02 00 00 00 00 00 00 00 00
0x7c4ff72600e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c4ff72600f0: 01 01 01 01 01 01 01 01 02 02 02 02 02 02 02 02
0x7c4ff7260100: 03 03 03 03 03 03 03 03 04 04 04 04 04 04 04 04
0x7c4ff7260110: 05 05 05 05 05 05 05 05 06 06 06 06 06 06 06 06
0x7c4ff7260120: 07 07 07 07 07 07 07 07 08 ff ff ff 09 09 09 09
=>0x7c4ff7260130: 0a 0a 0a 0a 0b [ff] ff ff 00 00 00 00 00 00 00 00
0x7c4ff7260140: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c4ff7260150: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c4ff7260160: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c4ff7260170: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c4ff7260180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c4ff7260190: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c4ff72601a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7c4ff72601b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Tags for short granules around the buggy address (one tag corresponds to 1 bytes):
0x7c4ff7260120: .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
=>0x7c4ff7260130: .. .. .. .. .. [..] .. .. .. .. .. .. .. .. .. ..
0x7c4ff7260140: .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
See https://clang.llvm.org/docs/HardwareAssistedAddressSanitizerDesign.html#short-granules for a description of short granule tags
SUMMARY: HWAddressSanitizer: tag-mismatch /src/leveldb/util/cache.cc:282:3 in leveldb::(anonymous namespace)::LRUCache::Insert(leveldb::Slice const&, unsigned int, void*, unsigned long, void (*)(leveldb::Slice const&, void*))
AbortedSource: google/leveldb