forked from rubenslte/android_kernel_samsung_msm8226
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: Manish Poddar <mpoddar@codeaurora.org> Signed-off-by: Trishansh Bhardwaj <tbhardwa@codeaurora.org> [haggertk: Backport to 3.4/msm8974. Note that this includes patching the non-standard camera_ll implementation as well on this kernel.] CVE-2017-9704 Signed-off-by: Kevin F. Haggerty <haggertk@lineageos.org>
This commit is contained in:
committed by
matteo0026
parent
c2e47eaa03
commit
c7dfc3df1d
@@ -138,7 +138,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); \
|
||||
@@ -249,21 +249,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);
|
||||
list, __msm_queue_find_session, &session_id);
|
||||
|
||||
if (!session)
|
||||
return;
|
||||
|
||||
stream = msm_queue_find(&session->stream_q, struct msm_stream,
|
||||
list, __msm_queue_find_stream, &stream_id);
|
||||
if (!stream)
|
||||
return;
|
||||
spin_lock_irqsave(&(session->stream_q.lock), flags);
|
||||
list_del_init(&stream->list);
|
||||
session->stream_q.len--;
|
||||
spin_unlock_irqrestore(&(session->stream_q.lock), flags);
|
||||
kzfree(stream);
|
||||
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) {
|
||||
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--;
|
||||
kzfree(stream);
|
||||
stream = NULL;
|
||||
spin_unlock_irqrestore(&(session->stream_q.lock), flags);
|
||||
write_unlock(&session->stream_rwlock);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void msm_sd_unregister_subdev(struct video_device *vdev)
|
||||
@@ -418,6 +444,7 @@ int msm_create_session(unsigned int session_id, struct video_device *vdev)
|
||||
msm_init_queue(&session->stream_q);
|
||||
msm_enqueue(msm_session_q, &session->list);
|
||||
mutex_init(&session->lock);
|
||||
rwlock_init(&session->stream_rwlock);
|
||||
pr_warn("msm_create_session : Succeed!, session_id %d",
|
||||
session_id);
|
||||
return 0;
|
||||
@@ -951,11 +978,9 @@ static struct v4l2_file_operations msm_fops = {
|
||||
.ioctl = video_ioctl2,
|
||||
};
|
||||
|
||||
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);
|
||||
@@ -964,6 +989,16 @@ struct msm_stream *msm_get_stream(unsigned int session_id,
|
||||
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);
|
||||
|
||||
@@ -998,6 +1033,33 @@ struct vb2_queue *msm_get_stream_vb2q(unsigned int session_id,
|
||||
return stream->vb2_q;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
struct msm_stream *msm_get_stream_from_vb2q(struct vb2_queue *q)
|
||||
{
|
||||
struct msm_session *session;
|
||||
|
||||
@@ -102,6 +102,7 @@ struct msm_session {
|
||||
* session struct msm_stream */
|
||||
struct msm_queue_head stream_q;
|
||||
struct mutex lock;
|
||||
rwlock_t stream_rwlock;
|
||||
};
|
||||
void msm_pm_qos_update_request(int val);
|
||||
int msm_cam_get_module_init_status(void);
|
||||
@@ -115,10 +116,12 @@ 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,
|
||||
unsigned int stream_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);
|
||||
#endif /*_MSM_H */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* 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
|
||||
@@ -40,16 +40,24 @@ 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;
|
||||
|
||||
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(vb, struct msm_vb2_buffer, vb2_buf);
|
||||
msm_vb2_buf->in_freeq = 0;
|
||||
|
||||
read_unlock(&session->stream_rwlock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -57,6 +65,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;
|
||||
|
||||
msm_vb2 = container_of(vb, struct msm_vb2_buffer, vb2_buf);
|
||||
@@ -66,21 +75,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 int 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;
|
||||
|
||||
@@ -91,9 +109,16 @@ static int msm_vb2_buf_finish(struct vb2_buffer *vb)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
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:%d] NULL stream", __func__, __LINE__);
|
||||
read_unlock(&session->stream_rwlock);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -106,6 +131,7 @@ static int msm_vb2_buf_finish(struct vb2_buffer *vb)
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&stream->stream_lock, flags);
|
||||
read_unlock(&session->stream_rwlock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -133,6 +159,24 @@ static void msm_vb2_buf_cleanup(struct vb2_buffer *vb)
|
||||
spin_unlock_irqrestore(&stream->stream_lock, flags);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
static struct vb2_ops msm_vb2_get_q_op = {
|
||||
.queue_setup = msm_vb2_queue_setup,
|
||||
.buf_init = msm_vb2_buf_init,
|
||||
@@ -186,13 +230,22 @@ static struct vb2_buffer *msm_vb2_get_buf(int session_id,
|
||||
{
|
||||
struct msm_stream *stream;
|
||||
struct vb2_buffer *vb2_buf = NULL;
|
||||
struct msm_session *session;
|
||||
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) {
|
||||
@@ -215,6 +268,7 @@ static struct vb2_buffer *msm_vb2_get_buf(int session_id,
|
||||
vb2_buf = NULL;
|
||||
end:
|
||||
spin_unlock_irqrestore(&stream->stream_lock, flags);
|
||||
read_unlock(&session->stream_rwlock);
|
||||
return vb2_buf;
|
||||
}
|
||||
|
||||
@@ -222,13 +276,23 @@ static int msm_vb2_put_buf(struct vb2_buffer *vb, int session_id,
|
||||
unsigned int stream_id)
|
||||
{
|
||||
struct msm_stream *stream;
|
||||
struct msm_session *session;
|
||||
struct msm_vb2_buffer *msm_vb2;
|
||||
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) {
|
||||
msm_vb2 =
|
||||
@@ -243,6 +307,7 @@ static int msm_vb2_put_buf(struct vb2_buffer *vb, int session_id,
|
||||
rc = -EINVAL;
|
||||
}
|
||||
spin_unlock_irqrestore(&stream->stream_lock, flags);
|
||||
read_unlock(&session->stream_rwlock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -254,11 +319,21 @@ static int msm_vb2_buf_done(struct vb2_buffer *vb, int session_id,
|
||||
struct msm_vb2_buffer *msm_vb2;
|
||||
struct msm_stream *stream;
|
||||
struct vb2_buffer *vb2_buf = NULL;
|
||||
struct msm_session *session;
|
||||
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) {
|
||||
@@ -290,6 +365,7 @@ buf_check_done:
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&stream->stream_lock, flags);
|
||||
read_unlock(&session->stream_rwlock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2012-2013, 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
|
||||
@@ -66,5 +66,6 @@ struct msm_stream {
|
||||
struct vb2_ops *msm_vb2_get_q_ops(void);
|
||||
struct vb2_mem_ops *msm_vb2_get_q_mem_ops(void);
|
||||
int msm_vb2_request_cb(struct msm_sd_req_vb2_q *req_sd);
|
||||
int msm_vb2_get_stream_state(struct msm_stream *stream);
|
||||
|
||||
#endif /*_MSM_VB_H */
|
||||
|
||||
Reference in New Issue
Block a user