From: Greg Kroah-Hartman Date: Wed, 8 Apr 2026 12:04:15 +0000 (+0200) Subject: 6.12-stable patches X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=00304b56c272003c6b61cacfc9efe1f92272cd4f;p=thirdparty%2Fkernel%2Fstable-queue.git 6.12-stable patches 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 --- diff --git a/queue-6.12/series b/queue-6.12/series index a092681683..a50945e557 100644 --- a/queue-6.12/series +++ b/queue-6.12/series @@ -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 index 0000000000..4ebe3b25b4 --- /dev/null +++ b/queue-6.12/usb-gadget-f_ecm-fix-net_device-lifecycle-with-device_move.patch @@ -0,0 +1,160 @@ +From b2cc4fae67a51f60d81d6af2678696accb07c656 Mon Sep 17 00:00:00 2001 +From: Kuen-Han Tsai +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 + +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 +Link: https://patch.msgid.link/20260320-usb-net-lifecycle-v1-4-4886b578161b@google.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 + ++/** ++ * 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 index 0000000000..8925877940 --- /dev/null +++ b/queue-6.12/usb-gadget-f_eem-fix-net_device-lifecycle-with-device_move.patch @@ -0,0 +1,198 @@ +From d9270c9a8118c1535409db926ac1e2545dc97b81 Mon Sep 17 00:00:00 2001 +From: Kuen-Han Tsai +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 + +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 +Link: https://patch.msgid.link/20260320-usb-net-lifecycle-v1-5-4886b578161b@google.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + #include + #include +@@ -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 + ++/** ++ * 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 index 0000000000..801214adea --- /dev/null +++ b/queue-6.12/usb-gadget-f_hid-move-list-and-spinlock-inits-from-bind-to-alloc.patch @@ -0,0 +1,75 @@ +From 4e0a88254ad59f6c53a34bf5fa241884ec09e8b2 Mon Sep 17 00:00:00 2001 +From: Michael Zimmermann +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 + +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 +Cc: stable +Link: https://patch.msgid.link/20260331184844.2388761-1-sigmaepsilon92@gmail.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..5cfcac70e7 --- /dev/null +++ b/queue-6.12/usb-gadget-f_rndis-fix-net_device-lifecycle-with-device_move.patch @@ -0,0 +1,190 @@ +From e367599529dc42578545a7f85fde517b35b3cda7 Mon Sep 17 00:00:00 2001 +From: Kuen-Han Tsai +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 + +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 +Link: https://patch.msgid.link/20260320-usb-net-lifecycle-v1-7-4886b578161b@google.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 + ++/** ++ * 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 index 0000000000..623324b356 --- /dev/null +++ b/queue-6.12/usb-gadget-f_rndis-protect-rndis-options-with-mutex.patch @@ -0,0 +1,49 @@ +From 8d8c68b1fc06ece60cf43e1306ff0f4ac121547e Mon Sep 17 00:00:00 2001 +From: Kuen-Han Tsai +Date: Fri, 20 Mar 2026 16:54:45 +0800 +Subject: usb: gadget: f_rndis: Protect RNDIS options with mutex + +From: Kuen-Han Tsai + +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 +Link: https://patch.msgid.link/20260320-usb-net-lifecycle-v1-2-4886b578161b@google.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + #include + #include +@@ -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 index 0000000000..7d0991edc8 --- /dev/null +++ b/queue-6.12/usb-gadget-f_subset-fix-net_device-lifecycle-with-device_move.patch @@ -0,0 +1,193 @@ +From 06524cd1c9011bee141a87e43ab878641ed3652b Mon Sep 17 00:00:00 2001 +From: Kuen-Han Tsai +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 + +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 +Link: https://patch.msgid.link/20260320-usb-net-lifecycle-v1-6-4886b578161b@google.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 + ++/** ++ * 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 index 0000000000..deb2b5ed8a --- /dev/null +++ b/queue-6.12/usb-gadget-f_subset-fix-unbalanced-refcnt-in-geth_free.patch @@ -0,0 +1,48 @@ +From caa27923aacd8a5869207842f2ab1657c6c0c7bc Mon Sep 17 00:00:00 2001 +From: Kuen-Han Tsai +Date: Fri, 20 Mar 2026 16:54:44 +0800 +Subject: usb: gadget: f_subset: Fix unbalanced refcnt in geth_free + +From: Kuen-Han Tsai + +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 +Link: https://patch.msgid.link/20260320-usb-net-lifecycle-v1-1-4886b578161b@google.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + #include + #include +@@ -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 index 0000000000..a87e0d14b0 --- /dev/null +++ b/queue-6.12/usb-gadget-f_uac1_legacy-validate-control-request-size.patch @@ -0,0 +1,92 @@ +From 6e0e34d85cd46ceb37d16054e97a373a32770f6c Mon Sep 17 00:00:00 2001 +From: Taegu Ha +Date: Thu, 2 Apr 2026 04:13:11 +0900 +Subject: usb: gadget: f_uac1_legacy: validate control request size + +From: Taegu Ha + +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 +Cc: stable +Link: https://patch.msgid.link/20260401191311.3604898-1-hataegu0826@gmail.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..8a5b9b334c --- /dev/null +++ b/queue-6.12/usb-gadget-u_ether-fix-null-pointer-deref-in-eth_get_drvinfo.patch @@ -0,0 +1,54 @@ +From e002e92e88e12457373ed096b18716d97e7bbb20 Mon Sep 17 00:00:00 2001 +From: Kuen-Han Tsai +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 + +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 +Reported-by: Val Packett +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 +Signed-off-by: Kuen-Han Tsai +Link: https://patch.msgid.link/20260316-eth-null-deref-v1-1-07005f33be85@google.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..848ef40297 --- /dev/null +++ b/queue-6.12/usb-gadget-u_ether-fix-race-between-gether_disconnect-and-eth_stop.patch @@ -0,0 +1,77 @@ +From e1eabb072c75681f78312c484ccfffb7430f206e Mon Sep 17 00:00:00 2001 +From: Kuen-Han Tsai +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 + +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 +Signed-off-by: Kuen-Han Tsai +Link: https://patch.msgid.link/20260311-gether-disconnect-npe-v1-1-454966adf7c7@google.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..7cba4b83ed --- /dev/null +++ b/queue-6.12/usb-gadget-uvc-fix-null-pointer-dereference-during-unbind-race.patch @@ -0,0 +1,213 @@ +From eba2936bbe6b752a31725a9eb5c674ecbf21ee7d Mon Sep 17 00:00:00 2001 +From: Jimmy Hu +Date: Fri, 20 Mar 2026 14:54:27 +0800 +Subject: usb: gadget: uvc: fix NULL pointer dereference during unbind race + +From: Jimmy Hu + +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 +Suggested-by: Alan Stern +Signed-off-by: Jimmy Hu +Link: https://patch.msgid.link/20260320065427.1374555-1-hhhuuu@google.com +Signed-off-by: Greg Kroah-Hartman +Signed-off-by: Greg Kroah-Hartman +--- + 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); + } +