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

Haofeng Li <lihaofeng-UOlijcLmZ/[email protected]>
Newsgroups gmane.linux.usb.general,gmane.linux.kernel
Message-ID <[email protected]>
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-UOlijcLmZ/[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.