video: tegra: nvmap: Fix data race for RO dma-buf

There is a potential data race for RO dma-buf in the following scenario:
------------------------------------------------------------------
Process 1            | Process 2            | Process 3           |
------------------------------------------------------------------|
AllocAttr handle H1  |                      |                     |
MemMap (H1)          |                      |                     |
AllocAttr(H2)        |                      |                     |
MemMap(H2)           |                      |                     |
id1 = GetSciIpcId(H1)|                      |                     |
id2 = GetSciIpcId(H2)|H3=HandleFromSciIpcId |                     |
id3 = GetSciIpcId(H1)| (id1, RO)            |H4=HandleFromSciIpcId|
MemUnmap(H2)         |QueryHandlePararms(H3)|(id2, RO)            |
MemUnmap(H1)         |MemMap(H3)            |QueryHandleParams(H4)|
HandleFree(H2)       |MemUnmap(H3)          |MemMap(H4)           |
HandleFree(H1)       |HandleFree(H3)        |H5=HandleFromSciIpcId|
                     |                      |(id3, RO)            |
                     |                      |QueryHandleParams(H5)|
                     |                      |MemMap(H5)           |
                     |                      |MemUnmap(H4)         |
                     |                      |MemUnmap(H5)         |
                     |                      |HandleFree(H4)       |
                     |                      |HandleFree(H5)       |
-------------------------------------------------------------------

The race is happening between the HandleFree(H3) in process 2 and
HandleFromSciIpcId(id3, RO) in process 3. Process 2 tries to free the
H3, and function nvmap_free_handle decrements the RO dma-buf's counter,
so that it reaches 0, but nvmap_dmabuf_release is not called immediately
because of which the process 3 get's false value for the following check
if (is_ro && h->dmabuf_ro == NULL)
It results in calling nvmap_duplicate_handle and then meanwhile function
nvmap_dmabuf_release is called and it makes h->dmabuf_ro to NULL. Hence
get_dma_buf fails with null pointer dereference error.

Fix this issue with following approach:
- Before using dmabuf_ro, take the handle->lock, then check if it is not
NULL.
- If not NULL, then call get_file_rcu on the file associated with RO
dma-buf and check return value.
- If return value is false, then dma-buf's ref counter is zero and it is
going away. So wait until dmabuf_ro is set to NULL; and then create a
new dma-buf for RO.
- Otherwise, use the existing RO dma-buf and decrement the refcount
taken with get_file_rcu.

Bug 3741751

Change-Id: I8987efebc476a794b240ca968b7915b4263ba664
Signed-off-by: Ketan Patil <ketanp@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nvidia/+/2850394
Reviewed-by: svcacv <svcacv@nvidia.com>
Reviewed-by: svc_kernel_abi <svc_kernel_abi@nvidia.com>
Reviewed-by: svc-mobile-coverity <svc-mobile-coverity@nvidia.com>
Reviewed-by: Krishna Reddy <vdumpa@nvidia.com>
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
This commit is contained in:
Ketan Patil
2023-01-30 10:32:26 +00:00
committed by Laxman Dewangan
parent ae282d2c22
commit 9fdb78226d
4 changed files with 68 additions and 8 deletions

View File

