Repository navigation
fix(storage): avoid use-after-free when callback destroys async writer - #16555
kalragauri wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request prevents potential use-after-free issues in AsyncWriter and its connection implementations by ensuring member variables and futures are copied or moved before invoking operations that might synchronously destroy the object. It also adds comprehensive unit tests to verify lifetime safety. The review feedback highlights several violations of the repository style guide's type deduction rules, requesting that auto be replaced with explicit types for connection pointers and complex future types to avoid obscuring domain and return types.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #16555 +/- ##
==========================================
- Coverage 92.38% 92.37% -0.01%
==========================================
Files 2265 2265
Lines 219046 219266 +220
==========================================
+ Hits 202355 202546 +191
- Misses 16691 16720 +29 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
In
AsyncWriterConnectionBufferedStateandAsyncWriterConnectionResumedState,Finalize()andClose()read a member variable afterHandleNewData()returns:Problem
HandleNewData()starts it synchronously (HandleNewData() -> StartWriting() -> WriteLoop() -> FlushStep()).FlushStep()callsimpl_->Flush(...)and attaches a.then()continuation that locksWeakFromThis()into a localshared_ptr(self) and callsself->OnFlush(...).impl_->Flush(...)completes inline with a permanent error,OnFlush()callsSetError(), which unlocksmu_and satisfies all pendingWrite()andFlush()promises (flush_handlers_andpending_flush_promises_).promise::set_value()runs attached.then()callbacks synchronously on the calling thread, a user callback on one of those pending futures can destroy the connection (orAsyncWriter) beforeSetError()returns.OnFlush()then returns and theFlushStep()continuation finishes (still insideHandleNewData()) its localselfshared_ptris destroyed. If no other reference to the state remains,*thisis freed beforeHandleNewData()returns, and readingfinalized_future_orclosed_future_is a heap-use-after-free.AsyncWriterhas the same issue one layer up inWrite(),Finalize(),Flush(), andClose():In C++17, the call
impl_->Finalize(...)is sequenced before the lambda capture[impl = impl_]. If a synchronous callback invoked during that call destroys theAsyncWriter, capturingimpl_reads a member of the destroyedAsyncWriter.Fix
This PR updates both layers by moving or copying the required member into a local variable before calling
HandleNewData()orimpl_->..., so no member of*thisis accessed after the call. InAsyncWriter::Close(), movingimpl_before callingimpl->Close(...)also means any callback that re-enters the writer duringClose()observes a closed stream, matching the documented contract that the writer is not usable afterClose().