net: ipc_router: Fix xprt_info use after free issue

In SSR case the xprt_info pointer is freed without considering the users of
the pointer in TX path will leads to use after free of the pointer.

Use the reference count to keep track of the xprt_info structure and wait
for the all user to complete the task before free the xprt_info pointer.

CRs-Fixed: 999123
Change-Id: I894a877346ff1d395c6f1b06267dfec333cb1024
Signed-off-by: Arun Kumar Neelakantam <aneela@codeaurora.org>
This commit is contained in:
Arun Kumar Neelakantam 2016-04-07 18:46:20 +05:30 committed by Jeevan Shriram
parent 29a79eb7ca
commit 2bb44fdeb8

View file

@ -147,6 +147,8 @@ struct msm_ipc_router_xprt_info {
struct work_struct read_data;
struct workqueue_struct *workqueue;
void *log_ctx;
struct kref ref;
struct completion ref_complete;
};
#define RT_HASH_SIZE 4
@ -194,6 +196,9 @@ static void *ipc_router_get_log_ctx(char *sub_name);
static int process_resume_tx_msg(union rr_control_msg *msg,
struct rr_packet *pkt);
static void ipc_router_reset_conn(struct msm_ipc_router_remote_port *rport_ptr);
static int ipc_router_get_xprt_info_ref(
struct msm_ipc_router_xprt_info *xprt_info);
static void ipc_router_release_xprt_info_ref(struct kref *ref);
struct pil_vote_info {
void *pil_handle;
@ -2037,6 +2042,11 @@ static int forward_msg(struct msm_ipc_router_xprt_info *xprt_info,
down_read(&rt_entry->lock_lha4);
fwd_xprt_info = rt_entry->xprt_info;
ret = ipc_router_get_xprt_info_ref(fwd_xprt_info);
if (ret < 0) {
IPC_RTR_ERR("%s: Abort invalid xprt\n", __func__);
goto fm_error_xprt;
}
ret = prepend_header(pkt, fwd_xprt_info);
if (ret < 0) {
IPC_RTR_ERR("%s: Prepend Header failed\n", __func__);
@ -2071,6 +2081,8 @@ static int forward_msg(struct msm_ipc_router_xprt_info *xprt_info,
fm_error3:
mutex_unlock(&fwd_xprt_info->tx_lock_lhb2);
fm_error2:
kref_put(&fwd_xprt_info->ref, ipc_router_release_xprt_info_ref);
fm_error_xprt:
up_read(&rt_entry->lock_lha4);
fm_error1:
if (rt_entry)
@ -3037,6 +3049,13 @@ static int msm_ipc_router_write_pkt(struct msm_ipc_port *src,
}
down_read(&rt_entry->lock_lha4);
xprt_info = rt_entry->xprt_info;
ret = ipc_router_get_xprt_info_ref(xprt_info);
if (ret < 0) {
IPC_RTR_ERR("%s: Abort invalid xprt\n", __func__);
up_read(&rt_entry->lock_lha4);
kref_put(&rt_entry->ref, ipc_router_release_rtentry);
return ret;
}
ret = prepend_header(pkt, xprt_info);
if (ret < 0) {
IPC_RTR_ERR("%s: Prepend Header failed\n", __func__);
@ -3065,6 +3084,7 @@ out_write_pkt:
ipc_router_log_msg(xprt_info->log_ctx,
IPC_ROUTER_LOG_EVENT_TX_ERR, pkt, hdr, src, rport_ptr);
kref_put(&xprt_info->ref, ipc_router_release_xprt_info_ref);
return ret;
}
update_comm_mode_info(&src->mode_info, xprt_info);
@ -3082,6 +3102,7 @@ out_write_pkt:
(hdr->size & 0xffff));
}
kref_put(&xprt_info->ref, ipc_router_release_xprt_info_ref);
return hdr->size;
}
@ -3225,9 +3246,16 @@ static int msm_ipc_router_send_resume_tx(void *data)
__func__, hdr->src_node_id);
return -ENODEV;
}
ret = ipc_router_get_xprt_info_ref(rt_entry->xprt_info);
if (ret < 0) {
IPC_RTR_ERR("%s: Abort invalid xprt\n", __func__);
kref_put(&rt_entry->ref, ipc_router_release_rtentry);
return ret;
}
ret = ipc_router_send_ctl_msg(rt_entry->xprt_info, &msg,
hdr->src_node_id);
kref_put(&rt_entry->ref, ipc_router_release_rtentry);
kref_put(&rt_entry->xprt_info->ref, ipc_router_release_xprt_info_ref);
if (ret < 0)
IPC_RTR_ERR(
"%s: Send Resume_Tx Failed SRC_NODE: %d SRC_PORT: %d DEST_NODE: %d",
@ -3939,6 +3967,49 @@ static void *ipc_router_get_log_ctx(char *sub_name)
return log_ctx;
}
/**
* ipc_router_get_xprt_info_ref() - Get a reference to the xprt_info structure
* @xprt_info: pointer to the xprt_info.
*
* @return: Zero on success, -ENODEV on failure.
*
* This function is used to obtain a reference to the xprt_info structure
* corresponding to the requested @xprt_info pointer.
*/
static int ipc_router_get_xprt_info_ref(
struct msm_ipc_router_xprt_info *xprt_info)
{
int ret = -ENODEV;
struct msm_ipc_router_xprt_info *tmp_xprt_info;
down_read(&xprt_info_list_lock_lha5);
list_for_each_entry(tmp_xprt_info, &xprt_info_list, list) {
if (tmp_xprt_info == xprt_info) {
kref_get(&xprt_info->ref);
ret = 0;
break;
}
}
up_read(&xprt_info_list_lock_lha5);
return ret;
}
/**
* ipc_router_release_xprt_info_ref() - release the xprt_info last reference
* @ref: Reference to the xprt_info structure.
*
* This function is called when all references to the xprt_info structure
* are released.
*/
static void ipc_router_release_xprt_info_ref(struct kref *ref)
{
struct msm_ipc_router_xprt_info *xprt_info =
container_of(ref, struct msm_ipc_router_xprt_info, ref);
complete_all(&xprt_info->ref_complete);
}
static int msm_ipc_router_add_xprt(struct msm_ipc_router_xprt *xprt)
{
struct msm_ipc_router_xprt_info *xprt_info;
@ -3959,6 +4030,8 @@ static int msm_ipc_router_add_xprt(struct msm_ipc_router_xprt *xprt)
xprt_info->abort_data_read = 0;
INIT_WORK(&xprt_info->read_data, do_read_data);
INIT_LIST_HEAD(&xprt_info->list);
kref_init(&xprt_info->ref);
init_completion(&xprt_info->ref_complete);
xprt_info->workqueue = create_singlethread_workqueue(xprt->name);
if (!xprt_info->workqueue) {
@ -4022,6 +4095,10 @@ static void msm_ipc_router_remove_xprt(struct msm_ipc_router_xprt *xprt)
wakeup_source_trash(&xprt_info->ws);
kref_put(&xprt_info->ref,
ipc_router_release_xprt_info_ref);
wait_for_completion(&xprt_info->ref_complete);
xprt->priv = 0;
kfree(xprt_info);
}