]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
6.12-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 8 Apr 2026 12:04:15 +0000 (14:04 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 8 Apr 2026 12:04:15 +0000 (14:04 +0200)
added patches:
usb-gadget-f_ecm-fix-net_device-lifecycle-with-device_move.patch
usb-gadget-f_eem-fix-net_device-lifecycle-with-device_move.patch
usb-gadget-f_hid-move-list-and-spinlock-inits-from-bind-to-alloc.patch
usb-gadget-f_rndis-fix-net_device-lifecycle-with-device_move.patch
usb-gadget-f_rndis-protect-rndis-options-with-mutex.patch
usb-gadget-f_subset-fix-net_device-lifecycle-with-device_move.patch
usb-gadget-f_subset-fix-unbalanced-refcnt-in-geth_free.patch
usb-gadget-f_uac1_legacy-validate-control-request-size.patch
usb-gadget-u_ether-fix-null-pointer-deref-in-eth_get_drvinfo.patch
usb-gadget-u_ether-fix-race-between-gether_disconnect-and-eth_stop.patch
usb-gadget-uvc-fix-null-pointer-dereference-during-unbind-race.patch

12 files changed:
queue-6.12/series
queue-6.12/usb-gadget-f_ecm-fix-net_device-lifecycle-with-device_move.patch [new file with mode: 0644]
queue-6.12/usb-gadget-f_eem-fix-net_device-lifecycle-with-device_move.patch [new file with mode: 0644]
queue-6.12/usb-gadget-f_hid-move-list-and-spinlock-inits-from-bind-to-alloc.patch [new file with mode: 0644]
queue-6.12/usb-gadget-f_rndis-fix-net_device-lifecycle-with-device_move.patch [new file with mode: 0644]
queue-6.12/usb-gadget-f_rndis-protect-rndis-options-with-mutex.patch [new file with mode: 0644]
queue-6.12/usb-gadget-f_subset-fix-net_device-lifecycle-with-device_move.patch [new file with mode: 0644]
queue-6.12/usb-gadget-f_subset-fix-unbalanced-refcnt-in-geth_free.patch [new file with mode: 0644]
queue-6.12/usb-gadget-f_uac1_legacy-validate-control-request-size.patch [new file with mode: 0644]
queue-6.12/usb-gadget-u_ether-fix-null-pointer-deref-in-eth_get_drvinfo.patch [new file with mode: 0644]
queue-6.12/usb-gadget-u_ether-fix-race-between-gether_disconnect-and-eth_stop.patch [new file with mode: 0644]
queue-6.12/usb-gadget-uvc-fix-null-pointer-dereference-during-unbind-race.patch [new file with mode: 0644]

index a09268168334203eeb64c4f7c616dcf03250bbfa..a50945e557a9f3625052a0df81f4fcb6a29da181 100644 (file)
@@ -204,3 +204,14 @@ usb-typec-ucsi-validate-connector-number-in-ucsi_notify_common.patch
 ice-fix-memory-leak-in-ice_set_ringparam.patch
 btrfs-fix-the-qgroup-data-free-range-for-inline-data.patch
 btrfs-do-not-free-data-reservation-in-fallback-from-.patch
+usb-gadget-u_ether-fix-race-between-gether_disconnect-and-eth_stop.patch
+usb-gadget-u_ether-fix-null-pointer-deref-in-eth_get_drvinfo.patch
+usb-gadget-uvc-fix-null-pointer-dereference-during-unbind-race.patch
+usb-gadget-f_subset-fix-unbalanced-refcnt-in-geth_free.patch
+usb-gadget-f_rndis-protect-rndis-options-with-mutex.patch
+usb-gadget-f_ecm-fix-net_device-lifecycle-with-device_move.patch
+usb-gadget-f_eem-fix-net_device-lifecycle-with-device_move.patch
+usb-gadget-f_subset-fix-net_device-lifecycle-with-device_move.patch
+usb-gadget-f_rndis-fix-net_device-lifecycle-with-device_move.patch
+usb-gadget-f_hid-move-list-and-spinlock-inits-from-bind-to-alloc.patch
+usb-gadget-f_uac1_legacy-validate-control-request-size.patch
diff --git a/queue-6.12/usb-gadget-f_ecm-fix-net_device-lifecycle-with-device_move.patch b/queue-6.12/usb-gadget-f_ecm-fix-net_device-lifecycle-with-device_move.patch
new file mode 100644 (file)
index 0000000..4ebe3b2
--- /dev/null
@@ -0,0 +1,160 @@
+From b2cc4fae67a51f60d81d6af2678696accb07c656 Mon Sep 17 00:00:00 2001
+From: Kuen-Han Tsai <khtsai@google.com>
+Date: Fri, 20 Mar 2026 16:54:47 +0800
+Subject: usb: gadget: f_ecm: Fix net_device lifecycle with device_move
+
+From: Kuen-Han Tsai <khtsai@google.com>
+
+commit b2cc4fae67a51f60d81d6af2678696accb07c656 upstream.
+
+The net_device is allocated during function instance creation and
+registered during the bind phase with the gadget device as its sysfs
+parent. When the function unbinds, the parent device is destroyed, but
+the net_device survives, resulting in dangling sysfs symlinks:
+
+  console:/ # ls -l /sys/class/net/usb0
+  lrwxrwxrwx ... /sys/class/net/usb0 ->
+  /sys/devices/platform/.../gadget.0/net/usb0
+  console:/ # ls -l /sys/devices/platform/.../gadget.0/net/usb0
+  ls: .../gadget.0/net/usb0: No such file or directory
+
+Use device_move() to reparent the net_device between the gadget device
+tree and /sys/devices/virtual across bind and unbind cycles. During the
+final unbind, calling device_move(NULL) moves the net_device to the
+virtual device tree before the gadget device is destroyed. On rebinding,
+device_move() reparents the device back under the new gadget, ensuring
+proper sysfs topology and power management ordering.
+
+To maintain compatibility with legacy composite drivers (e.g., multi.c),
+the bound flag is used to indicate whether the network device is shared
+and pre-registered during the legacy driver's bind phase.
+
+Fixes: fee562a6450b ("usb: gadget: f_ecm: convert to new function interface with backward compatibility")
+Cc: stable@vger.kernel.org
+Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
+Link: https://patch.msgid.link/20260320-usb-net-lifecycle-v1-4-4886b578161b@google.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/f_ecm.c |   37 +++++++++++++++++++++++-------------
+ drivers/usb/gadget/function/u_ecm.h |   21 ++++++++++++++------
+ 2 files changed, 39 insertions(+), 19 deletions(-)
+
+--- a/drivers/usb/gadget/function/f_ecm.c
++++ b/drivers/usb/gadget/function/f_ecm.c
+@@ -681,6 +681,7 @@ ecm_bind(struct usb_configuration *c, st
+       struct usb_ep           *ep;
+       struct f_ecm_opts       *ecm_opts;
++      struct net_device       *net __free(detach_gadget) = NULL;
+       struct usb_request      *request __free(free_usb_request) = NULL;
+       if (!can_support_ecm(cdev->gadget))
+@@ -688,18 +689,18 @@ ecm_bind(struct usb_configuration *c, st
+       ecm_opts = container_of(f->fi, struct f_ecm_opts, func_inst);
+-      mutex_lock(&ecm_opts->lock);
+-
+-      gether_set_gadget(ecm_opts->net, cdev->gadget);
+-
+-      if (!ecm_opts->bound) {
+-              status = gether_register_netdev(ecm_opts->net);
+-              ecm_opts->bound = true;
+-      }
+-
+-      mutex_unlock(&ecm_opts->lock);
+-      if (status)
+-              return status;
++      scoped_guard(mutex, &ecm_opts->lock)
++              if (ecm_opts->bind_count == 0 && !ecm_opts->bound) {
++                      if (!device_is_registered(&ecm_opts->net->dev)) {
++                              gether_set_gadget(ecm_opts->net, cdev->gadget);
++                              status = gether_register_netdev(ecm_opts->net);
++                      } else
++                              status = gether_attach_gadget(ecm_opts->net, cdev->gadget);
++
++                      if (status)
++                              return status;
++                      net = ecm_opts->net;
++              }
+       ecm_string_defs[1].s = ecm->ethaddr;
+@@ -790,6 +791,9 @@ ecm_bind(struct usb_configuration *c, st
+       ecm->notify_req = no_free_ptr(request);
++      ecm_opts->bind_count++;
++      retain_and_null_ptr(net);
++
+       DBG(cdev, "CDC Ethernet: IN/%s OUT/%s NOTIFY/%s\n",
+                       ecm->port.in_ep->name, ecm->port.out_ep->name,
+                       ecm->notify->name);
+@@ -836,7 +840,7 @@ static void ecm_free_inst(struct usb_fun
+       struct f_ecm_opts *opts;
+       opts = container_of(f, struct f_ecm_opts, func_inst);
+-      if (opts->bound)
++      if (device_is_registered(&opts->net->dev))
+               gether_cleanup(netdev_priv(opts->net));
+       else
+               free_netdev(opts->net);
+@@ -906,9 +910,12 @@ static void ecm_free(struct usb_function
+ static void ecm_unbind(struct usb_configuration *c, struct usb_function *f)
+ {
+       struct f_ecm            *ecm = func_to_ecm(f);
++      struct f_ecm_opts       *ecm_opts;
+       DBG(c->cdev, "ecm unbind\n");
++      ecm_opts = container_of(f->fi, struct f_ecm_opts, func_inst);
++
+       usb_free_all_descriptors(f);
+       if (atomic_read(&ecm->notify_count)) {
+@@ -918,6 +925,10 @@ static void ecm_unbind(struct usb_config
+       kfree(ecm->notify_req->buf);
+       usb_ep_free_request(ecm->notify, ecm->notify_req);
++
++      ecm_opts->bind_count--;
++      if (ecm_opts->bind_count == 0 && !ecm_opts->bound)
++              gether_detach_gadget(ecm_opts->net);
+ }
+ static struct usb_function *ecm_alloc(struct usb_function_instance *fi)
+--- a/drivers/usb/gadget/function/u_ecm.h
++++ b/drivers/usb/gadget/function/u_ecm.h
+@@ -15,17 +15,26 @@
+ #include <linux/usb/composite.h>
++/**
++ * struct f_ecm_opts - ECM function options
++ * @func_inst: USB function instance.
++ * @net: The net_device associated with the ECM function.
++ * @bound: True if the net_device is shared and pre-registered during the
++ *         legacy composite driver's bind phase (e.g., multi.c). If false,
++ *         the ECM function will register the net_device during its own
++ *         bind phase.
++ * @bind_count: Tracks the number of configurations the ECM function is
++ *              bound to, preventing double-registration of the @net device.
++ * @lock: Protects the data from concurrent access by configfs read/write
++ *        and create symlink/remove symlink operations.
++ * @refcnt: Reference counter for the function instance.
++ */
+ struct f_ecm_opts {
+       struct usb_function_instance    func_inst;
+       struct net_device               *net;
+       bool                            bound;
++      int                             bind_count;
+-      /*
+-       * Read/write access to configfs attributes is handled by configfs.
+-       *
+-       * This is to protect the data from concurrent access by read/write
+-       * and create symlink/remove symlink.
+-       */
+       struct mutex                    lock;
+       int                             refcnt;
+ };
diff --git a/queue-6.12/usb-gadget-f_eem-fix-net_device-lifecycle-with-device_move.patch b/queue-6.12/usb-gadget-f_eem-fix-net_device-lifecycle-with-device_move.patch
new file mode 100644 (file)
index 0000000..8925877
--- /dev/null
@@ -0,0 +1,198 @@
+From d9270c9a8118c1535409db926ac1e2545dc97b81 Mon Sep 17 00:00:00 2001
+From: Kuen-Han Tsai <khtsai@google.com>
+Date: Fri, 20 Mar 2026 16:54:48 +0800
+Subject: usb: gadget: f_eem: Fix net_device lifecycle with device_move
+
+From: Kuen-Han Tsai <khtsai@google.com>
+
+commit d9270c9a8118c1535409db926ac1e2545dc97b81 upstream.
+
+The net_device is allocated during function instance creation and
+registered during the bind phase with the gadget device as its sysfs
+parent. When the function unbinds, the parent device is destroyed, but
+the net_device survives, resulting in dangling sysfs symlinks:
+
+console:/ # ls -l /sys/class/net/usb0
+lrwxrwxrwx ... /sys/class/net/usb0 ->
+/sys/devices/platform/.../gadget.0/net/usb0
+console:/ # ls -l /sys/devices/platform/.../gadget.0/net/usb0
+ls: .../gadget.0/net/usb0: No such file or directory
+
+Use device_move() to reparent the net_device between the gadget device
+tree and /sys/devices/virtual across bind and unbind cycles. During the
+final unbind, calling device_move(NULL) moves the net_device to the
+virtual device tree before the gadget device is destroyed. On rebinding,
+device_move() reparents the device back under the new gadget, ensuring
+proper sysfs topology and power management ordering.
+
+To maintain compatibility with legacy composite drivers (e.g., multi.c),
+the bound flag is used to indicate whether the network device is shared
+and pre-registered during the legacy driver's bind phase.
+
+Fixes: b29002a15794 ("usb: gadget: f_eem: convert to new function interface with backward compatibility")
+Cc: stable@vger.kernel.org
+Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
+Link: https://patch.msgid.link/20260320-usb-net-lifecycle-v1-5-4886b578161b@google.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/f_eem.c |   59 ++++++++++++++++++------------------
+ drivers/usb/gadget/function/u_eem.h |   21 +++++++++---
+ 2 files changed, 46 insertions(+), 34 deletions(-)
+
+--- a/drivers/usb/gadget/function/f_eem.c
++++ b/drivers/usb/gadget/function/f_eem.c
+@@ -7,6 +7,7 @@
+  * Copyright (C) 2009 EF Johnson Technologies
+  */
++#include <linux/cleanup.h>
+ #include <linux/kernel.h>
+ #include <linux/module.h>
+ #include <linux/device.h>
+@@ -251,24 +252,22 @@ static int eem_bind(struct usb_configura
+       struct usb_ep           *ep;
+       struct f_eem_opts       *eem_opts;
++      struct net_device       *net __free(detach_gadget) = NULL;
+       eem_opts = container_of(f->fi, struct f_eem_opts, func_inst);
+-      /*
+-       * in drivers/usb/gadget/configfs.c:configfs_composite_bind()
+-       * configurations are bound in sequence with list_for_each_entry,
+-       * in each configuration its functions are bound in sequence
+-       * with list_for_each_entry, so we assume no race condition
+-       * with regard to eem_opts->bound access
+-       */
+-      if (!eem_opts->bound) {
+-              mutex_lock(&eem_opts->lock);
+-              gether_set_gadget(eem_opts->net, cdev->gadget);
+-              status = gether_register_netdev(eem_opts->net);
+-              mutex_unlock(&eem_opts->lock);
+-              if (status)
+-                      return status;
+-              eem_opts->bound = true;
+-      }
++
++      scoped_guard(mutex, &eem_opts->lock)
++              if (eem_opts->bind_count == 0 && !eem_opts->bound) {
++                      if (!device_is_registered(&eem_opts->net->dev)) {
++                              gether_set_gadget(eem_opts->net, cdev->gadget);
++                              status = gether_register_netdev(eem_opts->net);
++                      } else
++                              status = gether_attach_gadget(eem_opts->net, cdev->gadget);
++
++                      if (status)
++                              return status;
++                      net = eem_opts->net;
++              }
+       us = usb_gstrings_attach(cdev, eem_strings,
+                                ARRAY_SIZE(eem_string_defs));
+@@ -279,21 +278,19 @@ static int eem_bind(struct usb_configura
+       /* allocate instance-specific interface IDs */
+       status = usb_interface_id(c, f);
+       if (status < 0)
+-              goto fail;
++              return status;
+       eem->ctrl_id = status;
+       eem_intf.bInterfaceNumber = status;
+-      status = -ENODEV;
+-
+       /* allocate instance-specific endpoints */
+       ep = usb_ep_autoconfig(cdev->gadget, &eem_fs_in_desc);
+       if (!ep)
+-              goto fail;
++              return -ENODEV;
+       eem->port.in_ep = ep;
+       ep = usb_ep_autoconfig(cdev->gadget, &eem_fs_out_desc);
+       if (!ep)
+-              goto fail;
++              return -ENODEV;
+       eem->port.out_ep = ep;
+       /* support all relevant hardware speeds... we expect that when
+@@ -309,16 +306,14 @@ static int eem_bind(struct usb_configura
+       status = usb_assign_descriptors(f, eem_fs_function, eem_hs_function,
+                       eem_ss_function, eem_ss_function);
+       if (status)
+-              goto fail;
++              return status;
++
++      eem_opts->bind_count++;
++      retain_and_null_ptr(net);
+       DBG(cdev, "CDC Ethernet (EEM): IN/%s OUT/%s\n",
+                       eem->port.in_ep->name, eem->port.out_ep->name);
+       return 0;
+-
+-fail:
+-      ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
+-
+-      return status;
+ }
+ static void eem_cmd_complete(struct usb_ep *ep, struct usb_request *req)
+@@ -597,7 +592,7 @@ static void eem_free_inst(struct usb_fun
+       struct f_eem_opts *opts;
+       opts = container_of(f, struct f_eem_opts, func_inst);
+-      if (opts->bound)
++      if (device_is_registered(&opts->net->dev))
+               gether_cleanup(netdev_priv(opts->net));
+       else
+               free_netdev(opts->net);
+@@ -640,9 +635,17 @@ static void eem_free(struct usb_function
+ static void eem_unbind(struct usb_configuration *c, struct usb_function *f)
+ {
++      struct f_eem_opts *opts;
++
+       DBG(c->cdev, "eem unbind\n");
++      opts = container_of(f->fi, struct f_eem_opts, func_inst);
++
+       usb_free_all_descriptors(f);
++
++      opts->bind_count--;
++      if (opts->bind_count == 0 && !opts->bound)
++              gether_detach_gadget(opts->net);
+ }
+ static struct usb_function *eem_alloc(struct usb_function_instance *fi)
+--- a/drivers/usb/gadget/function/u_eem.h
++++ b/drivers/usb/gadget/function/u_eem.h
+@@ -15,17 +15,26 @@
+ #include <linux/usb/composite.h>
++/**
++ * struct f_eem_opts - EEM function options
++ * @func_inst: USB function instance.
++ * @net: The net_device associated with the EEM function.
++ * @bound: True if the net_device is shared and pre-registered during the
++ *         legacy composite driver's bind phase (e.g., multi.c). If false,
++ *         the EEM function will register the net_device during its own
++ *         bind phase.
++ * @bind_count: Tracks the number of configurations the EEM function is
++ *              bound to, preventing double-registration of the @net device.
++ * @lock: Protects the data from concurrent access by configfs read/write
++ *        and create symlink/remove symlink operations.
++ * @refcnt: Reference counter for the function instance.
++ */
+ struct f_eem_opts {
+       struct usb_function_instance    func_inst;
+       struct net_device               *net;
+       bool                            bound;
++      int                             bind_count;
+-      /*
+-       * Read/write access to configfs attributes is handled by configfs.
+-       *
+-       * This is to protect the data from concurrent access by read/write
+-       * and create symlink/remove symlink.
+-       */
+       struct mutex                    lock;
+       int                             refcnt;
+ };
diff --git a/queue-6.12/usb-gadget-f_hid-move-list-and-spinlock-inits-from-bind-to-alloc.patch b/queue-6.12/usb-gadget-f_hid-move-list-and-spinlock-inits-from-bind-to-alloc.patch
new file mode 100644 (file)
index 0000000..801214a
--- /dev/null
@@ -0,0 +1,75 @@
+From 4e0a88254ad59f6c53a34bf5fa241884ec09e8b2 Mon Sep 17 00:00:00 2001
+From: Michael Zimmermann <sigmaepsilon92@gmail.com>
+Date: Tue, 31 Mar 2026 20:48:44 +0200
+Subject: usb: gadget: f_hid: move list and spinlock inits from bind to alloc
+
+From: Michael Zimmermann <sigmaepsilon92@gmail.com>
+
+commit 4e0a88254ad59f6c53a34bf5fa241884ec09e8b2 upstream.
+
+There was an issue when you did the following:
+- setup and bind an hid gadget
+- open /dev/hidg0
+- use the resulting fd in EPOLL_CTL_ADD
+- unbind the UDC
+- bind the UDC
+- use the fd in EPOLL_CTL_DEL
+
+When CONFIG_DEBUG_LIST was enabled, a list_del corruption was reported
+within remove_wait_queue (via ep_remove_wait_queue). After some
+debugging I found out that the queues, which f_hid registers via
+poll_wait were the problem. These were initialized using
+init_waitqueue_head inside hidg_bind. So effectively, the bind function
+re-initialized the queues while there were still items in them.
+
+The solution is to move the initialization from hidg_bind to hidg_alloc
+to extend their lifetimes to the lifetime of the function instance.
+
+Additionally, I found many other possibly problematic init calls in the
+bind function, which I moved as well.
+
+Signed-off-by: Michael Zimmermann <sigmaepsilon92@gmail.com>
+Cc: stable <stable@kernel.org>
+Link: https://patch.msgid.link/20260331184844.2388761-1-sigmaepsilon92@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/f_hid.c |   19 ++++++++++---------
+ 1 file changed, 10 insertions(+), 9 deletions(-)
+
+--- a/drivers/usb/gadget/function/f_hid.c
++++ b/drivers/usb/gadget/function/f_hid.c
+@@ -1255,17 +1255,8 @@ static int hidg_bind(struct usb_configur
+       if (status)
+               goto fail;
+-      spin_lock_init(&hidg->write_spinlock);
+       hidg->write_pending = 1;
+       hidg->req = NULL;
+-      spin_lock_init(&hidg->read_spinlock);
+-      spin_lock_init(&hidg->get_report_spinlock);
+-      init_waitqueue_head(&hidg->write_queue);
+-      init_waitqueue_head(&hidg->read_queue);
+-      init_waitqueue_head(&hidg->get_queue);
+-      init_waitqueue_head(&hidg->get_id_queue);
+-      INIT_LIST_HEAD(&hidg->completed_out_req);
+-      INIT_LIST_HEAD(&hidg->report_list);
+       INIT_WORK(&hidg->work, get_report_workqueue_handler);
+       hidg->workqueue = alloc_workqueue("report_work",
+@@ -1550,6 +1541,16 @@ static struct usb_function *hidg_alloc(s
+       mutex_lock(&opts->lock);
++      spin_lock_init(&hidg->write_spinlock);
++      spin_lock_init(&hidg->read_spinlock);
++      spin_lock_init(&hidg->get_report_spinlock);
++      init_waitqueue_head(&hidg->write_queue);
++      init_waitqueue_head(&hidg->read_queue);
++      init_waitqueue_head(&hidg->get_queue);
++      init_waitqueue_head(&hidg->get_id_queue);
++      INIT_LIST_HEAD(&hidg->completed_out_req);
++      INIT_LIST_HEAD(&hidg->report_list);
++
+       device_initialize(&hidg->dev);
+       hidg->dev.release = hidg_release;
+       hidg->dev.class = &hidg_class;
diff --git a/queue-6.12/usb-gadget-f_rndis-fix-net_device-lifecycle-with-device_move.patch b/queue-6.12/usb-gadget-f_rndis-fix-net_device-lifecycle-with-device_move.patch
new file mode 100644 (file)
index 0000000..5cfcac7
--- /dev/null
@@ -0,0 +1,190 @@
+From e367599529dc42578545a7f85fde517b35b3cda7 Mon Sep 17 00:00:00 2001
+From: Kuen-Han Tsai <khtsai@google.com>
+Date: Fri, 20 Mar 2026 16:54:50 +0800
+Subject: usb: gadget: f_rndis: Fix net_device lifecycle with device_move
+
+From: Kuen-Han Tsai <khtsai@google.com>
+
+commit e367599529dc42578545a7f85fde517b35b3cda7 upstream.
+
+The net_device is allocated during function instance creation and
+registered during the bind phase with the gadget device as its sysfs
+parent. When the function unbinds, the parent device is destroyed, but
+the net_device survives, resulting in dangling sysfs symlinks:
+
+  console:/ # ls -l /sys/class/net/usb0
+  lrwxrwxrwx ... /sys/class/net/usb0 ->
+  /sys/devices/platform/.../gadget.0/net/usb0
+  console:/ # ls -l /sys/devices/platform/.../gadget.0/net/usb0
+  ls: .../gadget.0/net/usb0: No such file or directory
+
+Use device_move() to reparent the net_device between the gadget device
+tree and /sys/devices/virtual across bind and unbind cycles. During the
+final unbind, calling device_move(NULL) moves the net_device to the
+virtual device tree before the gadget device is destroyed. On rebinding,
+device_move() reparents the device back under the new gadget, ensuring
+proper sysfs topology and power management ordering.
+
+To maintain compatibility with legacy composite drivers (e.g., multi.c),
+the borrowed_net flag is used to indicate whether the network device is
+shared and pre-registered during the legacy driver's bind phase.
+
+Fixes: f466c6353819 ("usb: gadget: f_rndis: convert to new function interface with backward compatibility")
+Cc: stable@vger.kernel.org
+Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
+Link: https://patch.msgid.link/20260320-usb-net-lifecycle-v1-7-4886b578161b@google.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/f_rndis.c |   42 ++++++++++++++++++++--------------
+ drivers/usb/gadget/function/u_rndis.h |   31 ++++++++++++++++++-------
+ 2 files changed, 48 insertions(+), 25 deletions(-)
+
+--- a/drivers/usb/gadget/function/f_rndis.c
++++ b/drivers/usb/gadget/function/f_rndis.c
+@@ -666,6 +666,7 @@ rndis_bind(struct usb_configuration *c,
+       struct f_rndis_opts *rndis_opts;
+       struct usb_os_desc_table        *os_desc_table __free(kfree) = NULL;
++      struct net_device               *net __free(detach_gadget) = NULL;
+       struct usb_request              *request __free(free_usb_request) = NULL;
+       if (!can_support_rndis(c))
+@@ -683,21 +684,18 @@ rndis_bind(struct usb_configuration *c,
+               rndis_iad_descriptor.bFunctionClass = rndis_opts->class;
+               rndis_iad_descriptor.bFunctionSubClass = rndis_opts->subclass;
+               rndis_iad_descriptor.bFunctionProtocol = rndis_opts->protocol;
+-      }
+-      /*
+-       * in drivers/usb/gadget/configfs.c:configfs_composite_bind()
+-       * configurations are bound in sequence with list_for_each_entry,
+-       * in each configuration its functions are bound in sequence
+-       * with list_for_each_entry, so we assume no race condition
+-       * with regard to rndis_opts->bound access
+-       */
+-      if (!rndis_opts->bound) {
+-              gether_set_gadget(rndis_opts->net, cdev->gadget);
+-              status = gether_register_netdev(rndis_opts->net);
+-              if (status)
+-                      return status;
+-              rndis_opts->bound = true;
++              if (rndis_opts->bind_count == 0 && !rndis_opts->borrowed_net) {
++                      if (!device_is_registered(&rndis_opts->net->dev)) {
++                              gether_set_gadget(rndis_opts->net, cdev->gadget);
++                              status = gether_register_netdev(rndis_opts->net);
++                      } else
++                              status = gether_attach_gadget(rndis_opts->net, cdev->gadget);
++
++                      if (status)
++                              return status;
++                      net = rndis_opts->net;
++              }
+       }
+       us = usb_gstrings_attach(cdev, rndis_strings,
+@@ -796,6 +794,9 @@ rndis_bind(struct usb_configuration *c,
+       }
+       rndis->notify_req = no_free_ptr(request);
++      rndis_opts->bind_count++;
++      retain_and_null_ptr(net);
++
+       /* NOTE:  all that is done without knowing or caring about
+        * the network link ... which is unavailable to this code
+        * until we're activated via set_alt().
+@@ -812,11 +813,11 @@ void rndis_borrow_net(struct usb_functio
+       struct f_rndis_opts *opts;
+       opts = container_of(f, struct f_rndis_opts, func_inst);
+-      if (opts->bound)
++      if (device_is_registered(&opts->net->dev))
+               gether_cleanup(netdev_priv(opts->net));
+       else
+               free_netdev(opts->net);
+-      opts->borrowed_net = opts->bound = true;
++      opts->borrowed_net = true;
+       opts->net = net;
+ }
+ EXPORT_SYMBOL_GPL(rndis_borrow_net);
+@@ -874,7 +875,7 @@ static void rndis_free_inst(struct usb_f
+       opts = container_of(f, struct f_rndis_opts, func_inst);
+       if (!opts->borrowed_net) {
+-              if (opts->bound)
++              if (device_is_registered(&opts->net->dev))
+                       gether_cleanup(netdev_priv(opts->net));
+               else
+                       free_netdev(opts->net);
+@@ -943,6 +944,9 @@ static void rndis_free(struct usb_functi
+ static void rndis_unbind(struct usb_configuration *c, struct usb_function *f)
+ {
+       struct f_rndis          *rndis = func_to_rndis(f);
++      struct f_rndis_opts     *rndis_opts;
++
++      rndis_opts = container_of(f->fi, struct f_rndis_opts, func_inst);
+       kfree(f->os_desc_table);
+       f->os_desc_n = 0;
+@@ -950,6 +954,10 @@ static void rndis_unbind(struct usb_conf
+       kfree(rndis->notify_req->buf);
+       usb_ep_free_request(rndis->notify, rndis->notify_req);
++
++      rndis_opts->bind_count--;
++      if (rndis_opts->bind_count == 0 && !rndis_opts->borrowed_net)
++              gether_detach_gadget(rndis_opts->net);
+ }
+ static struct usb_function *rndis_alloc(struct usb_function_instance *fi)
+--- a/drivers/usb/gadget/function/u_rndis.h
++++ b/drivers/usb/gadget/function/u_rndis.h
+@@ -15,12 +15,34 @@
+ #include <linux/usb/composite.h>
++/**
++ * struct f_rndis_opts - RNDIS function options
++ * @func_inst: USB function instance.
++ * @vendor_id: Vendor ID.
++ * @manufacturer: Manufacturer string.
++ * @net: The net_device associated with the RNDIS function.
++ * @bind_count: Tracks the number of configurations the RNDIS function is
++ *              bound to, preventing double-registration of the @net device.
++ * @borrowed_net: True if the net_device is shared and pre-registered during
++ *                the legacy composite driver's bind phase (e.g., multi.c).
++ *                If false, the RNDIS function will register the net_device
++ *                during its own bind phase.
++ * @rndis_interf_group: ConfigFS group for RNDIS interface.
++ * @rndis_os_desc: USB OS descriptor for RNDIS.
++ * @rndis_ext_compat_id: Extended compatibility ID.
++ * @class: USB class.
++ * @subclass: USB subclass.
++ * @protocol: USB protocol.
++ * @lock: Protects the data from concurrent access by configfs read/write
++ *        and create symlink/remove symlink operations.
++ * @refcnt: Reference counter for the function instance.
++ */
+ struct f_rndis_opts {
+       struct usb_function_instance    func_inst;
+       u32                             vendor_id;
+       const char                      *manufacturer;
+       struct net_device               *net;
+-      bool                            bound;
++      int                             bind_count;
+       bool                            borrowed_net;
+       struct config_group             *rndis_interf_group;
+@@ -30,13 +52,6 @@ struct f_rndis_opts {
+       u8                              class;
+       u8                              subclass;
+       u8                              protocol;
+-
+-      /*
+-       * Read/write access to configfs attributes is handled by configfs.
+-       *
+-       * This is to protect the data from concurrent access by read/write
+-       * and create symlink/remove symlink.
+-       */
+       struct mutex                    lock;
+       int                             refcnt;
+ };
diff --git a/queue-6.12/usb-gadget-f_rndis-protect-rndis-options-with-mutex.patch b/queue-6.12/usb-gadget-f_rndis-protect-rndis-options-with-mutex.patch
new file mode 100644 (file)
index 0000000..623324b
--- /dev/null
@@ -0,0 +1,49 @@
+From 8d8c68b1fc06ece60cf43e1306ff0f4ac121547e Mon Sep 17 00:00:00 2001
+From: Kuen-Han Tsai <khtsai@google.com>
+Date: Fri, 20 Mar 2026 16:54:45 +0800
+Subject: usb: gadget: f_rndis: Protect RNDIS options with mutex
+
+From: Kuen-Han Tsai <khtsai@google.com>
+
+commit 8d8c68b1fc06ece60cf43e1306ff0f4ac121547e upstream.
+
+The class/subclass/protocol options are suspectible to race conditions
+as they can be accessed concurrently through configfs.
+
+Use existing mutex to protect these options. This issue was identified
+during code inspection.
+
+Fixes: 73517cf49bd4 ("usb: gadget: add RNDIS configfs options for class/subclass/protocol")
+Cc: stable@vger.kernel.org
+Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
+Link: https://patch.msgid.link/20260320-usb-net-lifecycle-v1-2-4886b578161b@google.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/f_rndis.c |    9 ++++++---
+ 1 file changed, 6 insertions(+), 3 deletions(-)
+
+--- a/drivers/usb/gadget/function/f_rndis.c
++++ b/drivers/usb/gadget/function/f_rndis.c
+@@ -11,6 +11,7 @@
+ /* #define VERBOSE_DEBUG */
++#include <linux/cleanup.h>
+ #include <linux/slab.h>
+ #include <linux/kernel.h>
+ #include <linux/module.h>
+@@ -678,9 +679,11 @@ rndis_bind(struct usb_configuration *c,
+                       return -ENOMEM;
+       }
+-      rndis_iad_descriptor.bFunctionClass = rndis_opts->class;
+-      rndis_iad_descriptor.bFunctionSubClass = rndis_opts->subclass;
+-      rndis_iad_descriptor.bFunctionProtocol = rndis_opts->protocol;
++      scoped_guard(mutex, &rndis_opts->lock) {
++              rndis_iad_descriptor.bFunctionClass = rndis_opts->class;
++              rndis_iad_descriptor.bFunctionSubClass = rndis_opts->subclass;
++              rndis_iad_descriptor.bFunctionProtocol = rndis_opts->protocol;
++      }
+       /*
+        * in drivers/usb/gadget/configfs.c:configfs_composite_bind()
diff --git a/queue-6.12/usb-gadget-f_subset-fix-net_device-lifecycle-with-device_move.patch b/queue-6.12/usb-gadget-f_subset-fix-net_device-lifecycle-with-device_move.patch
new file mode 100644 (file)
index 0000000..7d0991e
--- /dev/null
@@ -0,0 +1,193 @@
+From 06524cd1c9011bee141a87e43ab878641ed3652b Mon Sep 17 00:00:00 2001
+From: Kuen-Han Tsai <khtsai@google.com>
+Date: Fri, 20 Mar 2026 16:54:49 +0800
+Subject: usb: gadget: f_subset: Fix net_device lifecycle with device_move
+
+From: Kuen-Han Tsai <khtsai@google.com>
+
+commit 06524cd1c9011bee141a87e43ab878641ed3652b upstream.
+
+The net_device is allocated during function instance creation and
+registered during the bind phase with the gadget device as its sysfs
+parent. When the function unbinds, the parent device is destroyed, but
+the net_device survives, resulting in dangling sysfs symlinks:
+
+  console:/ # ls -l /sys/class/net/usb0
+  lrwxrwxrwx ... /sys/class/net/usb0 ->
+  /sys/devices/platform/.../gadget.0/net/usb0
+  console:/ # ls -l /sys/devices/platform/.../gadget.0/net/usb0
+  ls: .../gadget.0/net/usb0: No such file or directory
+
+Use device_move() to reparent the net_device between the gadget device
+tree and /sys/devices/virtual across bind and unbind cycles. During the
+final unbind, calling device_move(NULL) moves the net_device to the
+virtual device tree before the gadget device is destroyed. On rebinding,
+device_move() reparents the device back under the new gadget, ensuring
+proper sysfs topology and power management ordering.
+
+To maintain compatibility with legacy composite drivers (e.g., multi.c),
+the bound flag is used to indicate whether the network device is shared
+and pre-registered during the legacy driver's bind phase.
+
+Fixes: 8cedba7c73af ("usb: gadget: f_subset: convert to new function interface with backward compatibility")
+Cc: stable@vger.kernel.org
+Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
+Link: https://patch.msgid.link/20260320-usb-net-lifecycle-v1-6-4886b578161b@google.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/f_subset.c |   57 ++++++++++++++++-----------------
+ drivers/usb/gadget/function/u_gether.h |   22 ++++++++----
+ 2 files changed, 44 insertions(+), 35 deletions(-)
+
+--- a/drivers/usb/gadget/function/f_subset.c
++++ b/drivers/usb/gadget/function/f_subset.c
+@@ -299,25 +299,22 @@ geth_bind(struct usb_configuration *c, s
+       struct usb_ep           *ep;
+       struct f_gether_opts    *gether_opts;
++      struct net_device       *net __free(detach_gadget) = NULL;
+       gether_opts = container_of(f->fi, struct f_gether_opts, func_inst);
+-      /*
+-       * in drivers/usb/gadget/configfs.c:configfs_composite_bind()
+-       * configurations are bound in sequence with list_for_each_entry,
+-       * in each configuration its functions are bound in sequence
+-       * with list_for_each_entry, so we assume no race condition
+-       * with regard to gether_opts->bound access
+-       */
+-      if (!gether_opts->bound) {
+-              mutex_lock(&gether_opts->lock);
+-              gether_set_gadget(gether_opts->net, cdev->gadget);
+-              status = gether_register_netdev(gether_opts->net);
+-              mutex_unlock(&gether_opts->lock);
+-              if (status)
+-                      return status;
+-              gether_opts->bound = true;
+-      }
++      scoped_guard(mutex, &gether_opts->lock)
++              if (gether_opts->bind_count == 0 && !gether_opts->bound) {
++                      if (!device_is_registered(&gether_opts->net->dev)) {
++                              gether_set_gadget(gether_opts->net, cdev->gadget);
++                              status = gether_register_netdev(gether_opts->net);
++                      } else
++                              status = gether_attach_gadget(gether_opts->net, cdev->gadget);
++
++                      if (status)
++                              return status;
++                      net = gether_opts->net;
++              }
+       us = usb_gstrings_attach(cdev, geth_strings,
+                                ARRAY_SIZE(geth_string_defs));
+@@ -330,20 +327,18 @@ geth_bind(struct usb_configuration *c, s
+       /* allocate instance-specific interface IDs */
+       status = usb_interface_id(c, f);
+       if (status < 0)
+-              goto fail;
++              return status;
+       subset_data_intf.bInterfaceNumber = status;
+-      status = -ENODEV;
+-
+       /* allocate instance-specific endpoints */
+       ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_in_desc);
+       if (!ep)
+-              goto fail;
++              return -ENODEV;
+       geth->port.in_ep = ep;
+       ep = usb_ep_autoconfig(cdev->gadget, &fs_subset_out_desc);
+       if (!ep)
+-              goto fail;
++              return -ENODEV;
+       geth->port.out_ep = ep;
+       /* support all relevant hardware speeds... we expect that when
+@@ -361,21 +356,19 @@ geth_bind(struct usb_configuration *c, s
+       status = usb_assign_descriptors(f, fs_eth_function, hs_eth_function,
+                       ss_eth_function, ss_eth_function);
+       if (status)
+-              goto fail;
++              return status;
+       /* NOTE:  all that is done without knowing or caring about
+        * the network link ... which is unavailable to this code
+        * until we're activated via set_alt().
+        */
++      gether_opts->bind_count++;
++      retain_and_null_ptr(net);
++
+       DBG(cdev, "CDC Subset: IN/%s OUT/%s\n",
+                       geth->port.in_ep->name, geth->port.out_ep->name);
+       return 0;
+-
+-fail:
+-      ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
+-
+-      return status;
+ }
+ static inline struct f_gether_opts *to_f_gether_opts(struct config_item *item)
+@@ -418,7 +411,7 @@ static void geth_free_inst(struct usb_fu
+       struct f_gether_opts *opts;
+       opts = container_of(f, struct f_gether_opts, func_inst);
+-      if (opts->bound)
++      if (device_is_registered(&opts->net->dev))
+               gether_cleanup(netdev_priv(opts->net));
+       else
+               free_netdev(opts->net);
+@@ -462,8 +455,16 @@ static void geth_free(struct usb_functio
+ static void geth_unbind(struct usb_configuration *c, struct usb_function *f)
+ {
++      struct f_gether_opts *opts;
++
++      opts = container_of(f->fi, struct f_gether_opts, func_inst);
++
+       geth_string_defs[0].id = 0;
+       usb_free_all_descriptors(f);
++
++      opts->bind_count--;
++      if (opts->bind_count == 0 && !opts->bound)
++              gether_detach_gadget(opts->net);
+ }
+ static struct usb_function *geth_alloc(struct usb_function_instance *fi)
+--- a/drivers/usb/gadget/function/u_gether.h
++++ b/drivers/usb/gadget/function/u_gether.h
+@@ -15,17 +15,25 @@
+ #include <linux/usb/composite.h>
++/**
++ * struct f_gether_opts - subset function options
++ * @func_inst: USB function instance.
++ * @net: The net_device associated with the subset function.
++ * @bound: True if the net_device is shared and pre-registered during the
++ *         legacy composite driver's bind phase (e.g., multi.c). If false,
++ *         the subset function will register the net_device during its own
++ *         bind phase.
++ * @bind_count: Tracks the number of configurations the subset function is
++ *              bound to, preventing double-registration of the @net device.
++ * @lock: Protects the data from concurrent access by configfs read/write
++ *        and create symlink/remove symlink operations.
++ * @refcnt: Reference counter for the function instance.
++ */
+ struct f_gether_opts {
+       struct usb_function_instance    func_inst;
+       struct net_device               *net;
+       bool                            bound;
+-
+-      /*
+-       * Read/write access to configfs attributes is handled by configfs.
+-       *
+-       * This is to protect the data from concurrent access by read/write
+-       * and create symlink/remove symlink.
+-       */
++      int                             bind_count;
+       struct mutex                    lock;
+       int                             refcnt;
+ };
diff --git a/queue-6.12/usb-gadget-f_subset-fix-unbalanced-refcnt-in-geth_free.patch b/queue-6.12/usb-gadget-f_subset-fix-unbalanced-refcnt-in-geth_free.patch
new file mode 100644 (file)
index 0000000..deb2b5e
--- /dev/null
@@ -0,0 +1,48 @@
+From caa27923aacd8a5869207842f2ab1657c6c0c7bc Mon Sep 17 00:00:00 2001
+From: Kuen-Han Tsai <khtsai@google.com>
+Date: Fri, 20 Mar 2026 16:54:44 +0800
+Subject: usb: gadget: f_subset: Fix unbalanced refcnt in geth_free
+
+From: Kuen-Han Tsai <khtsai@google.com>
+
+commit caa27923aacd8a5869207842f2ab1657c6c0c7bc upstream.
+
+geth_alloc() increments the reference count, but geth_free() fails to
+decrement it. This prevents the configuration of attributes via configfs
+after unlinking the function.
+
+Decrement the reference count in geth_free() to ensure proper cleanup.
+
+Fixes: 02832e56f88a ("usb: gadget: f_subset: add configfs support")
+Cc: stable@vger.kernel.org
+Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
+Link: https://patch.msgid.link/20260320-usb-net-lifecycle-v1-1-4886b578161b@google.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/f_subset.c |    6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/drivers/usb/gadget/function/f_subset.c
++++ b/drivers/usb/gadget/function/f_subset.c
+@@ -6,6 +6,7 @@
+  * Copyright (C) 2008 Nokia Corporation
+  */
++#include <linux/cleanup.h>
+ #include <linux/slab.h>
+ #include <linux/kernel.h>
+ #include <linux/module.h>
+@@ -449,8 +450,13 @@ static struct usb_function_instance *get
+ static void geth_free(struct usb_function *f)
+ {
+       struct f_gether *eth;
++      struct f_gether_opts *opts;
++
++      opts = container_of(f->fi, struct f_gether_opts, func_inst);
+       eth = func_to_geth(f);
++      scoped_guard(mutex, &opts->lock)
++              opts->refcnt--;
+       kfree(eth);
+ }
diff --git a/queue-6.12/usb-gadget-f_uac1_legacy-validate-control-request-size.patch b/queue-6.12/usb-gadget-f_uac1_legacy-validate-control-request-size.patch
new file mode 100644 (file)
index 0000000..a87e0d1
--- /dev/null
@@ -0,0 +1,92 @@
+From 6e0e34d85cd46ceb37d16054e97a373a32770f6c Mon Sep 17 00:00:00 2001
+From: Taegu Ha <hataegu0826@gmail.com>
+Date: Thu, 2 Apr 2026 04:13:11 +0900
+Subject: usb: gadget: f_uac1_legacy: validate control request size
+
+From: Taegu Ha <hataegu0826@gmail.com>
+
+commit 6e0e34d85cd46ceb37d16054e97a373a32770f6c upstream.
+
+f_audio_complete() copies req->length bytes into a 4-byte stack
+variable:
+
+  u32 data = 0;
+  memcpy(&data, req->buf, req->length);
+
+req->length is derived from the host-controlled USB request path,
+which can lead to a stack out-of-bounds write.
+
+Validate req->actual against the expected payload size for the
+supported control selectors and decode only the expected amount
+of data.
+
+This avoids copying a host-influenced length into a fixed-size
+stack object.
+
+Signed-off-by: Taegu Ha <hataegu0826@gmail.com>
+Cc: stable <stable@kernel.org>
+Link: https://patch.msgid.link/20260401191311.3604898-1-hataegu0826@gmail.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/f_uac1_legacy.c |   47 ++++++++++++++++++++++------
+ 1 file changed, 37 insertions(+), 10 deletions(-)
+
+--- a/drivers/usb/gadget/function/f_uac1_legacy.c
++++ b/drivers/usb/gadget/function/f_uac1_legacy.c
+@@ -360,19 +360,46 @@ static int f_audio_out_ep_complete(struc
+ static void f_audio_complete(struct usb_ep *ep, struct usb_request *req)
+ {
+       struct f_audio *audio = req->context;
+-      int status = req->status;
+-      u32 data = 0;
+       struct usb_ep *out_ep = audio->out_ep;
+-      switch (status) {
+-
+-      case 0:                         /* normal completion? */
+-              if (ep == out_ep)
++      switch (req->status) {
++      case 0:
++              if (ep == out_ep) {
+                       f_audio_out_ep_complete(ep, req);
+-              else if (audio->set_con) {
+-                      memcpy(&data, req->buf, req->length);
+-                      audio->set_con->set(audio->set_con, audio->set_cmd,
+-                                      le16_to_cpu(data));
++              } else if (audio->set_con) {
++                      struct usb_audio_control *con = audio->set_con;
++                      u8 type = con->type;
++                      u32 data;
++                      bool valid_request = false;
++
++                      switch (type) {
++                      case UAC_FU_MUTE: {
++                              u8 value;
++
++                              if (req->actual == sizeof(value)) {
++                                      memcpy(&value, req->buf, sizeof(value));
++                                      data = value;
++                                      valid_request = true;
++                              }
++                              break;
++                      }
++                      case UAC_FU_VOLUME: {
++                              __le16 value;
++
++                              if (req->actual == sizeof(value)) {
++                                      memcpy(&value, req->buf, sizeof(value));
++                                      data = le16_to_cpu(value);
++                                      valid_request = true;
++                              }
++                              break;
++                      }
++                      }
++
++                      if (valid_request)
++                              con->set(con, audio->set_cmd, data);
++                      else
++                              usb_ep_set_halt(ep);
++
+                       audio->set_con = NULL;
+               }
+               break;
diff --git a/queue-6.12/usb-gadget-u_ether-fix-null-pointer-deref-in-eth_get_drvinfo.patch b/queue-6.12/usb-gadget-u_ether-fix-null-pointer-deref-in-eth_get_drvinfo.patch
new file mode 100644 (file)
index 0000000..8a5b9b3
--- /dev/null
@@ -0,0 +1,54 @@
+From e002e92e88e12457373ed096b18716d97e7bbb20 Mon Sep 17 00:00:00 2001
+From: Kuen-Han Tsai <khtsai@google.com>
+Date: Mon, 16 Mar 2026 15:49:09 +0800
+Subject: usb: gadget: u_ether: Fix NULL pointer deref in eth_get_drvinfo
+
+From: Kuen-Han Tsai <khtsai@google.com>
+
+commit e002e92e88e12457373ed096b18716d97e7bbb20 upstream.
+
+Commit ec35c1969650 ("usb: gadget: f_ncm: Fix net_device lifecycle with
+device_move") reparents the gadget device to /sys/devices/virtual during
+unbind, clearing the gadget pointer. If the userspace tool queries on
+the surviving interface during this detached window, this leads to a
+NULL pointer dereference.
+
+Unable to handle kernel NULL pointer dereference
+Call trace:
+ eth_get_drvinfo+0x50/0x90
+ ethtool_get_drvinfo+0x5c/0x1f0
+ __dev_ethtool+0xaec/0x1fe0
+ dev_ethtool+0x134/0x2e0
+ dev_ioctl+0x338/0x560
+
+Add a NULL check for dev->gadget in eth_get_drvinfo(). When detached,
+skip copying the fw_version and bus_info strings, which is natively
+handled by ethtool_get_drvinfo for empty strings.
+
+Suggested-by: Val Packett <val@packett.cool>
+Reported-by: Val Packett <val@packett.cool>
+Closes: https://lore.kernel.org/linux-usb/10890524-cf83-4a71-b879-93e2b2cc1fcc@packett.cool/
+Fixes: ec35c1969650 ("usb: gadget: f_ncm: Fix net_device lifecycle with device_move")
+Cc: stable <stable@kernel.org>
+Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
+Link: https://patch.msgid.link/20260316-eth-null-deref-v1-1-07005f33be85@google.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/u_ether.c |    6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+--- a/drivers/usb/gadget/function/u_ether.c
++++ b/drivers/usb/gadget/function/u_ether.c
+@@ -112,8 +112,10 @@ static void eth_get_drvinfo(struct net_d
+       strscpy(p->driver, "g_ether", sizeof(p->driver));
+       strscpy(p->version, UETH__VERSION, sizeof(p->version));
+-      strscpy(p->fw_version, dev->gadget->name, sizeof(p->fw_version));
+-      strscpy(p->bus_info, dev_name(&dev->gadget->dev), sizeof(p->bus_info));
++      if (dev->gadget) {
++              strscpy(p->fw_version, dev->gadget->name, sizeof(p->fw_version));
++              strscpy(p->bus_info, dev_name(&dev->gadget->dev), sizeof(p->bus_info));
++      }
+ }
+ /* REVISIT can also support:
diff --git a/queue-6.12/usb-gadget-u_ether-fix-race-between-gether_disconnect-and-eth_stop.patch b/queue-6.12/usb-gadget-u_ether-fix-race-between-gether_disconnect-and-eth_stop.patch
new file mode 100644 (file)
index 0000000..848ef40
--- /dev/null
@@ -0,0 +1,77 @@
+From e1eabb072c75681f78312c484ccfffb7430f206e Mon Sep 17 00:00:00 2001
+From: Kuen-Han Tsai <khtsai@google.com>
+Date: Wed, 11 Mar 2026 17:12:15 +0800
+Subject: usb: gadget: u_ether: Fix race between gether_disconnect and eth_stop
+
+From: Kuen-Han Tsai <khtsai@google.com>
+
+commit e1eabb072c75681f78312c484ccfffb7430f206e upstream.
+
+A race condition between gether_disconnect() and eth_stop() leads to a
+NULL pointer dereference. Specifically, if eth_stop() is triggered
+concurrently while gether_disconnect() is tearing down the endpoints,
+eth_stop() attempts to access the cleared endpoint descriptor, causing
+the following NPE:
+
+  Unable to handle kernel NULL pointer dereference
+  Call trace:
+   __dwc3_gadget_ep_enable+0x60/0x788
+   dwc3_gadget_ep_enable+0x70/0xe4
+   usb_ep_enable+0x60/0x15c
+   eth_stop+0xb8/0x108
+
+Because eth_stop() crashes while holding the dev->lock, the thread
+running gether_disconnect() fails to acquire the same lock and spins
+forever, resulting in a hardlockup:
+
+  Core - Debugging Information for Hardlockup core(7)
+  Call trace:
+   queued_spin_lock_slowpath+0x94/0x488
+   _raw_spin_lock+0x64/0x6c
+   gether_disconnect+0x19c/0x1e8
+   ncm_set_alt+0x68/0x1a0
+   composite_setup+0x6a0/0xc50
+
+The root cause is that the clearing of dev->port_usb in
+gether_disconnect() is delayed until the end of the function.
+
+Move the clearing of dev->port_usb to the very beginning of
+gether_disconnect() while holding dev->lock. This cuts off the link
+immediately, ensuring eth_stop() will see dev->port_usb as NULL and
+safely bail out.
+
+Fixes: 2b3d942c4878 ("usb ethernet gadget: split out network core")
+Cc: stable <stable@kernel.org>
+Signed-off-by: Kuen-Han Tsai <khtsai@google.com>
+Link: https://patch.msgid.link/20260311-gether-disconnect-npe-v1-1-454966adf7c7@google.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/u_ether.c |   10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+--- a/drivers/usb/gadget/function/u_ether.c
++++ b/drivers/usb/gadget/function/u_ether.c
+@@ -1222,6 +1222,11 @@ void gether_disconnect(struct gether *li
+       DBG(dev, "%s\n", __func__);
++      spin_lock(&dev->lock);
++      dev->port_usb = NULL;
++      link->is_suspend = false;
++      spin_unlock(&dev->lock);
++
+       netif_stop_queue(dev->net);
+       netif_carrier_off(dev->net);
+@@ -1259,11 +1264,6 @@ void gether_disconnect(struct gether *li
+       dev->header_len = 0;
+       dev->unwrap = NULL;
+       dev->wrap = NULL;
+-
+-      spin_lock(&dev->lock);
+-      dev->port_usb = NULL;
+-      link->is_suspend = false;
+-      spin_unlock(&dev->lock);
+ }
+ EXPORT_SYMBOL_GPL(gether_disconnect);
diff --git a/queue-6.12/usb-gadget-uvc-fix-null-pointer-dereference-during-unbind-race.patch b/queue-6.12/usb-gadget-uvc-fix-null-pointer-dereference-during-unbind-race.patch
new file mode 100644 (file)
index 0000000..7cba4b8
--- /dev/null
@@ -0,0 +1,213 @@
+From eba2936bbe6b752a31725a9eb5c674ecbf21ee7d Mon Sep 17 00:00:00 2001
+From: Jimmy Hu <hhhuuu@google.com>
+Date: Fri, 20 Mar 2026 14:54:27 +0800
+Subject: usb: gadget: uvc: fix NULL pointer dereference during unbind race
+
+From: Jimmy Hu <hhhuuu@google.com>
+
+commit eba2936bbe6b752a31725a9eb5c674ecbf21ee7d upstream.
+
+Commit b81ac4395bbe ("usb: gadget: uvc: allow for application to cleanly
+shutdown") introduced two stages of synchronization waits totaling 1500ms
+in uvc_function_unbind() to prevent several types of kernel panics.
+However, this timing-based approach is insufficient during power
+management (PM) transitions.
+
+When the PM subsystem starts freezing user space processes, the
+wait_event_interruptible_timeout() is aborted early, which allows the
+unbind thread to proceed and nullify the gadget pointer
+(cdev->gadget = NULL):
+
+[  814.123447][  T947] configfs-gadget.g1 gadget.0: uvc: uvc_function_unbind()
+[  814.178583][ T3173] PM: suspend entry (deep)
+[  814.192487][ T3173] Freezing user space processes
+[  814.197668][  T947] configfs-gadget.g1 gadget.0: uvc: uvc_function_unbind no clean disconnect, wait for release
+
+When the PM subsystem resumes or aborts the suspend and tasks are
+restarted, the V4L2 release path is executed and attempts to access the
+already nullified gadget pointer, triggering a kernel panic:
+
+[  814.292597][    C0] PM: pm_system_irq_wakeup: 479 triggered dhdpcie_host_wake
+[  814.386727][ T3173] Restarting tasks ...
+[  814.403522][ T4558] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000030
+[  814.404021][ T4558] pc : usb_gadget_deactivate+0x14/0xf4
+[  814.404031][ T4558] lr : usb_function_deactivate+0x54/0x94
+[  814.404078][ T4558] Call trace:
+[  814.404080][ T4558]  usb_gadget_deactivate+0x14/0xf4
+[  814.404083][ T4558]  usb_function_deactivate+0x54/0x94
+[  814.404087][ T4558]  uvc_function_disconnect+0x1c/0x5c
+[  814.404092][ T4558]  uvc_v4l2_release+0x44/0xac
+[  814.404095][ T4558]  v4l2_release+0xcc/0x130
+
+Address the race condition and NULL pointer dereference by:
+
+1. State Synchronization (flag + mutex)
+Introduce a 'func_unbound' flag in struct uvc_device. This allows
+uvc_function_disconnect() to safely skip accessing the nullified
+cdev->gadget pointer. As suggested by Alan Stern, this flag is protected
+by a new mutex (uvc->lock) to ensure proper memory ordering and prevent
+instruction reordering or speculative loads. This mutex is also used to
+protect 'func_connected' for consistent state management.
+
+2. Explicit Synchronization (completion)
+Use a completion to synchronize uvc_function_unbind() with the
+uvc_vdev_release() callback. This prevents Use-After-Free (UAF) by
+ensuring struct uvc_device is freed after all video device resources
+are released.
+
+Fixes: b81ac4395bbe ("usb: gadget: uvc: allow for application to cleanly shutdown")
+Cc: stable <stable@kernel.org>
+Suggested-by: Alan Stern <stern@rowland.harvard.edu>
+Signed-off-by: Jimmy Hu <hhhuuu@google.com>
+Link: https://patch.msgid.link/20260320065427.1374555-1-hhhuuu@google.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/usb/gadget/function/f_uvc.c    |   39 ++++++++++++++++++++++++++++++---
+ drivers/usb/gadget/function/uvc.h      |    3 ++
+ drivers/usb/gadget/function/uvc_v4l2.c |    5 +++-
+ 3 files changed, 43 insertions(+), 4 deletions(-)
+
+--- a/drivers/usb/gadget/function/f_uvc.c
++++ b/drivers/usb/gadget/function/f_uvc.c
+@@ -409,6 +409,12 @@ uvc_function_disconnect(struct uvc_devic
+ {
+       int ret;
++      guard(mutex)(&uvc->lock);
++      if (uvc->func_unbound) {
++              dev_dbg(&uvc->vdev.dev, "skipping function deactivate (unbound)\n");
++              return;
++      }
++
+       if ((ret = usb_function_deactivate(&uvc->func)) < 0)
+               uvcg_info(&uvc->func, "UVC disconnect failed with %d\n", ret);
+ }
+@@ -427,6 +433,15 @@ static ssize_t function_name_show(struct
+ static DEVICE_ATTR_RO(function_name);
++static void uvc_vdev_release(struct video_device *vdev)
++{
++      struct uvc_device *uvc = video_get_drvdata(vdev);
++
++      /* Signal uvc_function_unbind() that the video device has been released */
++      if (uvc->vdev_release_done)
++              complete(uvc->vdev_release_done);
++}
++
+ static int
+ uvc_register_video(struct uvc_device *uvc)
+ {
+@@ -439,7 +454,7 @@ uvc_register_video(struct uvc_device *uv
+       uvc->vdev.v4l2_dev->dev = &cdev->gadget->dev;
+       uvc->vdev.fops = &uvc_v4l2_fops;
+       uvc->vdev.ioctl_ops = &uvc_v4l2_ioctl_ops;
+-      uvc->vdev.release = video_device_release_empty;
++      uvc->vdev.release = uvc_vdev_release;
+       uvc->vdev.vfl_dir = VFL_DIR_TX;
+       uvc->vdev.lock = &uvc->video.mutex;
+       uvc->vdev.device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
+@@ -655,6 +670,8 @@ uvc_function_bind(struct usb_configurati
+       int ret = -EINVAL;
+       uvcg_info(f, "%s()\n", __func__);
++      scoped_guard(mutex, &uvc->lock)
++              uvc->func_unbound = false;
+       opts = fi_to_f_uvc_opts(f->fi);
+       /* Sanity check the streaming endpoint module parameters. */
+@@ -984,12 +1001,19 @@ static void uvc_free(struct usb_function
+ static void uvc_function_unbind(struct usb_configuration *c,
+                               struct usb_function *f)
+ {
++      DECLARE_COMPLETION_ONSTACK(vdev_release_done);
+       struct usb_composite_dev *cdev = c->cdev;
+       struct uvc_device *uvc = to_uvc(f);
+       struct uvc_video *video = &uvc->video;
+       long wait_ret = 1;
++      bool connected;
+       uvcg_info(f, "%s()\n", __func__);
++      scoped_guard(mutex, &uvc->lock) {
++              uvc->func_unbound = true;
++              uvc->vdev_release_done = &vdev_release_done;
++              connected = uvc->func_connected;
++      }
+       if (video->async_wq)
+               destroy_workqueue(video->async_wq);
+@@ -1000,7 +1024,7 @@ static void uvc_function_unbind(struct u
+        * though the video device removal uevent. Allow some time for the
+        * application to close out before things get deleted.
+        */
+-      if (uvc->func_connected) {
++      if (connected) {
+               uvcg_dbg(f, "waiting for clean disconnect\n");
+               wait_ret = wait_event_interruptible_timeout(uvc->func_connected_queue,
+                               uvc->func_connected == false, msecs_to_jiffies(500));
+@@ -1011,7 +1035,10 @@ static void uvc_function_unbind(struct u
+       video_unregister_device(&uvc->vdev);
+       v4l2_device_unregister(&uvc->v4l2_dev);
+-      if (uvc->func_connected) {
++      scoped_guard(mutex, &uvc->lock)
++              connected = uvc->func_connected;
++
++      if (connected) {
+               /*
+                * Wait for the release to occur to ensure there are no longer any
+                * pending operations that may cause panics when resources are cleaned
+@@ -1023,6 +1050,10 @@ static void uvc_function_unbind(struct u
+               uvcg_dbg(f, "done waiting for release with ret: %ld\n", wait_ret);
+       }
++      /* Wait for the video device to be released */
++      wait_for_completion(&vdev_release_done);
++      uvc->vdev_release_done = NULL;
++
+       usb_ep_free_request(cdev->gadget->ep0, uvc->control_req);
+       kfree(uvc->control_buf);
+@@ -1041,6 +1072,8 @@ static struct usb_function *uvc_alloc(st
+               return ERR_PTR(-ENOMEM);
+       mutex_init(&uvc->video.mutex);
++      mutex_init(&uvc->lock);
++      uvc->func_unbound = true;
+       uvc->state = UVC_STATE_DISCONNECTED;
+       init_waitqueue_head(&uvc->func_connected_queue);
+       opts = fi_to_f_uvc_opts(fi);
+--- a/drivers/usb/gadget/function/uvc.h
++++ b/drivers/usb/gadget/function/uvc.h
+@@ -141,6 +141,9 @@ struct uvc_device {
+       enum uvc_state state;
+       struct usb_function func;
+       struct uvc_video video;
++      struct completion *vdev_release_done;
++      struct mutex lock;      /* protects func_unbound and func_connected */
++      bool func_unbound;
+       bool func_connected;
+       wait_queue_head_t func_connected_queue;
+--- a/drivers/usb/gadget/function/uvc_v4l2.c
++++ b/drivers/usb/gadget/function/uvc_v4l2.c
+@@ -512,6 +512,8 @@ uvc_v4l2_subscribe_event(struct v4l2_fh
+       if (sub->type < UVC_EVENT_FIRST || sub->type > UVC_EVENT_LAST)
+               return -EINVAL;
++      guard(mutex)(&uvc->lock);
++
+       if (sub->type == UVC_EVENT_SETUP && uvc->func_connected)
+               return -EBUSY;
+@@ -533,7 +535,8 @@ static void uvc_v4l2_disable(struct uvc_
+       uvc_function_disconnect(uvc);
+       uvcg_video_disable(&uvc->video);
+       uvcg_free_buffers(&uvc->video.queue);
+-      uvc->func_connected = false;
++      scoped_guard(mutex, &uvc->lock)
++              uvc->func_connected = false;
+       wake_up_interruptible(&uvc->func_connected_queue);
+ }