From: x8svc <enscrpt@pm.me>
To: Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.com>,
Clemens Ladisch <clemens@ladisch.de>
Cc: linux-sound@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH 0/2] ALSA: usb-audio, ua101: oversized packet copies overrun PCM ring buffers
Date: Mon, 26 Oct 2026 00:10:37 +0000 [thread overview]
Message-ID: <20261026001003.1067-1-enscrpt@pm.me> (raw)
Hello,
I found ring-buffer overruns in snd-usb-audio and snd-ua101 when the
amount of data copied from or to an isochronous transfer exceeds the
ALSA PCM buffer size. The copy paths use a two-part memcpy() and a
position update that handle only one wrap, which is not enough when a
single packet is larger than the whole buffer.
I reproduced the overruns under KASAN on 7.3-rc7 (2f2f7eeef8b0) in
snd-usb-audio and snd-ua101, for capture and playback. The two patches
that follow apply to and build on current mainline (c03c1a14507d). I
found similar code in snd-usb-line6, snd-usb-usx2y and cx231xx-audio,
and reproduced them on 7.3-rc7. em28xx-audio has the same shape. I
booted it, and KASAN reported nothing. By reading, the
same shape is in 5.15.y, 6.1.y, 6.6.y, 6.12.y, 6.18.y and 7.2.y. I did
not boot those branches. 5.10.y has older code, which I have not
checked.
The reproducer is available to maintainers on request.
snd-usb-audio (sound/usb/pcm.c)
-------------------------------
retire_capture_urb() and copy_to_urb(), called from
prepare_playback_urb(), copy data across the ALSA ring buffer with a
two-part memcpy() and update the position with a single wrap:
subs->hwptr_done += bytes;
if (subs->hwptr_done >= subs->buffer_bytes)
subs->hwptr_done -= subs->buffer_bytes;
if (oldptr + bytes > subs->buffer_bytes) {
unsigned int bytes1 = subs->buffer_bytes - oldptr;
memcpy(runtime->dma_area + oldptr, cp, bytes1);
memcpy(runtime->dma_area, cp + bytes1, bytes - bytes1);
}
Nothing in the path guarantees bytes <= buffer_bytes. The device sets
the packet size and the PCM client sets the buffer size.
setup_hw_info() permits 8 kHz mono U8 with a 128-byte buffer (64-byte
periods, two periods), and a full-speed endpoint can send 1023-byte
packets.
The first oversized capture packet already overruns the logical PCM
buffer: with oldptr = 0, the two-part copy writes 128 bytes and then
another 895 bytes at the start of the 128-byte buffer. It also leaves
hwptr_done at 895 (1023 - 128), beyond buffer_bytes. On the next
packet, bytes1 = 128 - 895 wraps to 4294966529, and memcpy() runs past
the URB buffer and the vmalloc'd DMA buffer.
For playback, copy_to_urb() has the same single-wrap assumption, and
the reproducer reaches an out-of-bounds read there as well.
KASAN reports (7.3-rc7, x86_64):
Capture:
snd-usb-audio 2-1:1.0: invalid position: , pos = 895, buffer size = 128, period size = 64
BUG: KASAN: slab-out-of-bounds in retire_capture_urb+0x251/0x870
Read of size 4294966529 at addr ffff88800c1e2800 by task vhci_rx/82
...
retire_capture_urb+0x251/0x870
snd_complete_urb
__usb_hcd_giveback_urb
vhci_rx_loop
Playback:
BUG: KASAN: vmalloc-out-of-bounds in copy_to_urb.isra.0+0x188/0x430
Read of size 4294966529 at addr ffffc9000004537f by task vhci_rx/79
...
prepare_playback_urb+0x98a/0x1fc0
The buggy address belongs to a 1-page vmalloc region starting at
0xffffc90000045000 allocated at __snd_dma_alloc_pages
On 6.19-rc3 the same reproducer faults in memcpy() from
retire_capture_urb() without KASAN (RDX = 0xfffffd01, the wrapped
bytes1).
The USB/IP server never reports an actual_length larger than the
requested packet: actual_length equals the URB's iso_frame_desc length
and curpacksize. I reproduced this through USB/IP only. I have not
tested a physical device or a physical host controller.
The local PCM client only needs permission to open the PCM device. My
reproducer runs as an unprivileged user in the audio group. I also
tested Fedora 42's PipeWire 1.4.11 with WirePlumber 0.5.14 and
PulseAudio 17.0 against the same USB/IP device:
- In the tested PipeWire configuration, the driver selects its maximum
buffer, capped at two seconds (16000 bytes for this stream).
Full-speed 1023-byte and high-speed 3072-byte packets fit. With a
super-speed device advertising wBytesPerInterval 49152, pw-record
hits retire_capture_urb() with a 33152-byte write (49152 - 16000)
and then faults at the vmalloc guard page. pw-play hits copy_to_urb()
with a 33152-byte read on PipeWire's data-loop.0 thread, and the
machine soft-locks.
- PulseAudio 17.0 inside this VM turns timer-based scheduling off by
itself. The measured buffer is four 25 ms fragments, 799 bytes, and
a full-speed 1023-byte capture packet reproduces the
retire_capture_urb() report. I did not measure timer-based scheduling
left on. The PulseAudio source uses a 2 second buffer in that case.
A client with permission to open the PCM device directly can trigger
the full- and high-speed cases by selecting a sufficiently small
buffer. The super-speed case reproduced in the tested PipeWire
configuration with an ordinary recording or playback application when
the emulated device was selected as the default device.
Reproducer
----------
I used USB/IP because I could not exercise this isochronous transfer
path with dummy_hcd and raw-gadget. The reproducer is available to
maintainers on request:
- usbip-server.py emulates a UAC1 microphone or, with --mode playback,
a speaker. It advertises a one-channel, 8-bit, 8000 Hz Type I stream
and a full-speed isochronous endpoint with wMaxPacketSize 1023 and
bmAttributes 0x80 (MaxPacketsOnly). Each packet is 1023 bytes.
- attach.c imports the device and attaches the socket to vhci-hcd
(CONFIG_USBIP_VHCI_HCD).
- client.c opens the PCM device with U8, one channel, 8000 Hz,
period_size 64 and two periods (buffer_bytes 128), sets
stop_threshold to LONG_MAX, and starts the stream. For playback it
first writes 128 bytes.
The fault occurs within a second of starting the stream in my tests.
Patch 1
-------
The snd-usb-audio patch bounds the capture packet copy by the PCM
buffer size and stops prepare_playback_urb() from scheduling more data
than the buffer can hold.
I tested it on 7.3-rc7 with the same reproducer for 30 seconds per
mode. KASAN reported nothing. Capture kept streaming and logged the
ratelimited truncation warning. Playback entered XRUN at once because
the buffer cannot hold a single packet, which is the safe failure for
such a device. With PipeWire and a super-speed device, capture
truncates the packet with the same warning and the stream keeps
running. I did not repeat the patched playback run with PipeWire.
Patch 2
-------
In snd-ua101 (sound/usb/misc/ua101.c), copy_capture_data() takes the
frame count from actual_length, and copy_playback_data() takes it from
the capture packets through rate_feedback. Both split the copy at the
end of the ring buffer and wrap the position once.
set_stream_hw() permits an 18-frame buffer: periods_min is 2 and the
period can be 9 frames. A 672-byte packet of one-channel, 24-bit audio
holds 224 frames. After the first packet the position is 206, which is
188 frames past the 18-frame buffer (224 - 18). On the next packet the
single-wrap calculation underflows.
KASAN reports in my tests:
snd-ua101 2-1:1.0: invalid position: , pos = 206, buffer size = 18, period size = 9
BUG: KASAN: slab-out-of-bounds in capture_urb_complete+0x9eb/0xcb0
Read of size 4294966732 at addr ffff88800d73e7e0 by task vhci_rx/81
BUG: KASAN: vmalloc-out-of-bounds in playback_work+0x789/0xac0
Read of size 4294966732 at addr ffffc9000002626a by task kworker/0:1H/80
The reproducer uses the same USB/IP server with --device ua101 to
present a UA-101 interface layout, and a client using S24_3LE, 44100
Hz, period_size 9 and one channel.
The patch clamps the capture copy to the buffer size. On playback it
copies at most one buffer and fills the rest of the packet with
silence, so the URB length, the delay accounting and the data sent stay
consistent. I first tried shortening the playback packet instead, but
that makes the URB's declared length disagree with its buffer and
USB/IP drops the device, so I kept the length. With the patch, capture
and playback each ran for 30 seconds without a KASAN report and the
streams kept advancing.
The period accounting still subtracts one period at a time, so a
packet spanning several periods of a tiny buffer leaves period_pos
above one period until later calls drain it. I did not see this cause
a problem in the runs, but I have not analysed it further and would
welcome your view.
Other drivers
--------------
snd-usb-line6, capture, on 7.3-rc7. A GuitarPort device sent 1024-byte
packets into a 128-byte buffer (S16_LE, 44100 Hz, 16 frames, 2 periods,
2 channels). ALSA logged:
snd_usb_toneport 2-1:1.0: invalid position: , pos = 224, buffer size = 32, period size = 16
KASAN reported nothing. The ring is a whole page, so the extra bytes of
the first copy stay inside that page. The driver then stopped copying
and logged "driver bug: len".
snd-usb-usx2y, capture, on the same tree. After the hwdep firmware
load, a US-122 device sent 1024-byte packets into a 512-byte buffer
(S16_LE, 44100 Hz, 64 frames, 2 periods, 2 channels). KASAN reported:
BUG: KASAN: out-of-bounds in i_usx2y_urb_complete+0x1285/0x1ac0
Read of size 18446744073709551104 at addr ffff88800e448800 by task vhci_rx/83
That read size is the single-wrap length once the position has passed
the buffer. These two patches do not change line6, usx2y, cx231xx
or em28xx.
cx231xx-audio, capture, on the same tree. A Pixelview Xcapture device
sent 1024-byte packets into a 128-byte buffer (S16_LE, 48000 Hz, 16
frames, 2 periods, 2 channels). KASAN reported:
BUG: KASAN: use-after-free in cx231xx_audio_isocirq+0x2c7/0x780
Read of size 4294966528 at addr ffff88800e8e0400 by task vhci_rx/82
ALSA logged pos = 224, buffer size = 32, period size = 16. That
position is one 256-frame packet minus the 32-frame buffer. The read
size is the single-wrap length once the position has passed the buffer.
em28xx-audio, capture, on the same tree. An em2860 device sent
3072-byte packets into a 24576-byte buffer (S16_LE, 48000 Hz, 3072
frames, 2 periods, 2 channels). The endpoint was 0x83, with 64 packets
per URB. Capture ran for 30 seconds. KASAN reported nothing.
period_bytes_min is 11059 and periods_min is 2, so the smallest buffer
is 22118 bytes. em28xx_audio_ep_packet_size() caps a high-speed
endpoint at 8188 bytes, so one packet cannot pass that buffer.
Process
-------
I understand this to fall under the regular-bug handling in
Documentation/process/threat-model.rst, because reproduction uses an
emulated USB device, so I am sending it to the subsystem list with the
proposed fixes. An AI tool helped with the analysis and the patches.
I reviewed and tested them myself.
Thanks,
x8svc
next reply other threads:[~2026-10-26 0:10 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-26 0:10 x8svc [this message]
2026-10-26 0:10 ` [PATCH 1/2] ALSA: usb-audio: Bound packet copies by the PCM buffer size x8svc
2026-10-26 0:10 ` [PATCH 2/2] ALSA: ua101: " x8svc
2026-10-26 8:08 ` [PATCH 0/2] ALSA: usb-audio, ua101: oversized packet copies overrun PCM ring buffers Takashi Iwai
2026-10-26 12:30 ` x8svc
2026-10-26 13:38 ` Takashi Iwai
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261026001003.1067-1-enscrpt@pm.me \
--to=enscrpt@pm.me \
--cc=clemens@ladisch.de \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-sound@vger.kernel.org \
--cc=perex@perex.cz \
--cc=tiwai@suse.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®