From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wm1-f50.google.com (mail-wm1-f50.google.com [209.85.128.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B78F5364038 for ; Thu, 30 Apr 2026 17:56:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.50 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777571810; cv=none; b=RNXvIq7O40WxsU/uS8zXuucPOKoz7fmaz2O9dG5agDHbr02ATHQcW8hZEqNnEY52KNzk6XvmIWSwelDkJTIR39IgG3fAt78hnpKcH3Oe1ATmn1IuxNd+WiRnqov1zxij5WElXVoASsr4KhpVW8xQBVN24Ft9bQalC2OpJNqQCCw= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777571810; c=relaxed/simple; bh=9spNfy5mYjSbXGFonhvO2PAbckrLShrX0goMofh2LrA=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=qwmrMxdaawz7uLkA+sth+ghQ+whbZe3+whnMUuwL5+sZHHUabDzGeG3MDU+ULVCCI8prgXSOJWF8x84Vy40cHihDd6DyDtN7+iJCDvTa5wzmoSNQqc2EhDZmZvCm4zIYuPXUtcDTYE8lc4/Guc45RRu/+PHokqRaLp8Pat+R8G8= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=DCODzT6c; arc=none smtp.client-ip=209.85.128.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="DCODzT6c" Received: by mail-wm1-f50.google.com with SMTP id 5b1f17b1804b1-4852a9c6309so10561655e9.0 for ; Thu, 30 Apr 2026 10:56:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1777571806; x=1778176606; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=LOoGOmEGcJMWKqKQxy0dOYEQAgzeeW+X/nrMaPWt9b8=; b=DCODzT6cG8ZbkdYIYnd8xZU64MNmg7P/I84rKCEpAstL+kA28g47aJb65zBSQrOBkU aUBaZMyDdmUs2P6LuR/p4q+m3TMU3Xqg4bFccGXL9RKjhvtgltS42ccdtBfrJjLwZ0Co CcZlsfTkUglbOlJWw+tJhRiGLX2zuncsRExIjkZzovqon7CpGrLP/Oamonp3a6hGuMrM VbJYhuTVejPnnG76CysWPD95wI8YxDvyFQkcQRvXAKJiBGybbZVCRL88oeLb++mZ67AA VCd/vmZ9+Oe3Yn6CqajQD+MQXb76UslgzdsYl/QikzzFNYe0T328CMUYWBJ8IhtEgdRi 4PUg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777571806; x=1778176606; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=LOoGOmEGcJMWKqKQxy0dOYEQAgzeeW+X/nrMaPWt9b8=; b=Eu1MHRzQggsI72Zi4B+R74fMptR2bQnS8qoBm8n9PE2DxtigVqh9OcaUu6C9Mvev8U kKMnrJZQ7Prm5SCcsyMcWBcn/4OMKGAYE6Y8y7tPz6UH+vNXYEk0t+9GyUSS8DdMIuIg wydUIQ82mPH7AKMEgXwZO1kzYuFJbHLuQWzK7j1rEXuIkdEKFCWzsyJM6IXsVSjMR1Ft +58zp4N+qo53GAVawpqJiHlAhJXAt/QL1SuuL8j0JWyfRZn3l7BqQ22h35d56yeYFeHo wSjYwLDtPPz7ck9l2Y+VSqzNgGe9Jt7NohqJNnrWlZfjPJo1hGoIRogm6lbj8OjNtfbd ISIA== X-Forwarded-Encrypted: i=1; AFNElJ8apYIoOHoKKpqDAB5rIAlcqogZ1u1pknRHcw8nidfgbF+agNm5b5LSQHCwR9Sqisj7/S48u1xZPg7kzwI=@vger.kernel.org X-Gm-Message-State: AOJu0YxjhBMqSzF6LlrkWppFuGY69hjbZhKyso3crxr4Gzo9PXVEaHP1 GVioguBEdNoRDI99TmGohS33oKavy9JVMkP77PMLkl58GuqdvNxLbjel X-Gm-Gg: AeBDieu4f/G7oJ9/RApASJl1Pr5aNH/x3ooQzypRezH+fwZezZlCexgAZOnfM1oxQ2o oiWwUQf/04rI3qBZkfvN+z3D0oWdEK7I7ulxRu0icGJIXWJpMEUEg7TLd4YpgL39LbUCYaCZF0j RASzDaqhE3OySrMxf8wAtEhiIEKHg6Me1wtQ/sg8J+RhVsvgLOEy/YTCka3LgjabbQYpdCdfT12 5H2CjV9j2DspCJgher7wZQr+axNqDhHZL5qquRajT6XeeitNTJumzXHGbHlwQBu9xUKqQeIUglk +FIiLVI0zzDji/7mQPSrttDNtrT7USRA4Cc3iKl3fBx5qwl0aDGw8MrwWysZV3OpO1iZYlNMoxJ 9UL8G1ILsnXtop3OqlcdFizAF8yVZyRwzujV2586mJBu8N2++kvUU+a4d84+Xf+D5YE1NjRUvG4 6F7kh8+ydH4By/tV9qT7K/UhrqoE5qF9FA/54X0lEEge/hCdQH2+PYqK4JI1wMZbjseTjcUqap5 LJeofZ7Axnn4jm8aHCUhiyZCC54/C3mEB9MkQ== X-Received: by 2002:a05:600c:1991:b0:488:79a3:f04c with SMTP id 5b1f17b1804b1-48a8445fc76mr58580445e9.27.1777571806038; Thu, 30 Apr 2026 10:56:46 -0700 (PDT) Received: from localhost.localdomain ([2a00:a041:e04f:2600:f9d2:9c9e:9a42:5d91]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-48a8d1a4186sm1109915e9.11.2026.04.30.10.56.44 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Thu, 30 Apr 2026 10:56:45 -0700 (PDT) From: Kai Aizen To: linux-usb@vger.kernel.org Cc: laurent.pinchart@ideasonboard.com, balbi@kernel.org, gregkh@linuxfoundation.org, w@1wt.eu, linux-kernel@vger.kernel.org, stable@vger.kernel.org, Kai Aizen Subject: [PATCH v2] usb: gadget: uvc: hold opts->lock across XU walks in uvc_function_bind Date: Thu, 30 Apr 2026 20:56:43 +0300 Message-ID: <20260430175643.67120-1-kai.aizen.dev@gmail.com> X-Mailer: git-send-email 2.53.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit uvc_function_bind() walks &opts->extension_units twice without holding opts->lock: - directly, for the iExtension string-descriptor fixup loop; - indirectly, four times via uvc_copy_descriptors() (once per speed), where the helper iterates uvc->desc.extension_units (which aliases &opts->extension_units) to size and emit XU descriptors. The configfs side (uvcg_extension_make / uvcg_extension_drop, in drivers/usb/gadget/function/uvc_configfs.c) takes opts->lock around its list_add_tail / list_del operations. A privileged userspace process that holds the configfs subtree open and writes the gadget UDC name to bind the function while concurrently rmdir()'ing an extensions subdir can race uvcg_extension_drop() against the bind-time list walks and dereference a freed struct uvcg_extension. Hold opts->lock from the start of the XU string-descriptor fixup through the last uvc_copy_descriptors() call, releasing on the descriptor-error path via a new error_unlock label that drops the lock before falling through to the existing error label. This matches the locking discipline of the configfs callbacks and removes the only remaining unsynchronised reader of the XU list during bind. Reachability: only privileged processes that can mount configfs and write to gadget UDC files can trigger the race, so this is a correctness fix rather than a security boundary. Fixes: 0525210c9840 ("usb: gadget: uvc: Allow definition of XUs in configfs") Cc: stable@vger.kernel.org Signed-off-by: Kai Aizen --- v2: fix From/Signed-off-by to use real name and email address. Drop paulelder@kernel.org (address bounced). --- drivers/usb/gadget/function/f_uvc.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index 8d404d883..73dc7e428 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -768,6 +768,16 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) uvc_hs_streaming_ep.bEndpointAddress = uvc->video.ep->address; uvc_ss_streaming_ep.bEndpointAddress = uvc->video.ep->address; + /* + * Hold opts->lock across both the XU string-descriptor fixup below and + * the descriptor-copy block further down. Without this, configfs + * uvcg_extension_drop() (which takes opts->lock) can race with the + * list_for_each_entry() walks here and inside uvc_copy_descriptors(), + * leading to a UAF on a freed struct uvcg_extension. See + * drivers/usb/gadget/function/uvc_configfs.c::uvcg_extension_drop(). + */ + mutex_lock(&opts->lock); + /* * XUs can have an arbitrary string descriptor describing them. If they * have one pick up the ID. @@ -785,7 +795,7 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) ARRAY_SIZE(uvc_en_us_strings)); if (IS_ERR(us)) { ret = PTR_ERR(us); - goto error; + goto error_unlock; } uvc_iad.iFunction = opts->iad_index ? cdev->usb_strings[opts->iad_index].id : @@ -799,14 +809,14 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) /* Allocate interface IDs. */ if ((ret = usb_interface_id(c, f)) < 0) - goto error; + goto error_unlock; uvc_iad.bFirstInterface = ret; uvc_control_intf.bInterfaceNumber = ret; uvc->control_intf = ret; opts->control_interface = ret; if ((ret = usb_interface_id(c, f)) < 0) - goto error; + goto error_unlock; uvc_streaming_intf_alt0.bInterfaceNumber = ret; uvc_streaming_intf_alt1.bInterfaceNumber = ret; uvc->streaming_intf = ret; @@ -817,30 +827,32 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) if (IS_ERR(f->fs_descriptors)) { ret = PTR_ERR(f->fs_descriptors); f->fs_descriptors = NULL; - goto error; + goto error_unlock; } f->hs_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_HIGH); if (IS_ERR(f->hs_descriptors)) { ret = PTR_ERR(f->hs_descriptors); f->hs_descriptors = NULL; - goto error; + goto error_unlock; } f->ss_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_SUPER); if (IS_ERR(f->ss_descriptors)) { ret = PTR_ERR(f->ss_descriptors); f->ss_descriptors = NULL; - goto error; + goto error_unlock; } f->ssp_descriptors = uvc_copy_descriptors(uvc, USB_SPEED_SUPER_PLUS); if (IS_ERR(f->ssp_descriptors)) { ret = PTR_ERR(f->ssp_descriptors); f->ssp_descriptors = NULL; - goto error; + goto error_unlock; } + mutex_unlock(&opts->lock); + /* Preallocate control endpoint request. */ uvc->control_req = usb_ep_alloc_request(cdev->gadget->ep0, GFP_KERNEL); uvc->control_buf = kmalloc(UVC_MAX_REQUEST_SIZE, GFP_KERNEL); @@ -872,6 +884,8 @@ uvc_function_bind(struct usb_configuration *c, struct usb_function *f) return 0; +error_unlock: + mutex_unlock(&opts->lock); v4l2_error: v4l2_device_unregister(&uvc->v4l2_dev); error: -- 2.43.0