# [Bug] Use-After-Free / Segfault when destroying Iterator after DB is closed
- Dominant language
- C++
- Stars
- 39.4k
- Forks
- 8.2k
- PR merge metrics
- No merged PRs in 30d
Description
### Summary
There is an implicit lifecycle dependency between the `DB` instance and the `Iterator` objects it creates. The `Iterator` (specifically `IterState` inside `DBImpl::NewInternalIterator`) holds a raw pointer to `DBImpl::mutex_`.
If the `DB` object is destroyed *before* the `Iterator` object (e.g., in a concurrent environment where the DB shutdown race happens against an iterator going out of scope), the `Iterator`'s destructor triggers a **Use-After-Free (UAF)** by attempting to lock the already-destroyed mutex in `CleanupIteratorState`.
While users *should* ensure iterators are destroyed before the DB, the library does not enforce this safety, leading to hard-to-debug crashes (Segfaults) rather than a clean error or assertion in Release builds.
### Reproduction Steps
Here is a minimal reproduction script that demonstrates the crash on the latest `main` branch.
**`reproduce_uaf.cc`**:
```cpp
#include
#include
#include "leveldb/db.h"
int main() {
leveldb::DB* db;
leveldb::Options options;
options.create_if_missing = true;
// 1. Open the DB
leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb_uaf", &db);
assert(status.ok());
db->Put(leveldb::WriteOptions(), "key", "value");
// 2. Create an Iterator (this holds a raw pointer to db->mutex_)
leveldb::Iterator* it = db->NewIterator(leveldb::ReadOptions());
it->SeekToFirst(); // Just to make it active
// 3. Destroy the DB
// This destroys DBImpl and its mutex_, but the Iterator is still alive.
delete db;
std::cout << "DB destroyed." << std::endl;
// 4. Destroy the Iterator
// CRASH: This calls CleanupIteratorState, which tries to access the freed mutex_.
delete it;
std::cout << "Iterator destroyed." << std::endl;
return 0;
}
```
**Compilation & Execution:**
```bash
g++ reproduce_uaf.cc -o reproduce_uaf -lleveldb -lpthread
./reproduce_uaf
```
### Actual Behavior
* **Release Build:** Segmentation fault (core dumped) or Undefined Behavior (depending on memory allocator state).
* **Debug Build / ASAN:** AddressSanitizer consistently reports **SEGV on unknown address** or **Heap-use-after-free**.
**Stack Trace (ASAN Summary):**
```
AddressSanitizer:DEADLYSIGNAL
=================================================================
==1587593==ERROR: AddressSanitizer: SEGV on unknown address ...
#0 0x559a8e291b17 in leveldb::UnrefEntry(void*, void*) ...
#1 0x559a8e29ef44 in leveldb::Iterator::~Iterator() ...
#2 0x559a8e2a58e4 in leveldb::(anonymous namespace)::TwoLevelIterator::~TwoLevelIterator() ...
...
```
### Root Cause Analysis
In `db/db_impl.cc`:
1. `NewInternalIterator` captures `&mutex_` (a member of `DBImpl`) into `IterState`:
```cpp
IterState* cleanup = new IterState(&mutex_, mem_, imm_, versions_->current());
internal_iter->RegisterCleanup(CleanupIteratorState, cleanup, nullptr);
```
2. `CleanupIteratorState` uses this pointer unconditionally:
```cpp
static void CleanupIteratorState(void* arg1, void* arg2) {
IterState* state = reinterpret_cast(arg1);
state->mu->Lock(); // <--- UAF if DBImpl is already deleted
// ...
}
```
Contributor guide
Assessment
This issue has not been assessed yet.