Skip to content

Commit f5468fa

Browse files
author
Craig Radcliffe
authored
scaled range timer: guard against queue deletion during timer fire (envoyproxy#14799)
Fix a potential use-after-free error in ScaledRangeTimerManagerImpl by adding a processing_timers_ flag to the timer queues that is set during onQueueTimerFired processing. This flag is checked when a timer is removed to ensure that the timer's queue isn't deleted while it is in a callback triggered by onQueueTimerFired. Signed-off-by: Craig Radcliffe <[email protected]>
1 parent 274e883 commit f5468fa

File tree

3 files changed

+65
-1
lines changed

3 files changed

+65
-1
lines changed

source/common/event/scaled_range_timer_manager_impl.cc

+7-1
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,11 @@ void ScaledRangeTimerManagerImpl::removeTimer(ScalingTimerHandle handle) {
225225
handle.queue_.range_timers_.erase(handle.iterator_);
226226
// Don't keep around empty queues
227227
if (handle.queue_.range_timers_.empty()) {
228-
queues_.erase(handle.queue_);
228+
// Skip erasing the queue if we're in the middle of processing timers for the queue. The
229+
// queue will be erased in `onQueueTimerFired` after the queue entries have been processed.
230+
if (!handle.queue_.processing_timers_) {
231+
queues_.erase(handle.queue_);
232+
}
229233
return;
230234
}
231235

@@ -255,12 +259,14 @@ void ScaledRangeTimerManagerImpl::onQueueTimerFired(Queue& queue) {
255259

256260
// Pop and trigger timers until the one at the front isn't supposed to have expired yet (given the
257261
// current scale factor).
262+
queue.processing_timers_ = true;
258263
while (!timers.empty() &&
259264
computeTriggerTime(timers.front(), queue.duration_, scale_factor_) <= now) {
260265
auto item = std::move(queue.range_timers_.front());
261266
queue.range_timers_.pop_front();
262267
item.timer_.trigger();
263268
}
269+
queue.processing_timers_ = false;
264270

265271
if (queue.range_timers_.empty()) {
266272
// Maintain the invariant that queues are never empty.

source/common/event/scaled_range_timer_manager_impl.h

+4
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ class ScaledRangeTimerManagerImpl : public ScaledRangeTimerManager {
6868
// 2) on expiration
6969
// 3) when the scale factor changes
7070
const TimerPtr timer_;
71+
72+
// A flag indicating whether the queue is currently processing timers. Used to guard against
73+
// queue deletion during timer processing.
74+
bool processing_timers_{false};
7175
};
7276

7377
/**

test/common/event/scaled_range_timer_manager_impl_test.cc

+54
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,60 @@ TEST_F(ScaledRangeTimerManagerTest, DisableWhileScalingMax) {
157157
simTime().advanceTimeAndRun(std::chrono::seconds(100), dispatcher_, Dispatcher::RunType::Block);
158158
}
159159

160+
TEST_F(ScaledRangeTimerManagerTest, InCallbackDisableLastTimerInSameQueue) {
161+
ScaledRangeTimerManagerImpl manager(dispatcher_);
162+
163+
MockFunction<TimerCb> callback1;
164+
auto timer1 =
165+
manager.createTimer(AbsoluteMinimum(std::chrono::seconds(0)), callback1.AsStdFunction());
166+
MockFunction<TimerCb> callback2;
167+
auto timer2 =
168+
manager.createTimer(AbsoluteMinimum(std::chrono::seconds(5)), callback2.AsStdFunction());
169+
170+
timer1->enableTimer(std::chrono::seconds(95));
171+
timer2->enableTimer(std::chrono::seconds(100));
172+
173+
simTime().advanceTimeAndRun(std::chrono::seconds(5), dispatcher_, Dispatcher::RunType::Block);
174+
175+
EXPECT_TRUE(timer1->enabled());
176+
EXPECT_TRUE(timer2->enabled());
177+
178+
EXPECT_CALL(callback1, Call).WillOnce(Invoke([&]() {
179+
timer2->disableTimer();
180+
timer2.reset();
181+
}));
182+
183+
// Run the dispatcher to make sure nothing happens when it's not supposed to.
184+
simTime().advanceTimeAndRun(std::chrono::seconds(100), dispatcher_, Dispatcher::RunType::Block);
185+
}
186+
187+
TEST_F(ScaledRangeTimerManagerTest, InCallbackDisableTimerInOtherQueue) {
188+
ScaledRangeTimerManagerImpl manager(dispatcher_);
189+
190+
MockFunction<TimerCb> callback1;
191+
auto timer1 =
192+
manager.createTimer(AbsoluteMinimum(std::chrono::seconds(5)), callback1.AsStdFunction());
193+
MockFunction<TimerCb> callback2;
194+
auto timer2 =
195+
manager.createTimer(AbsoluteMinimum(std::chrono::seconds(5)), callback2.AsStdFunction());
196+
197+
timer1->enableTimer(std::chrono::seconds(95));
198+
timer2->enableTimer(std::chrono::seconds(100));
199+
200+
simTime().advanceTimeAndRun(std::chrono::seconds(5), dispatcher_, Dispatcher::RunType::Block);
201+
202+
EXPECT_TRUE(timer1->enabled());
203+
EXPECT_TRUE(timer2->enabled());
204+
205+
EXPECT_CALL(callback1, Call).WillOnce(Invoke([&]() {
206+
timer2->disableTimer();
207+
timer2.reset();
208+
}));
209+
210+
// Run the dispatcher to make sure nothing happens when it's not supposed to.
211+
simTime().advanceTimeAndRun(std::chrono::seconds(100), dispatcher_, Dispatcher::RunType::Block);
212+
}
213+
160214
TEST_F(ScaledRangeTimerManagerTest, DisableWithZeroMinTime) {
161215
ScaledRangeTimerManagerImpl manager(dispatcher_);
162216

0 commit comments

Comments
 (0)