Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6b6b1c62 authored by Manish Poddar's avatar Manish Poddar Committed by Gerrit - the friendly Code Review server
Browse files

msm: camera: Fix Use after free bug in msm_vb2.c.



There is no syncronization between msm_vb2_get_buf
and msm_delete_stream which can lead to use after
free.
Fixed it by using read/write lock.

Change-Id: I8e80d70ec866253aab8836457a28ae14175f5d61
Signed-off-by: default avatarManish Poddar <mpoddar@codeaurora.org>
parent a62f4296
Loading
Loading
Loading
Loading
+76 −15
Original line number Diff line number Diff line
@@ -32,7 +32,6 @@
#include "cam_hw_ops.h"
#include <media/msmb_generic_buf_mgr.h>


static struct v4l2_device *msm_v4l2_dev;
static struct list_head    ordered_sd_list;

@@ -149,7 +148,7 @@ typedef int (*msm_queue_find_func)(void *d1, void *d2);
#define msm_queue_find(queue, type, member, func, data) ({\
	unsigned long flags;					\
	struct msm_queue_head *__q = (queue);			\
	type *node = 0; \
	type *node = NULL; \
	typeof(node) __ret = NULL; \
	msm_queue_find_func __f = (func); \
	spin_lock_irqsave(&__q->lock, flags);			\
@@ -279,22 +278,47 @@ void msm_delete_stream(unsigned int session_id, unsigned int stream_id)
	struct msm_session *session = NULL;
	struct msm_stream  *stream = NULL;
	unsigned long flags;
	int try_count = 0;

	session = msm_queue_find(msm_session_q, struct msm_session,
		list, __msm_queue_find_session, &session_id);

	if (!session)
		return;

	while (1) {

		if (try_count > 5) {
			pr_err("%s : not able to delete stream %d\n",
				__func__, __LINE__);
			break;
		}

		write_lock(&session->stream_rwlock);
		try_count++;
		stream = msm_queue_find(&session->stream_q, struct msm_stream,
			list, __msm_queue_find_stream, &stream_id);
	if (!stream)

		if (!stream) {
			write_unlock(&session->stream_rwlock);
			return;
		}

		if (msm_vb2_get_stream_state(stream) != 1) {
			write_unlock(&session->stream_rwlock);
			continue;
		}

		spin_lock_irqsave(&(session->stream_q.lock), flags);
		list_del_init(&stream->list);
		session->stream_q.len--;
		kfree(stream);
		stream = NULL;
		spin_unlock_irqrestore(&(session->stream_q.lock), flags);
		write_unlock(&session->stream_rwlock);
		break;
	}

}
EXPORT_SYMBOL(msm_delete_stream);

