From 4b24731640abfdc9fe97dba193cf7abe590ab2fe Mon Sep 17 00:00:00 2001 From: Corey Farrell <git@cfware.com> Date: Tue, 13 Nov 2018 11:51:00 -0500 Subject: [PATCH] test_res_pjsip_scheduler: Fix possible write after free in scheduler_policy. It's possible for a 4th task to be spawned before we cancel. This results in a write to the already freed test_data1. Wait long enough to verify success of the cancelation before freeing test_data1. Change-Id: I057e2fcbe97f8a175e50890be89c28c20490a20f --- tests/test_res_pjsip_scheduler.c | 39 ++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/tests/test_res_pjsip_scheduler.c b/tests/test_res_pjsip_scheduler.c index d0c2d90b52..cecb742a77 100644 --- a/tests/test_res_pjsip_scheduler.c +++ b/tests/test_res_pjsip_scheduler.c @@ -53,6 +53,7 @@ struct test_data { int interval; int sleep; int done; + int no_clear_done; struct ast_test *test; }; @@ -63,7 +64,9 @@ static int task_1(void *data) { struct test_data *test = data; - test->done = 0; + if (!test->no_clear_done) { + test->done = 0; + } test->task_start = ast_tvnow(); test->tid = pthread_self(); test->is_servant = ast_sip_thread_is_servant(); @@ -71,7 +74,7 @@ static int task_1(void *data) test->task_end = ast_tvnow(); ast_mutex_lock(&test->lock); - test->done = 1; + test->done++; ast_mutex_unlock(&test->lock); ast_cond_signal(&test->cond); @@ -345,11 +348,12 @@ AST_TEST_DEFINE(scheduler_policy) test_data1->test_start = ast_tvnow(); test_data1->interval = 1000; test_data1->sleep = 500; + test_data1->no_clear_done = 1; ast_mutex_init(&test_data1->lock); ast_cond_init(&test_data1->cond, NULL); ast_test_status_update(test, "This test will take about %3.1f seconds\n", - ((test_data1->interval * 3) + test_data1->sleep) / 1000.0); + ((test_data1->interval * 4) + test_data1->sleep) / 1000.0); task = ast_sip_schedule_task(NULL, test_data1->interval, task_1, "test_1", test_data1, AST_SIP_SCHED_TASK_DATA_NO_CLEANUP | AST_SIP_SCHED_TASK_PERIODIC); @@ -368,8 +372,33 @@ AST_TEST_DEFINE(scheduler_policy) ast_test_validate(test, when > test_data1->interval * 3 * 0.9 && when < test_data1->interval * 3 * 1.1); ast_sip_sched_task_cancel(task); - ao2_ref(task, -1); - task = NULL; + + /* Wait a full interval in case a 4th call to test_1 happened before the cancel */ + usleep(M2U(test_data1->interval)); + + ast_mutex_lock(&test_data1->lock); + if (test_data1->done) { + int done = test_data1->done; + + test_data1->done = 0; + ast_mutex_unlock(&test_data1->lock); + + ast_test_validate(test, done == 1); + + /* Wait two full intervals to be certain no further calls to test_1. */ + usleep(M2U(test_data1->interval * 2)); + + ast_mutex_lock(&test_data1->lock); + if (test_data1->done != 0) { + ast_mutex_unlock(&test_data1->lock); + /* The cancelation failed so we need to prevent cleanup of + * test_data1 to prevent a crash from write-after-free. */ + test_data1 = NULL; + ast_test_status_update(test, "Failed to cancel task"); + return AST_TEST_FAIL; + } + } + ast_mutex_unlock(&test_data1->lock); return AST_TEST_PASS; } -- GitLab