[PATCH] usb: gadget: f_uac1_legacy: fix heap overflow in f_audio_out_ep_complete()

Haofeng Li posted 1 patch 1 month ago
drivers/usb/gadget/function/f_uac1_legacy.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
[PATCH] usb: gadget: f_uac1_legacy: fix heap overflow in f_audio_out_ep_complete()
Posted by Haofeng Li 1 month ago
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@kylinos.cn>
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(&copy_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