[PATCH] usb: gadget: f_uac1_legacy: fix heap overflow in f_audio_out_ep_complete()
From: Haofeng Li
Date: Sat Aug 22 2026 - 15:46:42 EST
f_audio_out_ep_complete() queues the accumulation buffer for playback
and allocates a fresh, audio_buf_size-byte one whenever an incoming
request does not fit into the space left in the current buffer. It then
unconditionally copies req->actual bytes into copy_buf->buf.
audio_buf_size and req_buf_size are independent configfs attributes of
the function and nothing makes the former cover the latter: a request
buffer is req_buf_size bytes, req->actual is the size of the packet the
USB host chose to send, and as soon as one packet exceeds audio_buf_size
the memcpy() writes past the end of a kzalloc(audio_buf_size) object.
Attack chain (write access to the gadget's configfs attributes before
the function is bound, plus control of the USB host side; no race
needed):
echo 1 > .../functions/uac1_legacy.0/audio_buf_size
echo 200 > .../functions/uac1_legacy.0/req_buf_size
bind the gadget, host sets the AS interface to altsetting 1
-> f_audio_set_alt(): 1-byte accumulation buffer allocated,
200-byte request buffers queued with req->length = 200
host sends a single 200-byte OUT audio packet
-> f_audio_complete() -> f_audio_out_ep_complete()
-> audio_buf_size - actual = 1 - 0 < 200: the empty buffer is
queued for playback and a new 1-byte buffer is allocated
-> memcpy(copy_buf->buf + 0, req->buf, 200)
-> 199-byte out-of-bounds heap write
The write is silent: FORTIFY cannot derive the size of the destination
through the runtime offset buf + actual, and because the neighbouring
slab objects stay addressable, generic KASAN reports nothing either.
Reproduced on 7.2.0+ with KASAN and slub_debug=Z, where the injected
200-byte pattern lands entirely outside the 1-byte kmalloc-8 object and
reaches the SLUB redzone; the resulting freelist corruption was observed
to hang the machine (GPF in get_from_partial_node() during later device
enumeration).
Clamp the copy to the space actually available in the accumulation
buffer. For requests that fit - the only case a sensible configuration
produces - the clamp is a no-op; oversized requests now lose their
excess bytes instead of corrupting the heap.
Signed-off-by: Haofeng Li <lihaofeng@xxxxxxxxxx>
Assisted-by: opencode:deepseek-v4-flash-free
---
drivers/usb/gadget/function/f_uac1_legacy.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c
index 5d201a2e30e7..aa045f64c51e 100644
--- a/drivers/usb/gadget/function/f_uac1_legacy.c
+++ b/drivers/usb/gadget/function/f_uac1_legacy.c
@@ -324,7 +324,8 @@ static int f_audio_out_ep_complete(struct usb_ep *ep, struct usb_request *req)
struct usb_composite_dev *cdev = audio->card.func.config->cdev;
struct f_audio_buf *copy_buf = audio->copy_buf;
struct f_uac1_legacy_opts *opts;
- int audio_buf_size;
+ unsigned int audio_buf_size;
+ unsigned int cp_len;
int err;
opts = container_of(audio->card.func.fi, struct f_uac1_legacy_opts,
@@ -335,7 +336,7 @@ static int f_audio_out_ep_complete(struct usb_ep *ep, struct usb_request *req)
return -EINVAL;
/* Copy buffer is full, add it to the play_queue */
- if (audio_buf_size - copy_buf->actual < req->actual) {
+ if (audio_buf_size < copy_buf->actual + req->actual) {
spin_lock_irq(&audio->lock);
list_add_tail(©_buf->list, &audio->play_queue);
spin_unlock_irq(&audio->lock);
@@ -345,8 +346,10 @@ static int f_audio_out_ep_complete(struct usb_ep *ep, struct usb_request *req)
return -ENOMEM;
}
- memcpy(copy_buf->buf + copy_buf->actual, req->buf, req->actual);
- copy_buf->actual += req->actual;
+ /* Clamp the copy to the space left; req->actual may exceed it */
+ cp_len = min(req->actual, audio_buf_size - copy_buf->actual);
+ memcpy(copy_buf->buf + copy_buf->actual, req->buf, cp_len);
+ copy_buf->actual += cp_len;
audio->copy_buf = copy_buf;
err = usb_ep_queue(ep, req, GFP_ATOMIC);
--
2.25.1