ALSA: usb-audio: Add sanity check for OOB writes at silencing

[ Upstream commit fba2105a157fffcf19825e4eea498346738c9948 ]

At silencing the playback URB packets in the implicit fb mode before
the actual playback, we blindly assume that the received packets fit
with the buffer size.  But when the setup in the capture stream
differs from the playback stream (e.g. due to the USB core limitation
of max packet size), such an inconsistency may lead to OOB writes to
the buffer, resulting in a crash.

For addressing it, add a sanity check of the transfer buffer size at
prepare_silent_urb(), and stop the data copy if the received data
overflows.  Also, report back the transfer error properly from there,
too.

Note that this doesn't fix the root cause of the playback error
itself, but this merely covers the kernel Oops.

Link: https://bugzilla.kernel.org/show_bug.cgi?id=221076
Link: https://patch.msgid.link/20260216141209.1849200-4-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
Takashi Iwai 2026-02-16 15:12:07 +01:00 committed by Sasha Levin
parent d6e933f29b
commit 6af16f1b86
1 changed files with 21 additions and 16 deletions

View File

@ -275,7 +275,7 @@ static inline bool has_tx_length_quirk(struct snd_usb_audio *chip)
return chip->quirk_flags & QUIRK_FLAG_TX_LENGTH;
}
static void prepare_silent_urb(struct snd_usb_endpoint *ep,
static int prepare_silent_urb(struct snd_usb_endpoint *ep,
struct snd_urb_ctx *ctx)
{
struct urb *urb = ctx->urb;
@ -289,28 +289,34 @@ static void prepare_silent_urb(struct snd_usb_endpoint *ep,
extra = sizeof(packet_length);
for (i = 0; i < ctx->packets; ++i) {
unsigned int offset;
unsigned int length;
int counts;
int length;
counts = snd_usb_endpoint_next_packet_size(ep, ctx, i, 0);
length = counts * ep->stride; /* number of silent bytes */
offset = offs * ep->stride + extra * i;
urb->iso_frame_desc[i].offset = offset;
length = snd_usb_endpoint_next_packet_size(ep, ctx, i, 0);
if (length < 0)
return length;
length *= ep->stride; /* number of silent bytes */
if (offs + length + extra > ctx->buffer_size)
break;
urb->iso_frame_desc[i].offset = offs;
urb->iso_frame_desc[i].length = length + extra;
if (extra) {
packet_length = cpu_to_le32(length);
memcpy(urb->transfer_buffer + offset,
memcpy(urb->transfer_buffer + offs,
&packet_length, sizeof(packet_length));
offs += extra;
}
memset(urb->transfer_buffer + offset + extra,
memset(urb->transfer_buffer + offs,
ep->silence_value, length);
offs += counts;
offs += length;
}
urb->number_of_packets = ctx->packets;
urb->transfer_buffer_length = offs * ep->stride + ctx->packets * extra;
if (!offs)
return -EPIPE;
urb->number_of_packets = i;
urb->transfer_buffer_length = offs;
ctx->queued = 0;
return 0;
}
/*
@ -332,8 +338,7 @@ static int prepare_outbound_urb(struct snd_usb_endpoint *ep,
if (data_subs && ep->prepare_data_urb)
return ep->prepare_data_urb(data_subs, urb, in_stream_lock);
/* no data provider, so send silence */
prepare_silent_urb(ep, ctx);
break;
return prepare_silent_urb(ep, ctx);
case SND_USB_ENDPOINT_TYPE_SYNC:
if (snd_usb_get_speed(ep->chip->dev) >= USB_SPEED_HIGH) {