mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 09:11:26 +03:00
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:
committed by
Laxman Dewangan
parent
ae282d2c22
commit
9fdb78226d
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user