@@ -1,7 +1,7 @@
/*
* dma_buf exporter for nvmap
*
* Copyright (c) 2012-2022, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2012-2023, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
@@ -217,6 +217,7 @@ static void nvmap_dmabuf_release(struct dma_buf *dmabuf)
if (info->is_ro) {
BUG_ON(dmabuf != info->handle->dmabuf_ro);
info->handle->dmabuf_ro = NULL;
wake_up(&info->handle->waitq);
} else {
BUG_ON(dmabuf != info->handle->dmabuf);
info->handle->dmabuf = NULL;

View File

@@ -27,6 +27,7 @@
#include <linux/moduleparam.h>
#include <linux/nvmap.h>
#include <linux/version.h>
#include <linux/wait.h>
#if KERNEL_VERSION(4, 15, 0) > LINUX_VERSION_CODE
#include <soc/tegra/chip-id.h>
#else
@@ -247,6 +248,7 @@ struct nvmap_handle_ref *nvmap_create_handle(struct nvmap_client *client,
INIT_LIST_HEAD(&h->dmabuf_priv);
INIT_LIST_HEAD(&h->pg_ref_h);
init_waitqueue_head(&h->waitq);
/*
* This takes out 1 ref on the dambuf. This corresponds to the
* handle_ref that gets automatically made by nvmap_create_handle().
@@ -474,6 +476,7 @@ struct nvmap_handle_ref *nvmap_dup_handle_ro(struct nvmap_client *client,
struct nvmap_handle *h;
struct nvmap_handle_ref *ref = NULL;
bool dmabuf_created = false;
long remain;
if (!client)
return ERR_PTR(-EINVAL);
@@ -488,14 +491,38 @@ struct nvmap_handle_ref *nvmap_dup_handle_ro(struct nvmap_client *client,
return ERR_CAST(h);
}
mutex_lock(&h->lock);
if (h->dmabuf_ro == NULL) {
h->dmabuf_ro = __nvmap_make_dmabuf(client, h, true);
if (IS_ERR(h->dmabuf_ro)) {
nvmap_handle_put(h);
mutex_unlock(&h->lock);
return ERR_CAST(h->dmabuf_ro);
}
dmabuf_created = true;
} else {
if (!get_file_rcu(h->dmabuf_ro->file)) {
mutex_unlock(&h->lock);
remain = wait_event_interruptible_timeout(h->waitq,
!h->dmabuf_ro, (long)msecs_to_jiffies(100U));
if (remain > 0 && !h->dmabuf_ro) {
mutex_lock(&h->lock);
h->dmabuf_ro = __nvmap_make_dmabuf(client, h, true);
if (IS_ERR(h->dmabuf_ro)) {
nvmap_handle_put(h);
mutex_unlock(&h->lock);
return ERR_CAST(h->dmabuf_ro);
}
dmabuf_created = true;
} else {
nvmap_handle_put(h);
return ERR_PTR(-EINVAL);
}
} else {
dma_buf_put(h->dmabuf_ro);
}
}
mutex_unlock(&h->lock);
ref = nvmap_duplicate_handle(client, h, false, true);
if (!ref) {

View File

@@ -272,6 +272,10 @@ struct nvmap_handle {
struct list_head pg_ref_h;
struct mutex pg_ref_h_lock;
bool is_subhandle;
/*
* waitq to wait on RO dmabuf release completion, if release is already in progress.
*/
wait_queue_head_t waitq;
};
struct nvmap_handle_info {

View File

@@ -3,7 +3,7 @@
*
* mapping between nvmap_hnadle and sci_ipc entery
*
* Copyright (c) 2019-2022, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2019-2023, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
@@ -22,6 +22,7 @@
#include <linux/rbtree.h>
#include <linux/list.h>
#include <linux/mman.h>
#include <linux/wait.h>
#include <linux/nvscierror.h>
#include <linux/nvsciipc_interface.h>
@@ -224,6 +225,7 @@ int nvmap_get_handle_from_sci_ipc_id(struct nvmap_client *client, u32 flags,
struct nvmap_sci_ipc_entry *entry;
struct dma_buf *dmabuf = NULL;
struct nvmap_handle *h;
long remain;
int ret = 0;
int fd;
@@ -245,14 +247,40 @@ int nvmap_get_handle_from_sci_ipc_id(struct nvmap_client *client, u32 flags,
h = entry->handle;
if (is_ro && h->dmabuf_ro == NULL) {
h->dmabuf_ro = __nvmap_make_dmabuf(client, h, true);
if (IS_ERR(h->dmabuf_ro)) {
ret = PTR_ERR(h->dmabuf_ro);
goto unlock;
mutex_lock(&h->lock);
if (is_ro) {
if (h->dmabuf_ro == NULL) {
h->dmabuf_ro = __nvmap_make_dmabuf(client, h, true);
if (IS_ERR(h->dmabuf_ro)) {
ret = PTR_ERR(h->dmabuf_ro);
mutex_unlock(&h->lock);
goto unlock;
}
dmabuf_created = true;
} else {
if (!get_file_rcu(h->dmabuf_ro->file)) {
mutex_unlock(&h->lock);
remain = wait_event_interruptible_timeout(h->waitq,
!h->dmabuf_ro, (long)msecs_to_jiffies(100U));
if (remain > 0 && !h->dmabuf_ro) {
mutex_lock(&h->lock);
h->dmabuf_ro = __nvmap_make_dmabuf(client, h, true);
if (IS_ERR(h->dmabuf_ro)) {
ret = PTR_ERR(h->dmabuf_ro);
mutex_unlock(&h->lock);
goto unlock;
}
dmabuf_created = true;
} else {
ret = -EINVAL;
goto unlock;
}
} else {
dma_buf_put(h->dmabuf_ro);
}
}
dmabuf_created = true;
}
mutex_unlock(&h->lock);
ref = nvmap_duplicate_handle(client, h, false, is_ro);
if (!ref) {