video: tegra: nvmap: fix use-after-free race condition

Incremented nvmap_handle ref count in utility function
nvmap_get_id_from_dmabuf_fd() before the function release reference
to dma buffer. This is required to avoid race conditions in nvmap
code where nvmap_handle returned by this function could be freed
concurrently while the caller is still using it.

As a side effect of above change, every caller of this utility
function must decrement nvmap_handle ref count after using the
returned nvmap_handle.

Bug: 17453812

Change-Id: Iffc2e5819f8b493d5ed95a9d0c422ccd52438965
Signed-off-by: Maneet Singh <mmaneetsingh@nvidia.com>
(cherry picked from commit 0b4d06100bf603c80f29f8f2b19537929fce1807)
diff --git a/drivers/video/tegra/nvmap/nvmap_dmabuf.c b/drivers/video/tegra/nvmap/nvmap_dmabuf.c
index 6f47c49..e0ade75 100644
--- a/drivers/video/tegra/nvmap/nvmap_dmabuf.c
+++ b/drivers/video/tegra/nvmap/nvmap_dmabuf.c
@@ -684,6 +684,11 @@
 /*
  * Returns the nvmap handle ID associated with the passed dma_buf's fd. This
  * does not affect the ref count of the dma_buf.
+ * NOTE: Callers of this utility function must invoke nvmap_handle_put after
+ * using the returned nvmap_handle. Call to nvmap_handle_get is required in
+ * this utility function to avoid race conditions in code where nvmap_handle
+ * returned by this function is freed concurrently while the caller is still
+ * using it.
  */
 struct nvmap_handle *nvmap_get_id_from_dmabuf_fd(struct nvmap_client *client,
 						 int fd)
@@ -698,6 +703,8 @@
 	if (dmabuf->ops == &nvmap_dma_buf_ops) {
 		info = dmabuf->priv;
 		handle = info->handle;
+		if (!nvmap_handle_get(handle))
+			handle = ERR_PTR(-EINVAL);
 	}
 	dma_buf_put(dmabuf);
 	return handle;
@@ -719,6 +726,7 @@
 		return -EINVAL;
 
 	op.fd = nvmap_get_dmabuf_fd(client, handle);
+	nvmap_handle_put(handle);
 	if (op.fd < 0)
 		return op.fd;
 
diff --git a/drivers/video/tegra/nvmap/nvmap_handle.c b/drivers/video/tegra/nvmap/nvmap_handle.c
index a1eb641..f7646ae 100644
--- a/drivers/video/tegra/nvmap/nvmap_handle.c
+++ b/drivers/video/tegra/nvmap/nvmap_handle.c
@@ -428,7 +428,11 @@
 void nvmap_free_handle_user_id(struct nvmap_client *client,
 			       unsigned long user_id)
 {
-	nvmap_free_handle(client, unmarshal_user_handle(user_id));
+	struct nvmap_handle *handle = unmarshal_user_handle(user_id);
+	if (handle) {
+		nvmap_free_handle(client, handle);
+		nvmap_handle_put(handle);
+	}
 }
 
 static void add_handle_ref(struct nvmap_client *client,
@@ -599,6 +603,7 @@
 	if (IS_ERR(handle))
 		return ERR_CAST(handle);
 	ref = nvmap_duplicate_handle(client, handle, 1);
+	nvmap_handle_put(handle);
 	return ref;
 }
 
diff --git a/drivers/video/tegra/nvmap/nvmap_ioctl.c b/drivers/video/tegra/nvmap/nvmap_ioctl.c
index 7a60fb6..f942920 100644
--- a/drivers/video/tegra/nvmap/nvmap_ioctl.c
+++ b/drivers/video/tegra/nvmap/nvmap_ioctl.c
@@ -48,6 +48,9 @@
 			 unsigned long sys_stride, unsigned long elem_size,
 			 unsigned long count);
 
+/* NOTE: Callers of this utility function must invoke nvmap_handle_put after
+ * using the returned nvmap_handle.
+ */
 struct nvmap_handle *unmarshal_user_handle(__u32 handle)
 {
 	struct nvmap_handle *h;
@@ -105,8 +108,8 @@
 	struct nvmap_handle *on_stack[16];
 	struct nvmap_handle **refs;
 	unsigned long __user *output = NULL;
-	unsigned int i;
 	int err = 0;
+	u32 i, n_unmarshal_handles = 0;
 
 #ifdef CONFIG_COMPAT
 	if (is32) {
@@ -149,6 +152,7 @@
 				err = -EINVAL;
 				goto out;
 			}
+			n_unmarshal_handles++;
 		}
 	} else {
 		refs = on_stack;
@@ -158,6 +162,7 @@
 			err = -EINVAL;
 			goto out;
 		}
+		n_unmarshal_handles++;
 	}
 
 	trace_nvmap_ioctl_pinop(filp->private_data, is_pin, op.count, refs);
@@ -220,6 +225,9 @@
 		nvmap_unpin_ids(filp->private_data, op.count, refs);
 
 out:
+	for (i = 0; i < n_unmarshal_handles; i++)
+		nvmap_handle_put(refs[i]);
+
 	if (refs != on_stack)
 		kfree(refs);
 
@@ -238,11 +246,6 @@
 	if (!h)
 		return -EINVAL;
 
-	h = nvmap_handle_get(h);
-
-	if (!h)
-		return -EPERM;
-
 	op.id = marshal_id(h);
 	nvmap_handle_put(h);
 
@@ -284,6 +287,7 @@
 		return -EINVAL;
 
 	op.fd = nvmap_get_dmabuf_fd(client, handle);
