mirror of
https://github.com/isc-projects/bind9.git
synced 2026-05-28 04:34:54 -04:00
Fix a data race in isc_task_purgeevent()
When isc_task_purgeevent() is called for and 'event', the event, in the meanwhile, could in theory get processed, unlinked, and freed. So when the function then operates on the 'event', it causes a segmentation fault. The only place where isc_task_purgeevent() is called is from timer_purge(). In order to resolve the data race, call isc_task_purgeevent() inside the 'timer->lock' locked block, so that timerevent_destroy() won't be able to destroy the event if it was processed in the meanwhile, before isc_task_purgeevent() had a chance to purge it. In order to be able to do that, move the responsibility of calling isc_event_free() (upon a successful purge) out from the isc_task_purgeevent() function to its caller instead, so that it can be called outside of the timer->lock locked block.
This commit is contained in:
parent
857f6adaec
commit
0a48252b53
3 changed files with 26 additions and 6 deletions
|
|
@ -612,6 +612,15 @@ isc_task_purge(isc_task_t *task, void *sender, isc_eventtype_t type,
|
|||
return (isc_task_purgerange(task, sender, type, type, tag));
|
||||
}
|
||||
|
||||
/*
|
||||
* The caller is responsible for freeing the event if this function returns
|
||||
* true. If it returns false, then the event was already processed before it
|
||||
* could be purged, so the event's action is responsible for freeing the event.
|
||||
* The caller however must make sure that the event's destroy function, called
|
||||
* when the event's action calls isc_event_destroy(), doesn't free the event
|
||||
* while this function is still running. That is, 'event' must remain valid
|
||||
* throughout the whole execution of this function.
|
||||
*/
|
||||
bool
|
||||
isc_task_purgeevent(isc_task_t *task, isc_event_t *event) {
|
||||
bool found = false;
|
||||
|
|
@ -625,9 +634,7 @@ isc_task_purgeevent(isc_task_t *task, isc_event_t *event) {
|
|||
/*
|
||||
* If 'event' is on the task's event queue, it will be purged,
|
||||
* unless it is marked as unpurgeable. 'event' does not have to be
|
||||
* on the task's event queue; in fact, it can even be an invalid
|
||||
* pointer. Purging only occurs if the event is actually on the task's
|
||||
* event queue.
|
||||
* on the task's event queue.
|
||||
*
|
||||
* Purging never changes the state of the task.
|
||||
*/
|
||||
|
|
@ -644,8 +651,6 @@ isc_task_purgeevent(isc_task_t *task, isc_event_t *event) {
|
|||
return (false);
|
||||
}
|
||||
|
||||
isc_event_free(&event);
|
||||
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -231,11 +231,23 @@ timer_purge(isc_timer_t *timer) {
|
|||
|
||||
while ((event = ISC_LIST_HEAD(timer->active)) != NULL) {
|
||||
timerevent_unlink(timer, event);
|
||||
bool purged = isc_task_purgeevent(timer->task,
|
||||
(isc_event_t *)event);
|
||||
UNLOCK(&timer->lock);
|
||||
#if defined(UNIT_TESTING)
|
||||
usleep(100);
|
||||
#endif
|
||||
(void)isc_task_purgeevent(timer->task, (isc_event_t *)event);
|
||||
if (purged) {
|
||||
isc_event_free((isc_event_t **)&event);
|
||||
} else {
|
||||
/*
|
||||
* The event was processed while we were trying to
|
||||
* purge it. The event's action is responsible for
|
||||
* calling isc_event_free(), which in turn will call
|
||||
* event->ev_destroy() (timerevent_destroy() here),
|
||||
* which will unlink and destroy it.
|
||||
*/
|
||||
}
|
||||
LOCK(&timer->lock);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1398,6 +1398,9 @@ try_purgeevent(bool purgeable) {
|
|||
|
||||
purged = isc_task_purgeevent(task, event2_clone);
|
||||
assert_int_equal(purgeable, purged);
|
||||
if (purged) {
|
||||
isc_event_free(&event2_clone);
|
||||
}
|
||||
|
||||
/*
|
||||
* Unblock the task, allowing event processing.
|
||||
|
|
|
|||
Loading…
Reference in a new issue