mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2025-01-18 14:25:25 +00:00
V4L/DVB (13831): uvcvideo: Fix oops caused by a race condition in buffer dequeuing
Buffers were marked as done before being removed from the IRQ queue. If a userspace application dequeued and requeued the buffer fast enough during that time window, the buffer could end up being deleted twice, generating an oops in interrupt context. Add a new state, UVC_BUF_STATE_READY, to mark buffers as ready for reuse but not yet removed from the queue, and transition to UVC_BUF_STATE_DONE only when the buffer is removed from the queue. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
This commit is contained in:
parent
2c4d9de8ab
commit
d7c0d43997
@ -59,9 +59,9 @@
|
|||||||
* returns immediately.
|
* returns immediately.
|
||||||
*
|
*
|
||||||
* When the buffer is full, the completion handler removes it from the irq
|
* When the buffer is full, the completion handler removes it from the irq
|
||||||
* queue, marks it as ready (UVC_BUF_STATE_DONE) and wakes its wait queue.
|
* queue, marks it as done (UVC_BUF_STATE_DONE) and wakes its wait queue.
|
||||||
* At that point, any process waiting on the buffer will be woken up. If a
|
* At that point, any process waiting on the buffer will be woken up. If a
|
||||||
* process tries to dequeue a buffer after it has been marked ready, the
|
* process tries to dequeue a buffer after it has been marked done, the
|
||||||
* dequeing will succeed immediately.
|
* dequeing will succeed immediately.
|
||||||
*
|
*
|
||||||
* 2. Buffers are queued, user is waiting on a buffer and the device gets
|
* 2. Buffers are queued, user is waiting on a buffer and the device gets
|
||||||
@ -201,6 +201,7 @@ static void __uvc_query_buffer(struct uvc_buffer *buf,
|
|||||||
break;
|
break;
|
||||||
case UVC_BUF_STATE_QUEUED:
|
case UVC_BUF_STATE_QUEUED:
|
||||||
case UVC_BUF_STATE_ACTIVE:
|
case UVC_BUF_STATE_ACTIVE:
|
||||||
|
case UVC_BUF_STATE_READY:
|
||||||
v4l2_buf->flags |= V4L2_BUF_FLAG_QUEUED;
|
v4l2_buf->flags |= V4L2_BUF_FLAG_QUEUED;
|
||||||
break;
|
break;
|
||||||
case UVC_BUF_STATE_IDLE:
|
case UVC_BUF_STATE_IDLE:
|
||||||
@ -295,13 +296,15 @@ static int uvc_queue_waiton(struct uvc_buffer *buf, int nonblocking)
|
|||||||
{
|
{
|
||||||
if (nonblocking) {
|
if (nonblocking) {
|
||||||
return (buf->state != UVC_BUF_STATE_QUEUED &&
|
return (buf->state != UVC_BUF_STATE_QUEUED &&
|
||||||
buf->state != UVC_BUF_STATE_ACTIVE)
|
buf->state != UVC_BUF_STATE_ACTIVE &&
|
||||||
|
buf->state != UVC_BUF_STATE_READY)
|
||||||
? 0 : -EAGAIN;
|
? 0 : -EAGAIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
return wait_event_interruptible(buf->wait,
|
return wait_event_interruptible(buf->wait,
|
||||||
buf->state != UVC_BUF_STATE_QUEUED &&
|
buf->state != UVC_BUF_STATE_QUEUED &&
|
||||||
buf->state != UVC_BUF_STATE_ACTIVE);
|
buf->state != UVC_BUF_STATE_ACTIVE &&
|
||||||
|
buf->state != UVC_BUF_STATE_READY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -348,6 +351,7 @@ int uvc_dequeue_buffer(struct uvc_video_queue *queue,
|
|||||||
case UVC_BUF_STATE_IDLE:
|
case UVC_BUF_STATE_IDLE:
|
||||||
case UVC_BUF_STATE_QUEUED:
|
case UVC_BUF_STATE_QUEUED:
|
||||||
case UVC_BUF_STATE_ACTIVE:
|
case UVC_BUF_STATE_ACTIVE:
|
||||||
|
case UVC_BUF_STATE_READY:
|
||||||
default:
|
default:
|
||||||
uvc_trace(UVC_TRACE_CAPTURE, "[E] Invalid buffer state %u "
|
uvc_trace(UVC_TRACE_CAPTURE, "[E] Invalid buffer state %u "
|
||||||
"(driver bug?).\n", buf->state);
|
"(driver bug?).\n", buf->state);
|
||||||
@ -489,6 +493,7 @@ struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue,
|
|||||||
|
|
||||||
spin_lock_irqsave(&queue->irqlock, flags);
|
spin_lock_irqsave(&queue->irqlock, flags);
|
||||||
list_del(&buf->queue);
|
list_del(&buf->queue);
|
||||||
|
buf->state = UVC_BUF_STATE_DONE;
|
||||||
if (!list_empty(&queue->irqqueue))
|
if (!list_empty(&queue->irqqueue))
|
||||||
nextbuf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
|
nextbuf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
|
||||||
queue);
|
queue);
|
||||||
|
@ -441,7 +441,7 @@ static int uvc_video_decode_start(struct uvc_streaming *stream,
|
|||||||
if (fid != stream->last_fid && buf->buf.bytesused != 0) {
|
if (fid != stream->last_fid && buf->buf.bytesused != 0) {
|
||||||
uvc_trace(UVC_TRACE_FRAME, "Frame complete (FID bit "
|
uvc_trace(UVC_TRACE_FRAME, "Frame complete (FID bit "
|
||||||
"toggled).\n");
|
"toggled).\n");
|
||||||
buf->state = UVC_BUF_STATE_DONE;
|
buf->state = UVC_BUF_STATE_READY;
|
||||||
return -EAGAIN;
|
return -EAGAIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,7 +470,7 @@ static void uvc_video_decode_data(struct uvc_streaming *stream,
|
|||||||
/* Complete the current frame if the buffer size was exceeded. */
|
/* Complete the current frame if the buffer size was exceeded. */
|
||||||
if (len > maxlen) {
|
if (len > maxlen) {
|
||||||
uvc_trace(UVC_TRACE_FRAME, "Frame complete (overflow).\n");
|
uvc_trace(UVC_TRACE_FRAME, "Frame complete (overflow).\n");
|
||||||
buf->state = UVC_BUF_STATE_DONE;
|
buf->state = UVC_BUF_STATE_READY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,7 +482,7 @@ static void uvc_video_decode_end(struct uvc_streaming *stream,
|
|||||||
uvc_trace(UVC_TRACE_FRAME, "Frame complete (EOF found).\n");
|
uvc_trace(UVC_TRACE_FRAME, "Frame complete (EOF found).\n");
|
||||||
if (data[0] == len)
|
if (data[0] == len)
|
||||||
uvc_trace(UVC_TRACE_FRAME, "EOF in empty payload.\n");
|
uvc_trace(UVC_TRACE_FRAME, "EOF in empty payload.\n");
|
||||||
buf->state = UVC_BUF_STATE_DONE;
|
buf->state = UVC_BUF_STATE_READY;
|
||||||
if (stream->dev->quirks & UVC_QUIRK_STREAM_NO_FID)
|
if (stream->dev->quirks & UVC_QUIRK_STREAM_NO_FID)
|
||||||
stream->last_fid ^= UVC_STREAM_FID;
|
stream->last_fid ^= UVC_STREAM_FID;
|
||||||
}
|
}
|
||||||
@ -568,8 +568,7 @@ static void uvc_video_decode_isoc(struct urb *urb, struct uvc_streaming *stream,
|
|||||||
uvc_video_decode_end(stream, buf, mem,
|
uvc_video_decode_end(stream, buf, mem,
|
||||||
urb->iso_frame_desc[i].actual_length);
|
urb->iso_frame_desc[i].actual_length);
|
||||||
|
|
||||||
if (buf->state == UVC_BUF_STATE_DONE ||
|
if (buf->state == UVC_BUF_STATE_READY)
|
||||||
buf->state == UVC_BUF_STATE_ERROR)
|
|
||||||
buf = uvc_queue_next_buffer(&stream->queue, buf);
|
buf = uvc_queue_next_buffer(&stream->queue, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -627,8 +626,7 @@ static void uvc_video_decode_bulk(struct urb *urb, struct uvc_streaming *stream,
|
|||||||
if (!stream->bulk.skip_payload && buf != NULL) {
|
if (!stream->bulk.skip_payload && buf != NULL) {
|
||||||
uvc_video_decode_end(stream, buf, stream->bulk.header,
|
uvc_video_decode_end(stream, buf, stream->bulk.header,
|
||||||
stream->bulk.payload_size);
|
stream->bulk.payload_size);
|
||||||
if (buf->state == UVC_BUF_STATE_DONE ||
|
if (buf->state == UVC_BUF_STATE_READY)
|
||||||
buf->state == UVC_BUF_STATE_ERROR)
|
|
||||||
buf = uvc_queue_next_buffer(&stream->queue,
|
buf = uvc_queue_next_buffer(&stream->queue,
|
||||||
buf);
|
buf);
|
||||||
}
|
}
|
||||||
@ -669,7 +667,7 @@ static void uvc_video_encode_bulk(struct urb *urb, struct uvc_streaming *stream,
|
|||||||
stream->bulk.payload_size == stream->bulk.max_payload_size) {
|
stream->bulk.payload_size == stream->bulk.max_payload_size) {
|
||||||
if (buf->buf.bytesused == stream->queue.buf_used) {
|
if (buf->buf.bytesused == stream->queue.buf_used) {
|
||||||
stream->queue.buf_used = 0;
|
stream->queue.buf_used = 0;
|
||||||
buf->state = UVC_BUF_STATE_DONE;
|
buf->state = UVC_BUF_STATE_READY;
|
||||||
uvc_queue_next_buffer(&stream->queue, buf);
|
uvc_queue_next_buffer(&stream->queue, buf);
|
||||||
stream->last_fid ^= UVC_STREAM_FID;
|
stream->last_fid ^= UVC_STREAM_FID;
|
||||||
}
|
}
|
||||||
|
@ -365,8 +365,9 @@ enum uvc_buffer_state {
|
|||||||
UVC_BUF_STATE_IDLE = 0,
|
UVC_BUF_STATE_IDLE = 0,
|
||||||
UVC_BUF_STATE_QUEUED = 1,
|
UVC_BUF_STATE_QUEUED = 1,
|
||||||
UVC_BUF_STATE_ACTIVE = 2,
|
UVC_BUF_STATE_ACTIVE = 2,
|
||||||
UVC_BUF_STATE_DONE = 3,
|
UVC_BUF_STATE_READY = 3,
|
||||||
UVC_BUF_STATE_ERROR = 4,
|
UVC_BUF_STATE_DONE = 4,
|
||||||
|
UVC_BUF_STATE_ERROR = 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct uvc_buffer {
|
struct uvc_buffer {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user