Undefined behavior in leveldb: invocation to memcpy reads padding bytes because of flexible array member
- Dominant language
- C++
- Stars
- 39.4k
- Forks
- 8.2k
- PR merge metrics
- No merged PRs in 30d
Description
### 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 LTS
```
- leveld 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(malloc(sizeof(LRUHandle) - 1 + key.size()));
b:
LRUHandle* e =
reinterpret_cast(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, std::__1::allocator> 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, std::__1::allocator> 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, std::__1::allocator> 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, std::__1::allocator> 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*))
Aborted
Contributor guide
Assessment
This issue has not been assessed yet.