@@ -444,6 +468,7 @@ int msm_create_session(unsigned int session_id, struct video_device *vdev)
	mutex_init(&session->lock);
	mutex_init(&session->lock_q);
	mutex_init(&session->close_lock);
	rwlock_init(&session->stream_rwlock);

	if (gpu_limit) {
		session->sysfs_pwr_limit = kgsl_pwr_limits_add(KGSL_DEVICE_3D0);
@@ -1048,17 +1073,25 @@ static struct v4l2_file_operations msm_fops = {
#endif
};

struct msm_stream *msm_get_stream(unsigned int session_id,
	unsigned int stream_id)
struct msm_session *msm_get_session(unsigned int session_id)
{
	struct msm_session *session;
	struct msm_stream *stream;

	session = msm_queue_find(msm_session_q, struct msm_session,
		list, __msm_queue_find_session, &session_id);
	if (!session)
		return ERR_PTR(-EINVAL);

	return session;
}
EXPORT_SYMBOL(msm_get_session);


struct msm_stream *msm_get_stream(struct msm_session *session,
	unsigned int stream_id)
{
	struct msm_stream *stream;

	stream = msm_queue_find(&session->stream_q, struct msm_stream,
		list, __msm_queue_find_stream, &stream_id);

@@ -1115,6 +1148,34 @@ struct msm_stream *msm_get_stream_from_vb2q(struct vb2_queue *q)
}
EXPORT_SYMBOL(msm_get_stream_from_vb2q);

struct msm_session *msm_get_session_from_vb2q(struct vb2_queue *q)
{
	struct msm_session *session;
	struct msm_stream *stream;
	unsigned long flags1;
	unsigned long flags2;

	spin_lock_irqsave(&msm_session_q->lock, flags1);
	list_for_each_entry(session, &(msm_session_q->list), list) {
		spin_lock_irqsave(&(session->stream_q.lock), flags2);
		list_for_each_entry(
			stream, &(session->stream_q.list), list) {
			if (stream->vb2_q == q) {
				spin_unlock_irqrestore
					(&(session->stream_q.lock), flags2);
				spin_unlock_irqrestore
					(&msm_session_q->lock, flags1);
				return session;
			}
		}
		spin_unlock_irqrestore(&(session->stream_q.lock), flags2);
	}
	spin_unlock_irqrestore(&msm_session_q->lock, flags1);
	return NULL;
}
EXPORT_SYMBOL(msm_get_session_from_vb2q);


#ifdef CONFIG_COMPAT
long msm_copy_camera_private_ioctl_args(unsigned long arg,
	struct msm_camera_private_ioctl_arg *k_ioctl,
+4 −1
Original line number Diff line number Diff line
@@ -111,6 +111,7 @@ struct msm_session {
	struct mutex lock;
	struct mutex lock_q;
	struct mutex close_lock;
	rwlock_t	stream_rwlock;
	struct kgsl_pwr_limit *sysfs_pwr_limit;
};

@@ -129,11 +130,13 @@ int msm_create_stream(unsigned int session_id,
void msm_delete_stream(unsigned int session_id, unsigned int stream_id);
int  msm_create_command_ack_q(unsigned int session_id, unsigned int stream_id);
void msm_delete_command_ack_q(unsigned int session_id, unsigned int stream_id);
struct msm_stream *msm_get_stream(unsigned int session_id,
struct msm_session *msm_get_session(unsigned int session_id);
struct msm_stream *msm_get_stream(struct msm_session *session,
	unsigned int stream_id);
struct vb2_queue *msm_get_stream_vb2q(unsigned int session_id,
	unsigned int stream_id);
struct msm_stream *msm_get_stream_from_vb2q(struct vb2_queue *q);
struct msm_session *msm_get_session_from_vb2q(struct vb2_queue *q);
struct msm_session *msm_session_find(unsigned int session_id);
#ifdef CONFIG_COMPAT
long msm_copy_camera_private_ioctl_args(unsigned long arg,
+132 −14
Original line number Diff line number Diff line
/* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved.
/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -44,17 +44,25 @@ static int msm_vb2_queue_setup(struct vb2_queue *q,
int msm_vb2_buf_init(struct vb2_buffer *vb)
{
	struct msm_stream *stream;
	struct msm_session *session;
	struct msm_vb2_buffer *msm_vb2_buf;
	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);

	session = msm_get_session_from_vb2q(vb->vb2_queue);
	if (IS_ERR_OR_NULL(session))
		return -EINVAL;

	read_lock(&session->stream_rwlock);

	stream = msm_get_stream_from_vb2q(vb->vb2_queue);
	if (!stream) {
		pr_err("%s: Couldn't find stream\n", __func__);
		read_unlock(&session->stream_rwlock);
		return -EINVAL;
	}
	msm_vb2_buf = container_of(vbuf, struct msm_vb2_buffer, vb2_v4l2_buf);
	msm_vb2_buf->in_freeq = 0;

	read_unlock(&session->stream_rwlock);
	return 0;
}

@@ -62,6 +70,7 @@ static void msm_vb2_buf_queue(struct vb2_buffer *vb)
{
	struct msm_vb2_buffer *msm_vb2;
	struct msm_stream *stream;
	struct msm_session *session;
	unsigned long flags;
	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);

@@ -71,21 +80,30 @@ static void msm_vb2_buf_queue(struct vb2_buffer *vb)
		return;
	}

	session = msm_get_session_from_vb2q(vb->vb2_queue);
	if (IS_ERR_OR_NULL(session))
		return;

	read_lock(&session->stream_rwlock);

	stream = msm_get_stream_from_vb2q(vb->vb2_queue);
	if (!stream) {
		pr_err("%s:%d] NULL stream", __func__, __LINE__);
		read_unlock(&session->stream_rwlock);
		return;
	}

	spin_lock_irqsave(&stream->stream_lock, flags);
	list_add_tail(&msm_vb2->list, &stream->queued_list);
	spin_unlock_irqrestore(&stream->stream_lock, flags);
	read_unlock(&session->stream_rwlock);
}

static void msm_vb2_buf_finish(struct vb2_buffer *vb)
{
	struct msm_vb2_buffer *msm_vb2;
	struct msm_stream *stream;
	struct msm_session *session;
	unsigned long flags;
	struct msm_vb2_buffer *msm_vb2_entry, *temp;
	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
@@ -96,9 +114,16 @@ static void msm_vb2_buf_finish(struct vb2_buffer *vb)
		return; 
	}

	session = msm_get_session_from_vb2q(vb->vb2_queue);
	if (IS_ERR_OR_NULL(session))
		return;

	read_lock(&session->stream_rwlock);

	stream = msm_get_stream_from_vb2q(vb->vb2_queue);
	if (!stream) {
		pr_err("%s:%d] NULL stream", __func__, __LINE__);
		read_unlock(&session->stream_rwlock);
		return;
	}

@@ -111,6 +136,7 @@ static void msm_vb2_buf_finish(struct vb2_buffer *vb)
		}
	}
	spin_unlock_irqrestore(&stream->stream_lock, flags);
	read_unlock(&session->stream_rwlock);
	return;
}

@@ -118,12 +144,20 @@ static void msm_vb2_stop_stream(struct vb2_queue *q)
{
	struct msm_vb2_buffer *msm_vb2, *temp;
	struct msm_stream *stream;
	struct msm_session *session;
	unsigned long flags;
	struct vb2_v4l2_buffer *vb2_v4l2_buf;

	session = msm_get_session_from_vb2q(q);
	if (IS_ERR_OR_NULL(session))
		return;

	read_lock(&session->stream_rwlock);

	stream = msm_get_stream_from_vb2q(q);
	if (!stream) {
		pr_err_ratelimited("%s:%d] NULL stream", __func__, __LINE__);
		read_unlock(&session->stream_rwlock);
		return;
	}

@@ -143,7 +177,27 @@ static void msm_vb2_stop_stream(struct vb2_queue *q)
			msm_vb2->in_freeq = 0;
		}
	spin_unlock_irqrestore(&stream->stream_lock, flags);
	read_unlock(&session->stream_rwlock);
}

int msm_vb2_get_stream_state(struct msm_stream *stream)
{
	struct msm_vb2_buffer *msm_vb2, *temp;
	unsigned long flags;
	int rc = 1;

	spin_lock_irqsave(&stream->stream_lock, flags);
	list_for_each_entry_safe(msm_vb2, temp, &(stream->queued_list), list) {
		if (msm_vb2->in_freeq != 0) {
			rc = 0;
			break;
		}
	}
	spin_unlock_irqrestore(&stream->stream_lock, flags);
	return rc;
}
EXPORT_SYMBOL(msm_vb2_get_stream_state);


static struct vb2_ops msm_vb2_get_q_op = {
	.queue_setup	= msm_vb2_queue_setup,
@@ -198,14 +252,23 @@ static struct vb2_v4l2_buffer *msm_vb2_get_buf(int session_id,
	unsigned int stream_id)
{
	struct msm_stream *stream;
	struct msm_session *session;
	struct vb2_v4l2_buffer *vb2_v4l2_buf = NULL;
	struct msm_vb2_buffer *msm_vb2 = NULL;
	unsigned long flags;

	stream = msm_get_stream(session_id, stream_id);
	if (IS_ERR_OR_NULL(stream))
	session = msm_get_session(session_id);
	if (IS_ERR_OR_NULL(session))
		return NULL;

	read_lock(&session->stream_rwlock);

	stream = msm_get_stream(session, stream_id);
	if (IS_ERR_OR_NULL(stream)) {
		read_unlock(&session->stream_rwlock);
		return NULL;
	}

	spin_lock_irqsave(&stream->stream_lock, flags);

	if (!stream->vb2_q) {
@@ -228,6 +291,7 @@ static struct vb2_v4l2_buffer *msm_vb2_get_buf(int session_id,
	vb2_v4l2_buf = NULL;
end:
	spin_unlock_irqrestore(&stream->stream_lock, flags);
	read_unlock(&session->stream_rwlock);
	return vb2_v4l2_buf;
}

@@ -235,13 +299,23 @@ static struct vb2_v4l2_buffer *msm_vb2_get_buf_by_idx(int session_id,
	unsigned int stream_id, uint32_t index)
{
	struct msm_stream *stream;
	struct msm_session *session;
	struct vb2_v4l2_buffer *vb2_v4l2_buf = NULL;
	struct msm_vb2_buffer *msm_vb2 = NULL;
	unsigned long flags;

	stream = msm_get_stream(session_id, stream_id);
	if (IS_ERR_OR_NULL(stream))
	session = msm_get_session(session_id);
	if (IS_ERR_OR_NULL(session))
		return NULL;

	read_lock(&session->stream_rwlock);

	stream = msm_get_stream(session, stream_id);

	if (IS_ERR_OR_NULL(stream)) {
		read_unlock(&session->stream_rwlock);
		return NULL;
	}

	spin_lock_irqsave(&stream->stream_lock, flags);

@@ -263,6 +337,7 @@ static struct vb2_v4l2_buffer *msm_vb2_get_buf_by_idx(int session_id,
	vb2_v4l2_buf = NULL;
end:
	spin_unlock_irqrestore(&stream->stream_lock, flags);
	read_unlock(&session->stream_rwlock);
	return vb2_v4l2_buf;
}

@@ -270,14 +345,24 @@ static int msm_vb2_put_buf(struct vb2_v4l2_buffer *vb, int session_id,
				unsigned int stream_id)
{
	struct msm_stream *stream;
	struct msm_session *session;
	struct msm_vb2_buffer *msm_vb2;
	struct vb2_v4l2_buffer *vb2_v4l2_buf = NULL;
	int rc = 0;
	unsigned long flags;
	stream = msm_get_stream(session_id, stream_id);
	if (IS_ERR_OR_NULL(stream))

	session = msm_get_session(session_id);
	if (IS_ERR_OR_NULL(session))
		return -EINVAL;

	read_lock(&session->stream_rwlock);

	stream = msm_get_stream(session, stream_id);
	if (IS_ERR_OR_NULL(stream)) {
		read_unlock(&session->stream_rwlock);
		return -EINVAL;
	}

	spin_lock_irqsave(&stream->stream_lock, flags);
	if (vb) {
		list_for_each_entry(msm_vb2, &(stream->queued_list), list) {
@@ -305,6 +390,7 @@ static int msm_vb2_put_buf(struct vb2_v4l2_buffer *vb, int session_id,
		rc = -EINVAL;
	}
	spin_unlock_irqrestore(&stream->stream_lock, flags);
	read_unlock(&session->stream_rwlock);
	return rc;
}

@@ -315,12 +401,22 @@ static int msm_vb2_buf_done(struct vb2_v4l2_buffer *vb, int session_id,
	unsigned long flags;
	struct msm_vb2_buffer *msm_vb2;
	struct msm_stream *stream;
	struct msm_session *session;
	struct vb2_v4l2_buffer *vb2_v4l2_buf = NULL;
	int rc = 0;

	stream = msm_get_stream(session_id, stream_id);
	if (IS_ERR_OR_NULL(stream))
	session = msm_get_session(session_id);
	if (IS_ERR_OR_NULL(session))
		return -EINVAL;

	read_lock(&session->stream_rwlock);

	stream = msm_get_stream(session, stream_id);
	if (IS_ERR_OR_NULL(stream)) {
		read_unlock(&session->stream_rwlock);
		return -EINVAL;
	}

	spin_lock_irqsave(&stream->stream_lock, flags);
	if (vb) {
		list_for_each_entry(msm_vb2, &(stream->queued_list), list) {
@@ -352,6 +448,7 @@ static int msm_vb2_buf_done(struct vb2_v4l2_buffer *vb, int session_id,
		rc = -EINVAL;
	}
	spin_unlock_irqrestore(&stream->stream_lock, flags);
	read_unlock(&session->stream_rwlock);
	return rc;
}

@@ -359,15 +456,24 @@ long msm_vb2_return_buf_by_idx(int session_id, unsigned int stream_id,
				uint32_t index)
{
	struct msm_stream *stream;
	struct msm_session *session;
	struct vb2_v4l2_buffer *vb2_v4l2_buf = NULL;
	struct msm_vb2_buffer *msm_vb2 = NULL;
	unsigned long flags;
	long rc = -EINVAL;

	stream = msm_get_stream(session_id, stream_id);
	if (IS_ERR_OR_NULL(stream))
	session = msm_get_session(session_id);
	if (IS_ERR_OR_NULL(session))
		return rc;

	read_lock(&session->stream_rwlock);

	stream = msm_get_stream(session, stream_id);
	if (IS_ERR_OR_NULL(stream)) {
		read_unlock(&session->stream_rwlock);
		return -EINVAL;
	}

	spin_lock_irqsave(&stream->stream_lock, flags);

	if (!stream->vb2_q) {
@@ -393,6 +499,7 @@ long msm_vb2_return_buf_by_idx(int session_id, unsigned int stream_id,

end:
	spin_unlock_irqrestore(&stream->stream_lock, flags);
	read_unlock(&session->stream_rwlock);
	return rc;
}
EXPORT_SYMBOL(msm_vb2_return_buf_by_idx);
@@ -402,11 +509,21 @@ static int msm_vb2_flush_buf(int session_id, unsigned int stream_id)
	unsigned long flags;
	struct msm_vb2_buffer *msm_vb2;
	struct msm_stream *stream;
	struct msm_session *session;
	struct vb2_v4l2_buffer *vb2_v4l2_buf = NULL;

	stream = msm_get_stream(session_id, stream_id);
	if (IS_ERR_OR_NULL(stream))
	session = msm_get_session(session_id);
	if (IS_ERR_OR_NULL(session))
		return -EINVAL;

	read_lock(&session->stream_rwlock);

	stream = msm_get_stream(session, stream_id);
	if (IS_ERR_OR_NULL(stream)) {
		read_unlock(&session->stream_rwlock);
		return -EINVAL;
	}

	spin_lock_irqsave(&stream->stream_lock, flags);
	list_for_each_entry(msm_vb2, &(stream->queued_list), list) {
		vb2_v4l2_buf = &(msm_vb2->vb2_v4l2_buf);
@@ -415,6 +532,7 @@ static int msm_vb2_flush_buf(int session_id, unsigned int stream_id)
		msm_vb2->in_freeq = 0;
	}
	spin_unlock_irqrestore(&stream->stream_lock, flags);
	read_unlock(&session->stream_rwlock);
	return 0;
}

+2 −1
Original line number Diff line number Diff line
/* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved.
/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
@@ -67,5 +67,6 @@ struct vb2_mem_ops *msm_vb2_get_q_mem_ops(void);
int msm_vb2_request_cb(struct msm_sd_req_vb2_q *req_sd);
long msm_vb2_return_buf_by_idx(int session_id, unsigned int stream_id,
	uint32_t index);
int msm_vb2_get_stream_state(struct msm_stream *stream);

#endif /*_MSM_VB_H */