+	nvmap_handle_put(handle);
 	if (op.fd < 0)
 		return op.fd;
 
@@ -299,24 +303,27 @@
 	struct nvmap_alloc_handle op;
 	struct nvmap_client *client = filp->private_data;
 	struct nvmap_handle *handle;
+	int err;
 
 	if (copy_from_user(&op, arg, sizeof(op)))
 		return -EFAULT;
 
-	handle = unmarshal_user_handle(op.handle);
-	if (!handle)
+	if (op.align & (op.align - 1))
 		return -EINVAL;
 
-	if (op.align & (op.align - 1))
+	handle = unmarshal_user_handle(op.handle);
+	if (!handle)
 		return -EINVAL;
 
 	/* user-space handles are aligned to page boundaries, to prevent
 	 * data leakage. */
 	op.align = max_t(size_t, op.align, PAGE_SIZE);
 
-	return nvmap_alloc_handle(client, handle, op.heap_mask, op.align,
+	err = nvmap_alloc_handle(client, handle, op.heap_mask, op.align,
 				  0, /* no kind */
 				  op.flags & (~NVMAP_HANDLE_KIND_SPECIFIED));
+	nvmap_handle_put(handle);
+	return err;
 }
 
 int nvmap_ioctl_alloc_kind(struct file *filp, void __user *arg)
@@ -324,26 +331,29 @@
 	struct nvmap_alloc_kind_handle op;
 	struct nvmap_client *client = filp->private_data;
 	struct nvmap_handle *handle;
+	int err;
 
 	if (copy_from_user(&op, arg, sizeof(op)))
 		return -EFAULT;
 
-	handle = unmarshal_user_handle(op.handle);
-	if (!handle)
+	if (op.align & (op.align - 1))
 		return -EINVAL;
 
-	if (op.align & (op.align - 1))
+	handle = unmarshal_user_handle(op.handle);
+	if (!handle)
 		return -EINVAL;
 
 	/* user-space handles are aligned to page boundaries, to prevent
 	 * data leakage. */
 	op.align = max_t(size_t, op.align, PAGE_SIZE);
 
-	return nvmap_alloc_handle(client, handle,
+	err = nvmap_alloc_handle(client, handle,
 				  op.heap_mask,
 				  op.align,
 				  op.kind,
 				  op.flags);
+	nvmap_handle_put(handle);
+	return err;
 }
 
 int nvmap_create_fd(struct nvmap_client *client, struct nvmap_handle *h)
@@ -436,15 +446,9 @@
 			return -EFAULT;
 
 	h = unmarshal_user_handle(op.handle);
-
 	if (!h)
 		return -EINVAL;
 
-	h = nvmap_handle_get(h);
-
-	if (!h)
-		return -EPERM;
-
 	if(!h->alloc) {
 		nvmap_handle_put(h);
 		return -EFAULT;
@@ -537,10 +541,6 @@
 	if (!h)
 		return -EINVAL;
 
-	h = nvmap_handle_get(h);
-	if (!h)
-		return -EINVAL;
-
 	nvmap_ref_lock(client);
 	ref = __nvmap_validate_locked(client, h);
 	if (IS_ERR_OR_NULL(ref)) {
@@ -593,13 +593,12 @@
 		if (copy_from_user(&op, arg, sizeof(op)))
 			return -EFAULT;
 
-	h = unmarshal_user_handle(op.handle);
-	if (!h || !op.addr || !op.count || !op.elem_size)
+	if (!op.addr || !op.count || !op.elem_size)
 		return -EINVAL;
 
-	h = nvmap_handle_get(h);
+	h = unmarshal_user_handle(op.handle);
 	if (!h)
-		return -EPERM;
+		return -EINVAL;
 
 	trace_nvmap_ioctl_rw_handle(client, h, is_read, op.offset,
 				    op.addr, op.hmem_stride,
@@ -636,11 +635,14 @@
 	unsigned long end;
 	int err = 0;
 
-	handle = unmarshal_user_handle(op->handle);
-	if (!handle || !op->addr || op->op < NVMAP_CACHE_OP_WB ||
+	if (!op->addr || op->op < NVMAP_CACHE_OP_WB ||
 	    op->op > NVMAP_CACHE_OP_WB_INV)
 		return -EINVAL;
 
+	handle = unmarshal_user_handle(op->handle);
+	if (!handle)
+		return -EINVAL;
+
 	down_read(&current->mm->mmap_sem);
 
 	vma = find_vma(current->active_mm, (unsigned long)op->addr);
@@ -667,6 +669,7 @@
 				     false);
 out:
 	up_read(&current->mm->mmap_sem);
+	nvmap_handle_put(handle);
 	return err;
 }
 
@@ -1127,7 +1130,8 @@
 	u32 *offset_ptr;
 	u32 *size_ptr;
 	struct nvmap_handle **refs;
-	int i, err = 0;
+	int err = 0;
+	u32 i, n_unmarshal_handles = 0;
 
 	if (copy_from_user(&op, arg, sizeof(op)))
 		return -EFAULT;
@@ -1169,6 +1173,7 @@
 			err = -EINVAL;
 			goto free_mem;
 		}
+		n_unmarshal_handles++;
 	}
 
 	if (is_reserve_ioctl)
@@ -1179,6 +1184,8 @@
 						op.op, op.nr);
 
 free_mem:
+	for (i = 0; i < n_unmarshal_handles; i++)
+		nvmap_handle_put(refs[i]);
 	kfree(refs);
 	return err;
 }