linux-brain/sound/usb/line6/midibuf.c
Takashi Iwai 2d994c9cef ALSA: line6: Fix endless MIDI read loop
commit d683469b3c93d7e2afd39e6e1970f24700eb7a68 upstream.

The MIDI input event parser of the LINE6 driver may enter into an
endless loop when the unexpected data sequence is given, as it tries
to continue the secondary bytes without termination.  Also, when the
input data is too short, the parser returns a negative error, while
the caller doesn't handle it properly.  This would lead to the
unexpected behavior as well.

This patch addresses those issues by checking the return value
correctly and handling the one-byte event in the parser properly.

The bug was reported by syzkaller.

Reported-by: syzbot+cce32521ee0a824c21f7@syzkaller.appspotmail.com
Cc: <stable@vger.kernel.org>
Link: https://lore.kernel.org/r/000000000000033087059f8f8fa3@google.com
Link: https://lore.kernel.org/r/20200309095922.30269-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2020-03-25 08:25:51 +01:00

249 lines
5.1 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Line 6 Linux USB driver
*
* Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at)
*/
#include <linux/slab.h>
#include "midibuf.h"
static int midibuf_message_length(unsigned char code)
{
int message_length;
if (code < 0x80)
message_length = -1;
else if (code < 0xf0) {
static const int length[] = { 3, 3, 3, 3, 2, 2, 3 };
message_length = length[(code >> 4) - 8];
} else {
/*
Note that according to the MIDI specification 0xf2 is
the "Song Position Pointer", but this is used by Line 6
to send sysex messages to the host.
*/
static const int length[] = { -1, 2, -1, 2, -1, -1, 1, 1, 1, 1,
1, 1, 1, -1, 1, 1
};
message_length = length[code & 0x0f];
}
return message_length;
}
static int midibuf_is_empty(struct midi_buffer *this)
{
return (this->pos_read == this->pos_write) && !this->full;
}
static int midibuf_is_full(struct midi_buffer *this)
{
return this->full;
}
void line6_midibuf_reset(struct midi_buffer *this)
{
this->pos_read = this->pos_write = this->full = 0;
this->command_prev = -1;
}
int line6_midibuf_init(struct midi_buffer *this, int size, int split)
{
this->buf = kmalloc(size, GFP_KERNEL);
if (this->buf == NULL)
return -ENOMEM;
this->size = size;
this->split = split;
line6_midibuf_reset(this);
return 0;
}
int line6_midibuf_bytes_free(struct midi_buffer *this)
{
return
midibuf_is_full(this) ?
0 :
(this->pos_read - this->pos_write + this->size - 1) % this->size +
1;
}
int line6_midibuf_bytes_used(struct midi_buffer *this)
{
return
midibuf_is_empty(this) ?
0 :
(this->pos_write - this->pos_read + this->size - 1) % this->size +
1;
}
int line6_midibuf_write(struct midi_buffer *this, unsigned char *data,
int length)
{
int bytes_free;
int length1, length2;
int skip_active_sense = 0;
if (midibuf_is_full(this) || (length <= 0))
return 0;
/* skip trailing active sense */
if (data[length - 1] == 0xfe) {
--length;
skip_active_sense = 1;
}
bytes_free = line6_midibuf_bytes_free(this);
if (length > bytes_free)
length = bytes_free;
if (length > 0) {
length1 = this->size - this->pos_write;
if (length < length1) {
/* no buffer wraparound */
memcpy(this->buf + this->pos_write, data, length);
this->pos_write += length;
} else {
/* buffer wraparound */
length2 = length - length1;
memcpy(this->buf + this->pos_write, data, length1);
memcpy(this->buf, data + length1, length2);
this->pos_write = length2;
}
if (this->pos_write == this->pos_read)
this->full = 1;
}
return length + skip_active_sense;
}
int line6_midibuf_read(struct midi_buffer *this, unsigned char *data,
int length)
{
int bytes_used;
int length1, length2;
int command;
int midi_length;
int repeat = 0;
int i;
/* we need to be able to store at least a 3 byte MIDI message */
if (length < 3)
return -EINVAL;
if (midibuf_is_empty(this))
return 0;
bytes_used = line6_midibuf_bytes_used(this);
if (length > bytes_used)
length = bytes_used;
length1 = this->size - this->pos_read;
/* check MIDI command length */
command = this->buf[this->pos_read];
if (command & 0x80) {
midi_length = midibuf_message_length(command);
this->command_prev = command;
} else {
if (this->command_prev > 0) {
int midi_length_prev =
midibuf_message_length(this->command_prev);
if (midi_length_prev > 1) {
midi_length = midi_length_prev - 1;
repeat = 1;
} else
midi_length = -1;
} else
midi_length = -1;
}
if (midi_length < 0) {
/* search for end of message */
if (length < length1) {
/* no buffer wraparound */
for (i = 1; i < length; ++i)
if (this->buf[this->pos_read + i] & 0x80)
break;
midi_length = i;
} else {
/* buffer wraparound */
length2 = length - length1;
for (i = 1; i < length1; ++i)
if (this->buf[this->pos_read + i] & 0x80)
break;
if (i < length1)
midi_length = i;
else {
for (i = 0; i < length2; ++i)
if (this->buf[i] & 0x80)
break;
midi_length = length1 + i;
}
}
if (midi_length == length)
midi_length = -1; /* end of message not found */
}
if (midi_length < 0) {
if (!this->split)
return 0; /* command is not yet complete */
} else {
if (length < midi_length)
return 0; /* command is not yet complete */
length = midi_length;
}
if (length < length1) {
/* no buffer wraparound */
memcpy(data + repeat, this->buf + this->pos_read, length);
this->pos_read += length;
} else {
/* buffer wraparound */
length2 = length - length1;
memcpy(data + repeat, this->buf + this->pos_read, length1);
memcpy(data + repeat + length1, this->buf, length2);
this->pos_read = length2;
}
if (repeat)
data[0] = this->command_prev;
this->full = 0;
return length + repeat;
}
int line6_midibuf_ignore(struct midi_buffer *this, int length)
{
int bytes_used = line6_midibuf_bytes_used(this);
if (length > bytes_used)
length = bytes_used;
this->pos_read = (this->pos_read + length) % this->size;
this->full = 0;
return length;
}
void line6_midibuf_destroy(struct midi_buffer *this)
{
kfree(this->buf);
this->buf = NULL;
}