icnss: Handle wait being interrupted

When event needs to be processed synchronously, event posting
thread waits for the completion. After completion, result from
the event work is retrieved and event buffer would be freed. But
if waiting thread gets interrupted then wait_for_completion API
returns failure and it also frees the buffer posted for
processing. Event work queue may accesses the freed buffer and
crash the system.
Fix the issue by properly synchronizing event buffer free between
caller and event work by checking for return value of
wait_for_completion.

CRs-fixed: 1057180
Change-Id: Ic3968fd4c0232da6bc9a97d94376f540f62bd2e6
Signed-off-by: Prashanth Bhatta <bhattap@codeaurora.org>
This commit is contained in:
Prashanth Bhatta 2016-08-20 20:02:45 -07:00 committed by Gerrit - the friendly Code Review server
parent e8a0e0808d
commit 7d45313e05

View file

@ -217,6 +217,8 @@ module_param(quirks, ulong, 0600);
void *icnss_ipc_log_context; void *icnss_ipc_log_context;
#define ICNSS_EVENT_PENDING 2989
enum icnss_driver_event_type { enum icnss_driver_event_type {
ICNSS_DRIVER_EVENT_SERVER_ARRIVE, ICNSS_DRIVER_EVENT_SERVER_ARRIVE,
ICNSS_DRIVER_EVENT_SERVER_EXIT, ICNSS_DRIVER_EVENT_SERVER_EXIT,
@ -486,6 +488,7 @@ static int icnss_driver_event_post(enum icnss_driver_event_type type,
event->type = type; event->type = type;
event->data = data; event->data = data;
init_completion(&event->complete); init_completion(&event->complete);
event->ret = ICNSS_EVENT_PENDING;
event->sync = sync; event->sync = sync;
spin_lock_irqsave(&penv->event_lock, flags); spin_lock_irqsave(&penv->event_lock, flags);
@ -494,12 +497,26 @@ static int icnss_driver_event_post(enum icnss_driver_event_type type,
penv->stats.events[type].posted++; penv->stats.events[type].posted++;
queue_work(penv->event_wq, &penv->event_work); queue_work(penv->event_wq, &penv->event_work);
if (sync) {
ret = wait_for_completion_interruptible(&event->complete); if (!sync)
if (ret == 0) return ret;
ret = event->ret;
kfree(event); ret = wait_for_completion_interruptible(&event->complete);
icnss_pr_dbg("Completed event: %s(%d), state: 0x%lx, ret: %d/%d\n",
icnss_driver_event_to_str(type), type, penv->state, ret,
event->ret);
spin_lock_irqsave(&penv->event_lock, flags);
if (ret == -ERESTARTSYS && event->ret == ICNSS_EVENT_PENDING) {
event->sync = false;
spin_unlock_irqrestore(&penv->event_lock, flags);
return ret;
} }
spin_unlock_irqrestore(&penv->event_lock, flags);
ret = event->ret;
kfree(event);
return ret; return ret;
} }
@ -2071,11 +2088,15 @@ static void icnss_driver_event_work(struct work_struct *work)
penv->stats.events[event->type].processed++; penv->stats.events[event->type].processed++;
spin_lock_irqsave(&penv->event_lock, flags);
if (event->sync) { if (event->sync) {
event->ret = ret; event->ret = ret;
complete(&event->complete); complete(&event->complete);
} else continue;
kfree(event); }
spin_unlock_irqrestore(&penv->event_lock, flags);
kfree(event);
spin_lock_irqsave(&penv->event_lock, flags); spin_lock_irqsave(&penv->event_lock, flags);
} }
@ -2372,6 +2393,9 @@ int icnss_register_driver(struct icnss_driver_ops *ops)
ret = icnss_driver_event_post(ICNSS_DRIVER_EVENT_REGISTER_DRIVER, ret = icnss_driver_event_post(ICNSS_DRIVER_EVENT_REGISTER_DRIVER,
true, ops); true, ops);
if (ret == -ERESTARTSYS)
ret = 0;
out: out:
return ret; return ret;
} }